From 5d39314bd216b8818face21ec8be95d9f7cfff18 Mon Sep 17 00:00:00 2001 From: Xiu Hong Kooi Date: Wed, 1 Nov 2023 06:55:11 +0800 Subject: [PATCH 001/214] #3376 support mapping from iterables to collection --- .../internal/conversion/ConversionUtils.java | 1 + .../ap/internal/model/PropertyMapping.java | 3 +- .../creation/MappingResolverImpl.java | 1 - .../test/collection/iterabletolist/Fruit.java | 27 ++++++++++ .../collection/iterabletolist/FruitSalad.java | 29 +++++++++++ .../iterabletolist/FruitsMapper.java | 24 +++++++++ .../collection/iterabletolist/FruitsMenu.java | 35 +++++++++++++ .../IterableToListMappingTest.java | 47 +++++++++++++++++ .../test/collection/iterabletoset/Fruit.java | 27 ++++++++++ .../collection/iterabletoset/FruitSalad.java | 29 +++++++++++ .../iterabletoset/FruitsMapper.java | 24 +++++++++ .../collection/iterabletoset/FruitsMenu.java | 35 +++++++++++++ .../IterableToSetMappingTest.java | 50 +++++++++++++++++++ 13 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 496d11676f..aa01a73276 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -278,4 +278,5 @@ public static String uuid(ConversionContext conversionContext) { public static String url(ConversionContext conversionContext) { return typeReferenceName( conversionContext, URL.class ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index b81cf55447..50f23ecbff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -301,7 +301,8 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get private Assignment forge( ) { Assignment assignment; Type sourceType = rightHandSide.getSourceType(); - if ( (sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() ) { + if ( ( sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() + || ( sourceType.isIterableType() && targetType.isCollectionType() ) ) { assignment = forgeIterableMapping( sourceType, targetType, rightHandSide ); } else if ( sourceType.isMapType() && targetType.isMapType() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 32fa1af931..b290ff56bc 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -434,7 +434,6 @@ private boolean allow2Steps() { private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); - if ( conversionProvider == null ) { return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java new file mode 100644 index 0000000000..c989aa4e19 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletolist; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java new file mode 100644 index 0000000000..6a04121044 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletolist; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(List fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java new file mode 100644 index 0000000000..bb4fdc66af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletolist; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java new file mode 100644 index 0000000000..6f272dc454 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletolist; + +import java.util.Iterator; +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private List fruits; + + public FruitsMenu(List fruits) { + this.fruits = fruits; + } + + public List getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java new file mode 100644 index 0000000000..915b9e5fd4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletolist; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToListMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToList() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + assertThat( itr.next().getType() ).isEqualTo( "mango" ); + assertThat( itr.next().getType() ).isEqualTo( "apple" ); + assertThat( itr.next().getType() ).isEqualTo( "banana" ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapListToIterable() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).containsExactly( "mango", "apple", "banana" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java new file mode 100644 index 0000000000..a15b344908 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletoset; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java new file mode 100644 index 0000000000..b92ac3160c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletoset; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(Iterable fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java new file mode 100644 index 0000000000..413dd73617 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletoset; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java new file mode 100644 index 0000000000..8ae4b8b632 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletoset; + +import java.util.Iterator; +import java.util.Set; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private Set fruits; + + public FruitsMenu(Set fruits) { + this.fruits = fruits; + } + + public Set getFruits() { + return fruits; + } + + public void setFruits(Set fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java new file mode 100644 index 0000000000..8b2373d4d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.iterabletoset; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToSetMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToSet() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + Set fruitTypes = fruits.stream().map( Fruit::getType ).collect( Collectors.toSet() ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapSetToIterable() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).contains( "mango", "apple", "banana" ); + } +} From b77d321ffb06f42eab9219e120fdf1782bccef1b Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:01:45 +0100 Subject: [PATCH 002/214] Added recent contributors (including myself) --- copyright.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/copyright.txt b/copyright.txt index 1b23a0e84a..a111489d01 100644 --- a/copyright.txt +++ b/copyright.txt @@ -44,6 +44,7 @@ Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis Michael Pardo - https://github.com/pardom Mustafa Caylak - https://github.com/luxmeter Oliver Ehrenmüller - https://github.com/greuelpirat +Oliver Erhart - https://github.com/thunderhook Paul Strugnell - https://github.com/ps-powa Pascal Grün - https://github.com/pascalgn Pavel Makhov - https://github.com/streetturtle @@ -67,3 +68,4 @@ Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel +Xiu Hong Kooi - https://github.com/kooixh From 79f01e2de09c34ee08fef5288f8d8fa686250174 Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sat, 4 Nov 2023 21:35:40 +0100 Subject: [PATCH 003/214] Change master to main branch and fix CI status badge (#3423) --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8b8af9a69f..21d820dc58 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,10 @@ [![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.5.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) -[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) +[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) -[![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=master)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amaster+workflow%3ACI) -[![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct) +[![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=main)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amain+workflow%3ACI) +[![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct/tree/main) [![Gitter](https://img.shields.io/gitter/room/mapstruct/mapstruct.svg)](https://gitter.im/mapstruct/mapstruct-users) [![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/context:java) [![Total Alerts](https://img.shields.io/lgtm/alerts/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/alerts) From 0ac0c42dbc481a86879e3796d0201c622da61200 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 4 Nov 2023 23:06:16 +0100 Subject: [PATCH 004/214] [maven-release-plugin] prepare release 1.6.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..2e244492d9 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..ddf9514f19 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..5a6a8f500c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..e04b31cd73 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..49f1dc3d6e 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..f9f43c871c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 73239c87e3..6701e069f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 pom MapStruct Parent @@ -71,7 +71,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.6.0.Beta1 diff --git a/pom.xml b/pom.xml index 25a5ead6bc..4c99f88d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.6.0.Beta1 diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..5ab8d5027f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml From 04deac2b3ae4b9ffe1c31ce293ee2d6ad3b20418 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 4 Nov 2023 23:06:17 +0100 Subject: [PATCH 005/214] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 2e244492d9..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index ddf9514f19..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 5a6a8f500c..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index e04b31cd73..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 49f1dc3d6e..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f9f43c871c..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 6701e069f7..73239c87e3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -71,7 +71,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.6.0.Beta1 + HEAD diff --git a/pom.xml b/pom.xml index 4c99f88d0a..25a5ead6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.6.0.Beta1 + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 5ab8d5027f..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml From 2af291ce2f1a9978c9d3f449d8bb8a9bb3b969f2 Mon Sep 17 00:00:00 2001 From: wandi34 Date: Sat, 11 Nov 2023 21:48:52 +0100 Subject: [PATCH 006/214] Fix Typo Mappper in SubclassMapping Doc --- copyright.txt | 1 + core/src/main/java/org/mapstruct/SubclassMapping.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/copyright.txt b/copyright.txt index a111489d01..d6dc8d1b8a 100644 --- a/copyright.txt +++ b/copyright.txt @@ -68,4 +68,5 @@ Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel +Winter Andreas - https://github.dev/wandi34 Xiu Hong Kooi - https://github.com/kooixh diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java index ccf8d4d472..f5463b9026 100644 --- a/core/src/main/java/org/mapstruct/SubclassMapping.java +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java @@ -49,7 +49,7 @@ * } * * Example 2: For parents that can be created. (e.g. normal classes or interfaces with - * @Mappper( uses = ObjectFactory.class ) ) + * @Mapper( uses = ObjectFactory.class ) ) *

  * // generates
  * @Override

From 2bb2aefed8957861bf297b8f953b40e8753a8c75 Mon Sep 17 00:00:00 2001
From: Muhammad Usama 
Date: Fri, 24 Nov 2023 10:34:15 +0500
Subject: [PATCH 007/214] #3413 Using Mapping#expression and
 Mapping#conditionaQualifiedBy(Name) should lead to compile error

---
 copyright.txt                                 |  1 +
 .../internal/model/source/MappingOptions.java |  5 ++-
 .../mapstruct/ap/internal/util/Message.java   |  1 +
 .../test/bugs/_3413/Erroneous3413Mapper.java  | 45 +++++++++++++++++++
 .../ap/test/bugs/_3413/Issue3413Test.java     | 35 +++++++++++++++
 5 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java

diff --git a/copyright.txt b/copyright.txt
index d6dc8d1b8a..71d47a796a 100644
--- a/copyright.txt
+++ b/copyright.txt
@@ -42,6 +42,7 @@ Kevin Grüneberg - https://github.com/kevcodez
 Lukas Lazar - https://github.com/LukeLaz
 Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis
 Michael Pardo - https://github.com/pardom
+Muhammad Usama - https://github.com/the-mgi
 Mustafa Caylak - https://github.com/luxmeter
 Oliver Ehrenmüller - https://github.com/greuelpirat
 Oliver Erhart - https://github.com/thunderhook
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
index 2af1c95f71..dd5b2f17b9 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
@@ -207,10 +207,13 @@ private static boolean isConsistent(MappingGem gem, ExecutableElement method,
         if ( gem.source().hasValue() && gem.constant().hasValue() ) {
             message = Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED;
         }
+        else if ( gem.expression().hasValue() && gem.conditionQualifiedByName().hasValue() ) {
+            message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED;
+        }
         else if ( gem.source().hasValue() && gem.expression().hasValue() ) {
             message = Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED;
         }
-        else if (gem.expression().hasValue() && gem.constant().hasValue() ) {
+        else if ( gem.expression().hasValue() && gem.constant().hasValue() ) {
             message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED;
         }
         else if ( gem.expression().hasValue() && gem.defaultValue().hasValue() ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
index a24a43036c..4b43315390 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
@@ -84,6 +84,7 @@ public enum Message {
     PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET("The type of parameter \"%s\" has no property named \"%s\". Please define the source property explicitly."),
     PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."),
     PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ),
+    PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ),
 
     CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ),
     CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ),
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
new file mode 100644
index 0000000000..784e47b8e7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3413;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Muhammad Usama
+ */
+@Mapper
+public interface Erroneous3413Mapper {
+    Erroneous3413Mapper INSTANCE = Mappers.getMapper( Erroneous3413Mapper.class );
+
+    @Mapping(target = "", expression = "", conditionQualifiedByName = "")
+    ToPOJO map(FromPOJO fromPOJO);
+
+    class FromPOJO {
+        private String value;
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+    }
+
+    class ToPOJO {
+        private String value;
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+    }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java
new file mode 100644
index 0000000000..f98ed9cdbd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3413;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+
+/**
+ * @author Muhammad Usama
+ */
+@IssueKey("3413")
+public class Issue3413Test {
+    @ProcessorTest
+    @WithClasses(Erroneous3413Mapper.class)
+    @ExpectedCompilationOutcome(
+        value = CompilationResult.FAILED,
+        diagnostics = {
+            @Diagnostic(
+                kind = javax.tools.Diagnostic.Kind.ERROR,
+                line = 19,
+                message = "Expression and condition qualified by name are both defined in @Mapping, " +
+                    "either define an expression or a condition qualified by name."
+            )
+        }
+    )
+    void errorExpectedBecauseExpressionAndConditionQualifiedByNameCannotCoExists() {
+    }
+}

From 930f5709b65d6f8a573172247ad02293649c08a9 Mon Sep 17 00:00:00 2001
From: Ravil Galeyev 
Date: Wed, 29 Nov 2023 22:25:22 +0100
Subject: [PATCH 008/214] #3400 Remove unnecessary casts to long and double

---
 .../java/org/mapstruct/ap/internal/util/NativeTypes.java    | 4 ++--
 .../ap/test/source/constants/ConstantsMapperImpl.java       | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
index 769f7b83f0..5498d4c698 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
@@ -284,7 +284,7 @@ void parse(String val, int radix) {
 
         @Override
         public Class getLiteral() {
-            return float.class;
+            return double.class;
         }
 
     }
@@ -363,7 +363,7 @@ else if ( new BigInteger( val, radix ).bitLength() > 64 ) {
 
         @Override
         public Class getLiteral() {
-            return int.class;
+            return long.class;
         }
     }
 
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
index f5f37142c2..ec13eed3fc 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
@@ -33,12 +33,12 @@ public ConstantsTarget mapFromConstants(String dummy) {
         constantsTarget.setIntValue( -03777777 );
         constantsTarget.setIntBoxed( 15 );
         constantsTarget.setLongValue( 0x7fffffffffffffffL );
-        constantsTarget.setLongBoxed( (long) 0xCAFEBABEL );
+        constantsTarget.setLongBoxed( 0xCAFEBABEL );
         constantsTarget.setFloatValue( 1.40e-45f );
         constantsTarget.setFloatBoxed( 3.4028235e38f );
         constantsTarget.setDoubleValue( 1e137 );
-        constantsTarget.setDoubleBoxed( (double) 0x0.001P-1062d );
-        constantsTarget.setDoubleBoxedZero( (double) 0.0 );
+        constantsTarget.setDoubleBoxed( 0x0.001P-1062d );
+        constantsTarget.setDoubleBoxedZero( 0.0 );
 
         return constantsTarget;
     }

From fa857e9ff46b5bb3e5a6c2c8b1c56009944f5ef3 Mon Sep 17 00:00:00 2001
From: mosesonline 
Date: Sun, 10 Dec 2023 15:26:47 +0100
Subject: [PATCH 009/214] bump some lib versions (#3460)

---
 parent/pom.xml                                | 16 ++++-----
 .../testutil/assertions/JavaFileAssert.java   | 34 ++++---------------
 2 files changed, 15 insertions(+), 35 deletions(-)

diff --git a/parent/pom.xml b/parent/pom.xml
index 73239c87e3..1b2c27741d 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -22,17 +22,17 @@
     
         UTF-8
         1.0.0.Alpha3
-        3.0.0-M3
-        3.0.0-M5
+        3.4.1
+        3.2.2
         3.1.0
         5.3.18
         1.6.0
         8.36.1
-        5.8.0-M1
-        1.4.2
+        5.10.1
+        2.2.0
         
         1
-        3.17.2
+        3.24.2
         
         
         jdt_apt
@@ -226,7 +226,7 @@
             
                 org.projectlombok
                 lombok
-                1.18.22
+                1.18.30
             
             
                 org.immutables
@@ -253,7 +253,7 @@
             
                 joda-time
                 joda-time
-                2.9
+                2.12.5
             
 
             
@@ -309,7 +309,7 @@
             
                 commons-io
                 commons-io
-                2.7
+                2.15.0
             
 
             
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 e6eabd9f84..4c6da0d13c 100644
--- a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java
+++ b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java
@@ -5,7 +5,11 @@
  */
 package org.mapstruct.ap.testutil.assertions;
 
-import static java.lang.String.format;
+import org.assertj.core.api.FileAssert;
+import org.assertj.core.error.ShouldHaveSameContent;
+import org.assertj.core.internal.Diff;
+import org.assertj.core.internal.Failures;
+import org.assertj.core.util.diff.Delta;
 
 import java.io.File;
 import java.io.IOException;
@@ -15,14 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.commons.io.FileUtils;
-import org.assertj.core.api.AbstractCharSequenceAssert;
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.FileAssert;
-import org.assertj.core.error.ShouldHaveSameContent;
-import org.assertj.core.internal.Diff;
-import org.assertj.core.internal.Failures;
-import org.assertj.core.util.diff.Delta;
+import static java.lang.String.format;
 
 /**
  * Allows to perform assertions on .java source files.
@@ -39,7 +36,7 @@ public class JavaFileAssert extends FileAssert {
     private static final String IMPORT_GENERATED_ANNOTATION_REGEX = "import javax\\.annotation\\.(processing\\.)?" +
         "Generated;";
 
-    private Diff diff = new Diff();
+    private final Diff diff = new Diff();
 
     /**
      * @param actual the actual file
@@ -48,22 +45,6 @@ public JavaFileAssert(File actual) {
         super( actual );
     }
 
-    /**
-     * @return assertion on the file content
-     */
-    public AbstractCharSequenceAssert content() {
-        exists();
-        isFile();
-
-        try {
-            return Assertions.assertThat( FileUtils.readFileToString( actual, StandardCharsets.UTF_8 ) );
-        }
-        catch ( IOException e ) {
-            failWithMessage( "Unable to read" + actual + ". Exception: " + e.getMessage() );
-        }
-        return null;
-    }
-
     /**
      * Verifies that the specified class is imported in this Java file
      *
@@ -118,7 +99,6 @@ public void hasSameMapperContent(File expected) {
      * or if it is a change delta for the date/comments part of a {@code @Generated} annotation.
      *
      * @param delta that needs to be checked
-     *
      * @return {@code true} if this delta should be ignored, {@code false} otherwise
      */
     private boolean ignoreDelta(Delta delta) {

From 6d99f7b8f312617c8a9526e81fbb2dbf3eaa96a5 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov 
Date: Mon, 18 Dec 2023 07:35:26 +0100
Subject: [PATCH 010/214] #3473 Add Java 21 and EA to build matrix

Fix tests not running green on Java 21
Update Spring to run correctly on Java 21
---
 .github/workflows/java-ea.yml                            | 6 +++---
 .github/workflows/main.yml                               | 2 +-
 parent/pom.xml                                           | 4 ++--
 .../ap/test/conversion/jodatime/JodaConversionTest.java  | 9 ++++-----
 4 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml
index 3cc488ca7e..d9b018bfb8 100644
--- a/.github/workflows/java-ea.yml
+++ b/.github/workflows/java-ea.yml
@@ -17,9 +17,9 @@ jobs:
       - name: 'Checkout'
         uses: actions/checkout@v3
       - name: 'Set up JDK'
-        uses: actions/setup-java@v3
+        uses: oracle-actions/setup-java@v1
         with:
-          distribution: 'zulu'
-          java-version: ${{ matrix.java }}
+          website: jdk.java.net
+          release: EA
       - name: 'Test'
         run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 54cf0b1076..b27dfc075a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        java: [13, 17, 18]
+        java: [17, 21]
     name: 'Linux JDK ${{ matrix.java }}'
     runs-on: ubuntu-latest
     steps:
diff --git a/parent/pom.xml b/parent/pom.xml
index 1b2c27741d..e3f0d89627 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -25,7 +25,7 @@
         3.4.1
         3.2.2
         3.1.0
-        5.3.18
+        5.3.31
         1.6.0
         8.36.1
         5.10.1
@@ -576,7 +576,7 @@
                 
                     org.jacoco
                     jacoco-maven-plugin
-                    0.8.8
+                    0.8.11
                 
                 
                     org.jvnet.jaxb2.maven2
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
index 1ab73616d5..8574c7a6be 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
@@ -14,7 +14,6 @@
 import org.joda.time.LocalDateTime;
 import org.joda.time.LocalTime;
 import org.junit.jupiter.api.condition.EnabledForJreRange;
-import org.junit.jupiter.api.condition.EnabledOnJre;
 import org.junit.jupiter.api.condition.JRE;
 import org.junitpioneer.jupiter.DefaultLocale;
 import org.mapstruct.ap.testutil.IssueKey;
@@ -72,7 +71,7 @@ public void testLocalTimeToString() {
     }
 
     @ProcessorTest
-    @EnabledOnJre(JRE.JAVA_8)
+    @EnabledForJreRange(min = JRE.JAVA_21)
     // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+
     public void testSourceToTargetMappingForStrings() {
         Source src = new Source();
@@ -93,14 +92,14 @@ public void testSourceToTargetMappingForStrings() {
         // and now with default mappings
         target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src );
         assertThat( target ).isNotNull();
-        assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 00:00:00 UTC" );
-        assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 00:00:00" );
+        assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00 UTC" );
+        assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00" );
         assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" );
         assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" );
     }
 
     @ProcessorTest
-    @EnabledForJreRange(min = JRE.JAVA_11)
+    @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17)
     // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+
     public void testSourceToTargetMappingForStringsJdk11() {
         Source src = new Source();

From 7e6fee8714396cb79d81aaecae5f5cd69e178d09 Mon Sep 17 00:00:00 2001
From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com>
Date: Wed, 27 Dec 2023 13:40:04 +0100
Subject: [PATCH 011/214] #1064 Provide a switch to turn off CheckStyle on
 generated test sources

---
 .../mapstruct/ap/testutil/runner/CompilingExtension.java    | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

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 7c47d2a2e8..95874e4b3d 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
@@ -182,11 +182,15 @@ private void assertResult(CompilationOutcomeDescriptor actualResult, ExtensionCo
         assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() );
         assertNotes( actualResult.getNotes(), expectedResult.getNotes() );
 
-        if ( !findAnnotation( testClass, DisableCheckstyle.class ).isPresent() ) {
+        if ( !findAnnotation( testClass, DisableCheckstyle.class ).isPresent() && !skipCheckstyleBySystemProperty() ) {
             assertCheckstyleRules();
         }
     }
 
+    private static boolean skipCheckstyleBySystemProperty() {
+        return Boolean.parseBoolean( System.getProperty( "checkstyle.skip" ) );
+    }
+
     private void assertCheckstyleRules() throws Exception {
         if ( sourceOutputDir != null ) {
             Properties properties = new Properties();

From 6cb126cd7cd03aaf8254b3f7f12a29cb6d2ee1b0 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov 
Date: Sun, 31 Dec 2023 09:34:34 +0100
Subject: [PATCH 012/214] #3462 Stream getters should not be treated as
 alternative setter

---
 .../ap/internal/model/common/Type.java        | 16 ++++--
 .../ap/test/bugs/_3462/Issue3462Mapper.java   | 56 +++++++++++++++++++
 .../ap/test/bugs/_3462/Issue3462Test.java     | 34 +++++++++++
 3 files changed, 100 insertions(+), 6 deletions(-)
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java

diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
index f742e1ba0f..54305cffc1 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
@@ -821,12 +821,6 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER ) {
                     candidate = adderMethod;
                 }
 
-                if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE
-                    && candidate.getAccessorType() == AccessorType.GETTER ) {
-                    // If the collection mapping strategy is target immutable
-                    // then the getter method cannot be used as a setter
-                    continue;
-                }
             }
             else if ( candidate.getAccessorType() == AccessorType.FIELD  && ( Executables.isFinal( candidate ) ||
                 result.containsKey( targetPropertyName ) ) ) {
@@ -834,6 +828,16 @@ else if ( candidate.getAccessorType() == AccessorType.FIELD  && ( Executables.is
                 continue;
             }
 
+            if ( candidate.getAccessorType() == AccessorType.GETTER ) {
+                // When the candidate is a getter then it can't be used in the following cases:
+                // 1. The collection mapping strategy is target immutable
+                // 2. The target type is a stream (streams are immutable)
+                if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE ||
+                    targetType != null && targetType.isStreamType() ) {
+                    continue;
+                }
+            }
+
             Accessor previousCandidate = result.get( targetPropertyName );
             if ( previousCandidate == null || preferredType == null || ( targetType != null
                 && typeUtils.isAssignable( preferredType.getTypeMirror(), targetType.getTypeMirror() ) ) ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
new file mode 100644
index 0000000000..1bf87672c6
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3462;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3462Mapper {
+
+    Issue3462Mapper INSTANCE = Mappers.getMapper( Issue3462Mapper.class );
+
+    Target map(Source source);
+
+    class Source {
+        private final List values;
+
+        public Source(List values) {
+            this.values = values;
+        }
+
+        public List getValues() {
+            return values;
+        }
+
+        public Stream getValuesStream() {
+            return values != null ? values.stream() : Stream.empty();
+        }
+    }
+
+    class Target {
+        private List values;
+
+        public List getValues() {
+            return values;
+        }
+
+        public void setValues(List values) {
+            this.values = values;
+        }
+
+        public Stream getValuesStream() {
+            return values != null ? values.stream() : Stream.empty();
+        }
+    }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java
new file mode 100644
index 0000000000..16be4f441e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3462;
+
+import java.util.Arrays;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("3462")
+@WithClasses(Issue3462Mapper.class)
+class Issue3462Test {
+
+    @ProcessorTest
+    void shouldNotTreatStreamGettersAsAlternativeSetter() {
+
+        Issue3462Mapper.Source source = new Issue3462Mapper.Source( Arrays.asList( "first", "second" ) );
+        Issue3462Mapper.Target target = Issue3462Mapper.INSTANCE.map( source );
+
+        assertThat( target ).isNotNull();
+        assertThat( target.getValues() ).containsExactly( "first", "second" );
+        assertThat( target.getValuesStream() ).containsExactly( "first", "second" );
+
+    }
+}

From 60f162ca881076ac0540822026f0bb1c0a65b36e Mon Sep 17 00:00:00 2001
From: Filip Hrisafov 
Date: Sun, 28 Jan 2024 17:47:39 +0100
Subject: [PATCH 013/214] #3463 DefaultBuilderProvider should be able to handle
 methods in parent interfaces

---
 .../ap/spi/DefaultBuilderProvider.java        | 131 ++++++++++++++----
 .../ap/test/bugs/_3463/EntityBuilder.java     |  14 ++
 .../ap/test/bugs/_3463/Issue3463Mapper.java   |  21 +++
 .../ap/test/bugs/_3463/Issue3463Test.java     |  33 +++++
 .../mapstruct/ap/test/bugs/_3463/Person.java  |  50 +++++++
 .../ap/test/bugs/_3463/PersonDto.java         |  21 +++
 6 files changed, 242 insertions(+), 28 deletions(-)
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java
 create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java

diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
index f89e99cd17..6395364a35 100644
--- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
+++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
@@ -14,6 +14,7 @@
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
@@ -107,19 +108,17 @@ public BuilderInfo findBuilderInfo(TypeMirror type) {
      * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
      */
     protected TypeElement getTypeElement(TypeMirror type) {
-        if ( type.getKind() == TypeKind.ERROR ) {
-            throw new TypeHierarchyErroneousException( type );
-        }
-        DeclaredType declaredType = type.accept(
-            new SimpleTypeVisitor6() {
-                @Override
-                public DeclaredType visitDeclared(DeclaredType t, Void p) {
-                    return t;
-                }
-            },
-            null
-        );
+        DeclaredType declaredType = getDeclaredType( type );
+        return getTypeElement( declaredType );
+    }
 
+    /**
+     * Find the {@link TypeElement} for the given {@link DeclaredType}.
+     *
+     * @param declaredType for which the {@link TypeElement} needs to be found.
+     * @return the type element or {@code null} if the declared type element is not {@link TypeElement}
+     */
+    private TypeElement getTypeElement(DeclaredType declaredType) {
         if ( declaredType == null ) {
             return null;
         }
@@ -135,6 +134,28 @@ public TypeElement visitType(TypeElement e, Void p) {
         );
     }
 
+    /**
+     * Find the {@link DeclaredType} for the given {@link TypeMirror}.
+     *
+     * @param type for which the {@link DeclaredType} needs to be found.
+     * @return the declared or {@code null} if the {@link TypeMirror} is not a {@link DeclaredType}
+     * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
+     */
+    private DeclaredType getDeclaredType(TypeMirror type) {
+        if ( type.getKind() == TypeKind.ERROR ) {
+            throw new TypeHierarchyErroneousException( type );
+        }
+        return type.accept(
+            new SimpleTypeVisitor6() {
+                @Override
+                public DeclaredType visitDeclared(DeclaredType t, Void p) {
+                    return t;
+                }
+            },
+            null
+        );
+    }
+
     /**
      * Find the {@link BuilderInfo} for the given {@code typeElement}.
      * 

@@ -218,21 +239,32 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type * Searches for a build method for {@code typeElement} within the {@code builderElement}. *

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

* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * {@code builderElement} should be ignored, i.e. not checked for build elements. *

- * If there are multiple methods that satisfy - * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} and one of those methods - * is names {@code build} that that method would be considered as a build method. * @param builderElement the element for the builder * @param typeElement the element for the type that is being built * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not * {@code build} */ protected Collection findBuildMethods(TypeElement builderElement, TypeElement typeElement) { + if ( shouldIgnore( builderElement ) || typeElement == null ) { + return Collections.emptyList(); + } + DeclaredType builderType = getDeclaredType( builderElement.asType() ); + + if ( builderType == null ) { + return Collections.emptyList(); + } + + return findBuildMethods( builderElement, builderType, typeElement ); + } + + private Collection findBuildMethods(TypeElement builderElement, DeclaredType builderType, + TypeElement typeElement) { if ( shouldIgnore( builderElement ) ) { return Collections.emptyList(); } @@ -240,23 +272,57 @@ protected Collection findBuildMethods(TypeElement builderElem List builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); List buildMethods = new ArrayList<>(); for ( ExecutableElement buildMethod : builderMethods ) { - if ( isBuildMethod( buildMethod, typeElement ) ) { + if ( isBuildMethod( buildMethod, builderType, typeElement ) ) { buildMethods.add( buildMethod ); } } - if ( buildMethods.isEmpty() ) { - return findBuildMethods( - getTypeElement( builderElement.getSuperclass() ), + if ( !buildMethods.isEmpty() ) { + return buildMethods; + } + + Collection parentClassBuildMethods = findBuildMethods( + getTypeElement( builderElement.getSuperclass() ), + builderType, + typeElement + ); + + if ( !parentClassBuildMethods.isEmpty() ) { + return parentClassBuildMethods; + } + + List interfaces = builderElement.getInterfaces(); + if ( interfaces.isEmpty() ) { + return Collections.emptyList(); + } + + Collection interfaceBuildMethods = new ArrayList<>(); + + for ( TypeMirror builderInterface : interfaces ) { + interfaceBuildMethods.addAll( findBuildMethods( + getTypeElement( builderInterface ), + builderType, typeElement - ); + ) ); } - return buildMethods; + return interfaceBuildMethods; + } + + /** + * @see #isBuildMethod(ExecutableElement, DeclaredType, TypeElement) + * @deprecated use {@link #isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} instead + */ + @Deprecated + protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) { + return buildMethod.getParameters().isEmpty() && + buildMethod.getModifiers().contains( Modifier.PUBLIC ) + && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); } /** - * Checks if the {@code buildMethod} is a method that creates {@code typeElement}. + * Checks if the {@code buildMethod} is a method that creates the {@code typeElement} + * as a member of the {@code builderType}. *

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

    @@ -266,14 +332,23 @@ protected Collection findBuildMethods(TypeElement builderElem *
* * @param buildMethod the method that should be checked + * @param builderType the type of the builder in which the {@code buildMethod} is located in * @param typeElement the type element that needs to be built * @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false} * otherwise */ - protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) { - return buildMethod.getParameters().isEmpty() && - buildMethod.getModifiers().contains( Modifier.PUBLIC ) - && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); + protected boolean isBuildMethod(ExecutableElement buildMethod, DeclaredType builderType, TypeElement typeElement) { + if ( !buildMethod.getParameters().isEmpty() ) { + return false; + } + if ( !buildMethod.getModifiers().contains( Modifier.PUBLIC ) ) { + return false; + } + TypeMirror buildMethodType = typeUtils.asMemberOf( builderType, buildMethod ); + if ( buildMethodType instanceof ExecutableType ) { + return typeUtils.isAssignable( ( (ExecutableType) buildMethodType ).getReturnType(), typeElement.asType() ); + } + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java new file mode 100644 index 0000000000..6b0b71993e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public interface EntityBuilder { + + T build(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java new file mode 100644 index 0000000000..37ead3dfdf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3463; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3463Mapper { + + Issue3463Mapper INSTANCE = Mappers.getMapper( Issue3463Mapper.class ); + + Person map(PersonDto dto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java new file mode 100644 index 0000000000..e6cfc3ece7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3463; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3463") +@WithClasses({ + EntityBuilder.class, + Issue3463Mapper.class, + Person.class, + PersonDto.class +}) +class Issue3463Test { + + @ProcessorTest + void shouldUseInterfaceBuildMethod() { + Person person = Issue3463Mapper.INSTANCE.map( new PersonDto( "Tester" ) ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java new file mode 100644 index 0000000000..977d619474 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private final String name; + + private Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder extends EntityBuilder { + + Builder name(String name); + } + + private static final class BuilderImpl implements Builder { + + private String name; + + private BuilderImpl() { + } + + @Override + public Builder name(String name) { + this.name = name; + return this; + } + + @Override + public Person build() { + return new Person( this.name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java new file mode 100644 index 0000000000..447e4813cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + private final String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From 632213802817629c413bb83c9aebfc9bf25ee245 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:50:33 +0100 Subject: [PATCH 014/214] Add missing generic diamond operator to MappingOptions (#3498) --- .../org/mapstruct/ap/internal/model/source/MappingOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index dd5b2f17b9..f60eba1931 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -121,7 +121,7 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, String defaultValue = mapping.defaultValue().getValue(); Set dependsOn = mapping.dependsOn().hasValue() ? - new LinkedHashSet( mapping.dependsOn().getValue() ) : + new LinkedHashSet<>( mapping.dependsOn().getValue() ) : Collections.emptySet(); FormattingParameters formattingParam = new FormattingParameters( From 6830258f77096c12b826ab15851d0dc17cd9a9b4 Mon Sep 17 00:00:00 2001 From: Chanyut Yuvacharuskul Date: Sun, 28 Jan 2024 23:51:57 +0700 Subject: [PATCH 015/214] Fix typo in chapter-10-advanced-mapping-options.asciidoc (#3500) --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 87dffce31c..9d6c93f211 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -281,7 +281,7 @@ First calling a mapping method on the source property is not protected by a null The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean. -The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MaperConfig#nullValueCheckStrategy`. +The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MapperConfig#nullValueCheckStrategy`. [[source-presence-check]] === Source presence checking From 90a3ce0b464e96a82f6c7d81aaba1fd36fca7b8e Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:06:42 +0100 Subject: [PATCH 016/214] Use primitive types in NativeTypes (#3501) --- .../java/org/mapstruct/ap/internal/util/NativeTypes.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 5498d4c698..729cb180ef 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -270,11 +270,11 @@ public void validate(String s) { @Override void parse(String val, int radix) { - Double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); + double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( d ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( d.isInfinite() ) { + if ( Double.isInfinite( d ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } @@ -297,11 +297,11 @@ public void validate(String s) { NumberRepresentation br = new NumberRepresentation( s, false, false, true ) { @Override void parse(String val, int radix) { - Float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); + float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( f ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( f.isInfinite() ) { + if ( Float.isInfinite( f ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } From 0a43bc088f2f0bd934438d5ec8ca58b72e09998a Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:07:57 +0100 Subject: [PATCH 017/214] Add missing generic type to Javadoc Builder (#3499) --- .../src/main/java/org/mapstruct/ap/internal/model/Javadoc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java index a1efc8c022..1afb5258a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java @@ -31,7 +31,7 @@ public Builder value(String value) { return this; } - public Builder authors(List authors) { + public Builder authors(List authors) { this.authors = authors; return this; } From 8191c850e0c210a5215512fe9ff67380125e7120 Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sun, 11 Feb 2024 10:42:23 +0100 Subject: [PATCH 018/214] #3323 Support access to the source property name --- .../main/java/org/mapstruct/Condition.java | 1 + .../org/mapstruct/SourcePropertyName.java | 26 ++ .../org/mapstruct/TargetPropertyName.java | 6 +- ...apter-10-advanced-mapping-options.asciidoc | 22 +- .../ap/internal/gem/GemGenerator.java | 2 + .../ap/internal/model/BeanMappingMethod.java | 1 + .../ap/internal/model/PropertyMapping.java | 17 +- .../ap/internal/model/common/Parameter.java | 21 +- .../model/common/ParameterBinding.java | 33 +- .../internal/model/source/SourceMethod.java | 9 +- .../source/selector/SelectionContext.java | 18 + .../model/source/selector/TypeSelector.java | 1 + .../processor/MethodRetrievalProcessor.java | 11 + .../mapstruct/ap/internal/util/Message.java | 1 + .../ap/internal/model/MethodReference.ftl | 7 +- .../model/MethodReferencePresenceCheck.ftl | 3 +- .../ap/internal/model/PropertyMapping.ftl | 1 + .../ap/internal/model/TypeConversion.ftl | 1 + .../ap/internal/model/macro/CommonMacros.ftl | 2 + .../Address.java | 2 +- .../AddressDto.java | 2 +- .../DomainModel.java | 2 +- .../Employee.java | 2 +- .../EmployeeDto.java | 22 +- ...ollectionMapperWithSourcePropertyName.java | 48 +++ ...onalMethodInMapperWithAllExceptTarget.java | 54 +++ ...nditionalMethodInMapperWithAllOptions.java | 64 ++++ ...lMethodInMapperWithSourcePropertyName.java | 39 +++ ...hodInUsesMapperWithSourcePropertyName.java | 43 +++ ...WithSourcePropertyNameInContextMapper.java | 108 ++++++ ...sNonStringSourcePropertyNameParameter.java | 26 ++ .../SourcePropertyNameTest.java | 312 ++++++++++++++++++ ...ollectionMapperWithTargetPropertyName.java | 11 +- ...onalMethodInMapperWithAllExceptTarget.java | 14 +- ...nditionalMethodInMapperWithAllOptions.java | 26 +- ...lMethodInMapperWithTargetPropertyName.java | 7 +- ...hodInUsesMapperWithTargetPropertyName.java | 7 +- ...WithTargetPropertyNameInContextMapper.java | 33 +- ...sNonStringTargetPropertyNameParameter.java | 7 +- .../TargetPropertyNameTest.java | 67 ++-- 40 files changed, 979 insertions(+), 100 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/SourcePropertyName.java rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/Address.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/AddressDto.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/DomainModel.java (80%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/Employee.java (96%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/EmployeeDto.java (75%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java (76%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java (76%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java (61%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java (74%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java (59%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/TargetPropertyNameTest.java (88%) diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 148ec4879d..37f1553be1 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -24,6 +24,7 @@ *
  • The mapping source parameter
  • *
  • {@code @}{@link Context} parameter
  • *
  • {@code @}{@link TargetPropertyName} parameter
  • + *
  • {@code @}{@link SourcePropertyName} parameter
  • * * * Note: The usage of this annotation is mandatory diff --git a/core/src/main/java/org/mapstruct/SourcePropertyName.java b/core/src/main/java/org/mapstruct/SourcePropertyName.java new file mode 100644 index 0000000000..a9d036d5d8 --- /dev/null +++ b/core/src/main/java/org/mapstruct/SourcePropertyName.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a presence check method parameter as a source property name parameter. + *

    + * This parameter enables conditional filtering based on source property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + *

    + * + * @author Oliver Erhart + * @since 1.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface SourcePropertyName { +} diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java index 17af966fa8..c7ab8d957d 100644 --- a/core/src/main/java/org/mapstruct/TargetPropertyName.java +++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java @@ -11,10 +11,10 @@ import java.lang.annotation.Target; /** - * This annotation marks a presence check method parameter as a property name parameter. + * This annotation marks a presence check method parameter as a target property name parameter. *

    - * This parameter enables conditional filtering based on target property name at run-time. - * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + * This parameter enables conditional filtering based on target property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. *

    * @author Nikola Ivačič * @since 1.6 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 9d6c93f211..db13c0548b 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -406,9 +406,9 @@ public class CarMapperImpl implements CarMapper { ---- ==== -Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method: +Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method: -.Mapper using custom condition check method with `@TargetPropertyName` +.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName` ==== [source, java, linenums] [subs="verbatim,attributes"] @@ -416,11 +416,19 @@ Additionally `@TargetPropertyName` of type `java.lang.String` can be used in cus @Mapper public interface CarMapper { + @Mapping(target = "owner", source = "ownerName") CarDto carToCarDto(Car car, @MappingTarget CarDto carDto); @Condition - default boolean isNotEmpty(String value, @TargetPropertyName String name) { - if ( name.equals( "owner" ) { + default boolean isNotEmpty( + String value, + @TargetPropertyName String targetPropertyName, + @SourcePropertyName String sourcePropertyName + ) { + + if ( targetPropertyName.equals( "owner" ) + && sourcePropertyName.equals( "ownerName" ) ) { + return value != null && !value.isEmpty() && !value.equals( value.toLowerCase() ); @@ -431,7 +439,7 @@ public interface CarMapper { ---- ==== -The generated mapper with `@TargetPropertyName` will look like: +The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like: .Custom condition check in generated implementation ==== @@ -447,7 +455,7 @@ public class CarMapperImpl implements CarMapper { return carDto; } - if ( isNotEmpty( car.getOwner(), "owner" ) ) { + if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) { carDto.setOwner( car.getOwner() ); } else { carDto.setOwner( null ); @@ -470,7 +478,7 @@ If there is a custom `@Condition` method applicable for the property it will hav ==== Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input. -`@TargetPropertyName` parameter can only be used in `@Condition` methods. +`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods. ==== <> is also valid for `@Condition` methods. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 9ac13184cd..2ed9bd9a09 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -31,6 +31,7 @@ import org.mapstruct.Named; import org.mapstruct.ObjectFactory; import org.mapstruct.Qualifier; +import org.mapstruct.SourcePropertyName; import org.mapstruct.SubclassMapping; import org.mapstruct.SubclassMappings; import org.mapstruct.TargetPropertyName; @@ -57,6 +58,7 @@ @GemDefinition(BeanMapping.class) @GemDefinition(EnumMapping.class) @GemDefinition(MapMapping.class) +@GemDefinition(SourcePropertyName.class) @GemDefinition(SubclassMapping.class) @GemDefinition(SubclassMappings.class) @GemDefinition(TargetType.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index ee347ef35e..be7203507b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1522,6 +1522,7 @@ private void applyPropertyNameBasedMapping(List sourceReference MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) + .sourcePropertyName( targetPropertyName ) .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 50f23ecbff..2f30957962 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -70,6 +70,7 @@ public class PropertyMapping extends ModelElement { private final String name; + private final String sourcePropertyName; private final String sourceBeanName; private final String targetWriteAccessorName; private final ReadAccessor targetReadAccessorProvider; @@ -286,6 +287,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get } return new PropertyMapping( + sourcePropertyName, targetPropertyName, rightHandSide.getSourceParameterName(), targetWriteAccessor.getSimpleName(), @@ -1099,16 +1101,17 @@ private PropertyMapping(String name, String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, Assignment propertyAssignment, Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { - this( name, null, targetWriteAccessorName, targetReadAccessorProvider, + this( name, null, null, targetWriteAccessorName, targetReadAccessorProvider, targetType, propertyAssignment, dependsOn, defaultValueAssignment, constructorMapping ); } - private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - ReadAccessor targetReadAccessorProvider, Type targetType, - Assignment assignment, - Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName, + String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, + Assignment assignment, + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this.sourcePropertyName = sourcePropertyName; this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; @@ -1128,6 +1131,10 @@ public String getName() { return name; } + public String getSourcePropertyName() { + return sourcePropertyName; + } + public String getSourceBeanName() { return sourceBeanName; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 600c8ac337..44ac0eb7f0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -14,8 +14,9 @@ import org.mapstruct.ap.internal.gem.ContextGem; import org.mapstruct.ap.internal.gem.MappingTargetGem; -import org.mapstruct.ap.internal.gem.TargetTypeGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; +import org.mapstruct.ap.internal.gem.TargetTypeGem; import org.mapstruct.ap.internal.util.Collections; /** @@ -32,6 +33,7 @@ public class Parameter extends ModelElement { private final boolean mappingTarget; private final boolean targetType; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final boolean varArgs; @@ -44,12 +46,13 @@ private Parameter(Element element, Type type, boolean varArgs) { this.mappingTarget = MappingTargetGem.instanceOn( element ) != null; this.targetType = TargetTypeGem.instanceOn( element ) != null; this.mappingContext = ContextGem.instanceOn( element ) != null; + this.sourcePropertyName = SourcePropertyNameGem.instanceOn( element ) != null; this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null; this.varArgs = varArgs; } private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, - boolean targetPropertyName, + boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; @@ -58,12 +61,13 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT this.mappingTarget = mappingTarget; this.targetType = targetType; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false ); + this( name, type, false, false, false, false, false, false ); } public Element getElement() { @@ -99,6 +103,7 @@ private String format() { return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + ( mappingContext ? "@Context " : "" ) + + ( sourcePropertyName ? "@SourcePropertyName " : "" ) + ( targetPropertyName ? "@TargetPropertyName " : "" ) + "%s " + name; } @@ -120,6 +125,10 @@ public boolean isTargetPropertyName() { return targetPropertyName; } + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + public boolean isVarArgs() { return varArgs; } @@ -165,6 +174,7 @@ public static Parameter forForgedMappingTarget(Type parameterType) { false, false, false, + false, false ); } @@ -206,6 +216,10 @@ public static Parameter getTargetTypeParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); } + public static Parameter getSourcePropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isSourcePropertyName ).findAny().orElse( null ); + } + public static Parameter getTargetPropertyNameParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } @@ -214,6 +228,7 @@ private static boolean isSourceParameter( Parameter parameter ) { return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext() && + !parameter.isSourcePropertyName() && !parameter.isTargetPropertyName(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java index 18274e5492..0791ee626a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -22,16 +22,19 @@ public class ParameterBinding { private final boolean targetType; private final boolean mappingTarget; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final SourceRHS sourceRHS; private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) { + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, + SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; this.targetType = targetType; this.mappingTarget = mappingTarget; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.sourceRHS = sourceRHS; } @@ -64,11 +67,18 @@ public boolean isMappingContext() { return mappingContext; } + /** + * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. + */ + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + /** * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. */ public boolean isTargetPropertyName() { - return targetPropertyName; + return targetPropertyName; } /** @@ -108,6 +118,7 @@ public static ParameterBinding fromParameter(Parameter parameter) { parameter.isMappingTarget(), parameter.isTargetType(), parameter.isMappingContext(), + parameter.isSourcePropertyName(), parameter.isTargetPropertyName(), null ); @@ -129,6 +140,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame false, false, false, + false, null ); } @@ -138,14 +150,21 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, false, null ); + return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null ); } /** * @return a parameter binding representing a target property name parameter */ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, true, null ); + return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null ); + } + + /** + * @return a parameter binding representing a source property name parameter + */ + public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null ); } /** @@ -153,7 +172,7 @@ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, false, null ); + return new ParameterBinding( resultType, null, true, false, false, false, false, null ); } /** @@ -161,10 +180,10 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, false, null ); + return new ParameterBinding( sourceType, null, false, false, false, false, false, null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS ); + return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 37b57d9025..42c318ca49 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -47,6 +47,7 @@ public class SourceMethod implements Method { private final List parameters; private final Parameter mappingTargetParameter; private final Parameter targetTypeParameter; + private final Parameter sourcePropertyNameParameter; private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; private final boolean isPresenceCheck; @@ -249,6 +250,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); + this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters ); this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); @@ -265,9 +267,10 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) private boolean determineIfIsObjectFactory() { boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null; boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; return !isLifecycleCallbackMethod() && !returnType.isVoid() - && hasNoMappingTargetParam && hasNoTargetPropertyNameParam + && hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } @@ -382,6 +385,10 @@ public Parameter getTargetTypeParameter() { return targetTypeParameter; } + public Parameter getSourcePropertyNameParameter() { + return sourcePropertyNameParameter; + } + public Parameter getTargetPropertyNameParameter() { return targetPropertyNameParameter; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 9e82dae25d..800278a4c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -171,6 +171,7 @@ private static List getAvailableParameterBindingsFromMethod(Me if ( sourceRHS != null ) { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); + addSourcePropertyNameBindings( availableParams, sourceRHS.getSourceType(), typeFactory ); } else { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); @@ -189,6 +190,7 @@ private static List getAvailableParameterBindingsFromSourceTyp List availableParams = new ArrayList<>(); availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + addSourcePropertyNameBindings( availableParams, sourceType, typeFactory ); for ( Parameter param : mappingMethod.getParameters() ) { if ( param.isMappingContext() ) { @@ -201,6 +203,22 @@ private static List getAvailableParameterBindingsFromSourceTyp return availableParams; } + private static void addSourcePropertyNameBindings(List availableParams, Type sourceType, + TypeFactory typeFactory) { + + boolean sourcePropertyNameAvailable = false; + for ( ParameterBinding pb : availableParams ) { + if ( pb.isSourcePropertyName() ) { + sourcePropertyNameAvailable = true; + break; + } + } + if ( !sourcePropertyNameAvailable ) { + availableParams.add( ParameterBinding.forSourcePropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + + } + /** * Adds default parameter bindings for the mapping-target and target-type if not already available. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index 24275f594b..50f834d43e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -215,6 +215,7 @@ private static List findCandidateBindingsForParameter(List parameters, Type returnType) { for ( Parameter param : parameters ) { + + if ( param.isSourcePropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + SourcePropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + if ( param.isTargetPropertyName() && !param.getType().isString() ) { messager.printMessage( param.getElement(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 4b43315390..d8f27791ee 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -177,6 +177,7 @@ public enum Message { RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), + RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ), RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl index 4b45643dc8..63f983df46 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl @@ -44,6 +44,8 @@ <#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> <#elseif param.mappingContext> ${param.variableName}<#t> + <#elseif param.sourcePropertyName> + "${ext.sourcePropertyName}"<#t> <#elseif param.targetPropertyName> "${ext.targetPropertyName}"<#t> <#elseif param.sourceRHS??> @@ -60,7 +62,7 @@ <#-- macro: assignment - purpose: note: takes its targetyType from the singleSourceParameterType + purpose: note: takes its targetType from the singleSourceParameterType --> <#macro _assignment assignmentToUse> <@includeModel object=assignmentToUse @@ -69,6 +71,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=singleSourceParameterType/> - \ No newline at end of file + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 0452e1699a..68b05d84ed 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -8,5 +8,6 @@ <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> <#if isNegate()>!<@includeModel object=methodReference presenceCheck=true + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName - targetType=ext.targetType/> \ No newline at end of file + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index e6aef4cecd..f45659cb5d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -11,6 +11,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + sourcePropertyName=sourcePropertyName targetPropertyName=name targetType=targetType defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl index 4a9d356ce4..a5e5798d1c 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl @@ -14,6 +14,7 @@ ${openExpression}<@_assignment/>${closeExpression} existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index bce28ebe19..278b441aa9 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -17,6 +17,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName targetType=ext.targetType/> ) { <#nested> } @@ -61,6 +62,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetType=ext.targetType + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName /> ) { <#if needs_explicit_local_var> <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java index 162ed118bb..339af75944 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java index f4cbc71915..c6a63065d4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java similarity index 80% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java index 4d2c716a96..8e5fa8695d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * Target Property Name test entities diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java similarity index 96% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java index 59ee3426e1..497717a592 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java similarity index 75% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java index 5d81a9334b..f49f25e4a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; @@ -15,14 +15,14 @@ public class EmployeeDto implements DomainModel { private String firstName; private String lastName; private String title; - private String country; + private String originCountry; private boolean active; private int age; private EmployeeDto boss; private AddressDto primaryAddress; - private List addresses; + private List originAddresses; public String getFirstName() { return firstName; @@ -48,12 +48,12 @@ public void setTitle(String title) { this.title = title; } - public String getCountry() { - return country; + public String getOriginCountry() { + return originCountry; } - public void setCountry(String country) { - this.country = country; + public void setOriginCountry(String originCountry) { + this.originCountry = originCountry; } public boolean isActive() { @@ -88,11 +88,11 @@ public void setPrimaryAddress(AddressDto primaryAddress) { this.primaryAddress = primaryAddress; } - public List getAddresses() { - return addresses; + public List getOriginAddresses() { + return originAddresses; } - public void setAddresses(List addresses) { - this.addresses = addresses; + public void setOriginAddresses(List originAddresses) { + this.originAddresses = originAddresses; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..944fe5d146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collection; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithSourcePropertyName { + + ConditionalMethodForCollectionMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @SourcePropertyName String propName) { + if ( "originAddresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 0000000000..ea6a77d94e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @SourcePropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 0000000000..ee3be57648 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, + @Context PresenceUtils utils) { + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( sourcePropName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..41ff6c5264 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithSourcePropertyName { + + ConditionalMethodInMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..8ba222512f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithSourcePropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithSourcePropertyName { + + ConditionalMethodInUsesMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java new file mode 100644 index 0000000000..64b6fcbbab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java @@ -0,0 +1,108 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodWithSourcePropertyNameInContextMapper { + + ConditionalMethodWithSourcePropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithSourcePropertyNameInContextMapper.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visited.add( propName ); + return true; + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java new file mode 100644 index 0000000000..5ed118676d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; + +@Mapper +public interface ErroneousNonStringSourcePropertyNameParameter { + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java new file mode 100644 index 0000000000..2471ed647d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java @@ -0,0 +1,312 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@IssueKey("3323") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class SourcePropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithSourcePropertyName.class + }) + public void conditionalMethodInMapperWithSourcePropertyName() { + ConditionalMethodInMapperWithSourcePropertyName mapper + = ConditionalMethodInMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithSourcePropertyName.class + }) + public void conditionalMethodForCollectionMapperWithSourcePropertyName() { + ConditionalMethodForCollectionMapperWithSourcePropertyName mapper + = ConditionalMethodForCollectionMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithSourcePropertyName.class + }) + public void conditionalMethodInUsesMapperWithSourcePropertyName() { + ConditionalMethodInUsesMapperWithSourcePropertyName mapper + = ConditionalMethodInUsesMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourcePropertyNameInContextMapper.class + }) + public void conditionalMethodWithSourcePropertyNameInUsesContextMapper() { + ConditionalMethodWithSourcePropertyNameInContextMapper mapper + = ConditionalMethodWithSourcePropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "originCountry", + "active", + "age", + "boss", + "primaryAddress", + "originAddresses", + "street" + ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "originCountry", + "originAddresses", + "originAddresses.street", + "firstName", + "lastName", + "title", + "active", + "age", + "boss", + "boss.originCountry", + "boss.originAddresses", + "boss.originAddresses.street", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "primaryAddress" + ); + } + + @ProcessorTest + @WithClasses({ + ErroneousNonStringSourcePropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringSourcePropertyNameParameter.class, + line = 23, + message = "@SourcePropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringSourcePropertyNameParameter() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java index 71baa0942b..a51a318eb6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java @@ -3,15 +3,18 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collection; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Collection; - /** * @author Nikola Ivačič */ @@ -21,6 +24,8 @@ public interface ConditionalMethodForCollectionMapperWithTargetPropertyName { ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java index 430303d06d..1d0dd3fa51 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -3,17 +3,21 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -29,6 +33,8 @@ class PresenceUtils { Set visitedSources = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java similarity index 61% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java index a18ba0db73..d3ccb9ba2e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -3,18 +3,23 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -26,11 +31,14 @@ public interface ConditionalMethodInMapperWithAllOptions { = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); class PresenceUtils { - Set visited = new LinkedHashSet<>(); + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); Set visitedSources = new LinkedHashSet<>(); Set visitedTargets = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") void map(EmployeeDto employeeDto, @MappingTarget Employee employee, @Context PresenceUtils utils); @@ -39,12 +47,14 @@ void map(EmployeeDto employeeDto, default boolean isNotBlank(String value, DomainModel source, @MappingTarget DomainModel target, - @TargetPropertyName String propName, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, @Context PresenceUtils utils) { - utils.visited.add( propName ); + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); utils.visitedSources.add( source.getClass().getSimpleName() ); utils.visitedTargets.add( target.getClass().getSimpleName() ); - if ( propName.equalsIgnoreCase( "lastName" ) ) { + if ( targetPropName.equalsIgnoreCase( "lastName" ) ) { return false; } return value != null && !value.trim().isEmpty(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java index 6d27bc9036..d5dc378f32 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInMapperWithTargetPropertyName { ConditionalMethodInMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java index 7c526884e9..381b99d4f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInUsesMapperWithTargetPropertyName { ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); class PresenceUtils { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java index a7cde220c9..44bc262435 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java @@ -3,21 +3,29 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; import org.mapstruct.AfterMapping; import org.mapstruct.BeforeMapping; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Deque; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Set; - /** * @author Nikola Ivačič */ @@ -27,6 +35,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); Address map(AddressDto addressDto, @Context PresenceUtils utils); @@ -41,6 +51,8 @@ public boolean isNotBlank(String value, @TargetPropertyName String propName) { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); @@ -55,6 +67,8 @@ public boolean collect(@TargetPropertyName String propName) { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); @@ -68,8 +82,11 @@ default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource } @AfterMapping - default void after(@Context PresenceUtilsAllPropsWithSource utils) { - utils.path.pollLast(); + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } } class PresenceUtilsAllPropsWithSource { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java similarity index 59% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java index ec545a058b..d56277abf4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java @@ -3,15 +3,20 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; @Mapper public interface ErroneousNonStringTargetPropertyNameParameter { + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java index 91e4b77de2..bb90c0b069 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java @@ -3,9 +3,16 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -14,8 +21,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.GeneratedSource; -import java.util.Collections; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -46,8 +51,8 @@ public void conditionalMethodInMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -71,8 +76,8 @@ public void conditionalMethodForCollectionMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -94,8 +99,8 @@ public void conditionalMethodInUsesMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -122,8 +127,8 @@ public void conditionalMethodInMapperWithAllOptions() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -135,7 +140,9 @@ public void conditionalMethodInMapperWithAllOptions() { assertThat( employee.getAddresses() ) .extracting( Address::getStreet ) .containsExactly( "Testing St. 6" ); - assertThat( utils.visited ) + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); assertThat( utils.visitedTargets ).containsExactly( "Employee" ); @@ -155,8 +162,8 @@ public void conditionalMethodInMapperWithAllExceptTarget() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -185,8 +192,8 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setLastName( " " ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -204,8 +211,8 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -234,15 +241,15 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { EmployeeDto bossEmployeeDto = new EmployeeDto(); bossEmployeeDto.setLastName( "Boss Tester" ); - bossEmployeeDto.setCountry( "US" ); - bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto( + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 10" ) ) ); employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); + employeeDto.setOriginCountry( "US" ); employeeDto.setBoss( bossEmployeeDto ); - employeeDto.setAddresses( + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -259,26 +266,26 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { .containsExactly( "Testing St. 10" ); assertThat( allPropsUtilsWithSource.visited ) .containsExactly( + "country", + "addresses", + "addresses.street", "firstName", "lastName", "title", - "country", "active", "age", "boss", + "boss.country", + "boss.addresses", + "boss.addresses.street", "boss.firstName", "boss.lastName", "boss.title", - "boss.country", "boss.active", "boss.age", "boss.boss", "boss.primaryAddress", - "boss.addresses", - "boss.addresses.street", - "primaryAddress", - "addresses", - "addresses.street" + "primaryAddress" ); } @@ -293,7 +300,7 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousNonStringTargetPropertyNameParameter.class, - line = 18, + line = 23, message = "@TargetPropertyName can only by applied to a String parameter." ) } From ca1fd0d85dc76f914167c94f1e034e8dfd55bb61 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 11 Feb 2024 12:51:19 +0100 Subject: [PATCH 019/214] #3331 Do not handle defined mappings if the result type is abstract due to runtime exception subclass exhaustive strategy (#3487) --- .../ap/internal/model/BeanMappingMethod.java | 43 +++++++++++-- .../ap/test/bugs/_3331/Issue3331Mapper.java | 27 ++++++++ .../ap/test/bugs/_3331/Issue3331Test.java | 48 ++++++++++++++ .../mapstruct/ap/test/bugs/_3331/Vehicle.java | 64 +++++++++++++++++++ .../ap/test/bugs/_3331/VehicleDto.java | 51 +++++++++++++++ 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index be7203507b..8979901f80 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -284,10 +284,15 @@ else if ( !method.isUpdateMethod() ) { initializeMappingReferencesIfNeeded( resultTypeToMap ); - // map properties with mapping - boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); - if ( mappingErrorOccurred ) { - return null; + boolean shouldHandledDefinedMappings = shouldHandledDefinedMappings( resultTypeToMap ); + + + if ( shouldHandledDefinedMappings ) { + // map properties with mapping + boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); + if ( mappingErrorOccurred ) { + return null; + } } boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings(); @@ -1045,6 +1050,36 @@ private List getValueAsList(AnnotationValue av) { return (List) av.getValue(); } + /** + * Determine whether defined mappings should be handled on the result type. + * They should be, if any of the following is true: + *
      + *
    • The {@code resultTypeToMap} is not abstract
    • + *
    • There is a factory method
    • + *
    • The method is an update method
    • + *
    + * Otherwise, it means that we have reached this because subclass mappings are being used + * and the chosen strategy is runtime exception. + * + * @param resultTypeToMap the type in which the defined target properties are defined + * @return {@code true} if defined mappings should be handled for the result type, {@code false} otherwise + */ + private boolean shouldHandledDefinedMappings(Type resultTypeToMap) { + if ( !resultTypeToMap.isAbstract() ) { + return true; + } + + if ( hasFactoryMethod ) { + return true; + } + + if ( method.isUpdateMethod() ) { + return true; + } + + return false; + } + /** * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the * inverse mapping method. diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java new file mode 100644 index 0000000000..3011479c65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3331; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface Issue3331Mapper { + + Issue3331Mapper INSTANCE = Mappers.getMapper( Issue3331Mapper.class ); + + @SubclassMapping(source = Vehicle.Car.class, target = VehicleDto.Car.class) + @SubclassMapping(source = Vehicle.Motorbike.class, target = VehicleDto.Motorbike.class) + @Mapping(target = "name", constant = "noname") + VehicleDto map(Vehicle vehicle); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java new file mode 100644 index 0000000000..3871868226 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3331; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3331") +@WithClasses({ + Issue3331Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3331Test { + + @ProcessorTest + void shouldCorrectCompileAndThrowExceptionOnRuntime() { + VehicleDto target = Issue3331Mapper.INSTANCE.map( new Vehicle.Car( "Test car", 4 ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + target = Issue3331Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test bike", true ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Motorbike.class, bike -> { + assertThat( bike.isAllowedForMinor() ).isTrue(); + } ); + + assertThatThrownBy( () -> Issue3331Mapper.INSTANCE.map( new Vehicle.Truck( "Test truck", 3 ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Truck.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java new file mode 100644 index 0000000000..3b68a3acc4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + + protected Vehicle(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + + public static class Truck extends Vehicle { + + private final int numOfAxis; + + public Truck(String name, int numOfAxis) { + super( name ); + this.numOfAxis = numOfAxis; + } + + public int getNumOfAxis() { + return numOfAxis; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java new file mode 100644 index 0000000000..8c2ef13dd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private final String name; + + protected VehicleDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends VehicleDto { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends VehicleDto { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + +} From bb1cd6348502c592dfb77aa5ac86cfe62cc0ba32 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 11 Feb 2024 13:29:34 +0100 Subject: [PATCH 020/214] #2788 Improve unmapped source properties message for forged methods --- .../ap/internal/model/BeanMappingMethod.java | 115 +++---- .../mapstruct/ap/internal/util/Message.java | 2 + .../nestedbeans/DottedErrorMessageTest.java | 285 ++++++++++++++---- ...SourceCollectionElementPropertyMapper.java | 17 ++ .../UnmappableSourceDeepListMapper.java | 17 ++ .../UnmappableSourceDeepMapKeyMapper.java | 17 ++ .../UnmappableSourceDeepMapValueMapper.java | 17 ++ .../UnmappableSourceDeepNestingMapper.java | 17 ++ ...r.java => UnmappableSourceEnumMapper.java} | 2 +- .../UnmappableSourceValuePropertyMapper.java | 17 ++ ...argetCollectionElementPropertyMapper.java} | 4 +- ...va => UnmappableTargetDeepListMapper.java} | 4 +- ... => UnmappableTargetDeepMapKeyMapper.java} | 4 +- ...> UnmappableTargetDeepMapValueMapper.java} | 4 +- ...=> UnmappableTargetDeepNestingMapper.java} | 4 +- ... UnmappableTargetValuePropertyMapper.java} | 4 +- ...eWarnCollectionElementPropertyMapper.java} | 7 +- ...> UnmappableSourceWarnDeepListMapper.java} | 7 +- ...UnmappableSourceWarnDeepMapKeyMapper.java} | 7 +- ...mappableSourceWarnDeepMapValueMapper.java} | 7 +- ...nmappableSourceWarnDeepNestingMapper.java} | 7 +- ...appableSourceWarnValuePropertyMapper.java} | 7 +- ...etWarnCollectionElementPropertyMapper.java | 17 ++ .../UnmappableTargetWarnDeepListMapper.java | 17 ++ .../UnmappableTargetWarnDeepMapKeyMapper.java | 17 ++ ...nmappableTargetWarnDeepMapValueMapper.java | 17 ++ ...UnmappableTargetWarnDeepNestingMapper.java | 17 ++ ...mappableTargetWarnValuePropertyMapper.java | 17 ++ 28 files changed, 546 insertions(+), 128 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceCollectionElementPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapKeyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapValueMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepNestingMapper.java rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableEnumMapper.java => UnmappableSourceEnumMapper.java} (96%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceValuePropertyMapper.java rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableCollectionElementPropertyMapper.java => UnmappableTargetCollectionElementPropertyMapper.java} (68%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepListMapper.java => UnmappableTargetDeepListMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepMapKeyMapper.java => UnmappableTargetDeepMapKeyMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepMapValueMapper.java => UnmappableTargetDeepMapValueMapper.java} (70%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepNestingMapper.java => UnmappableTargetDeepNestingMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableValuePropertyMapper.java => UnmappableTargetValuePropertyMapper.java} (70%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnCollectionElementPropertyMapper.java => UnmappableSourceWarnCollectionElementPropertyMapper.java} (55%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepListMapper.java => UnmappableSourceWarnDeepListMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepMapKeyMapper.java => UnmappableSourceWarnDeepMapKeyMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepMapValueMapper.java => UnmappableSourceWarnDeepMapValueMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepNestingMapper.java => UnmappableSourceWarnDeepNestingMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnValuePropertyMapper.java => UnmappableSourceWarnValuePropertyMapper.java} (56%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnCollectionElementPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapKeyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapValueMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepNestingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnValuePropertyMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 8979901f80..9abed3ec44 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1718,54 +1718,22 @@ private void reportErrorForUnmappedTargetPropertiesIfRequired() { } else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { - if ( !( method instanceof ForgedMethod ) ) { - Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? - Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; - Object[] args = new Object[] { - MessageFormat.format( - "{0,choice,1#property|1 unmappedProperties, + Message unmappedPropertiesMsg, + Message unmappedForgedPropertiesMsg) { + if ( !( method instanceof ForgedMethod ) ) { Object[] args = new Object[] { MessageFormat.format( "{0,choice,1#property|1 Date: Sun, 11 Feb 2024 19:53:38 +0100 Subject: [PATCH 021/214] #3360 Do not report unmapped source and target properties when result type is abstract due to runtime exception subclass exhaustive strategy (#3526) --- .../ap/internal/model/BeanMappingMethod.java | 6 +- .../ap/test/bugs/_3360/Issue3360Mapper.java | 34 ++++++++++++ .../ap/test/bugs/_3360/Issue3360Test.java | 43 +++++++++++++++ .../mapstruct/ap/test/bugs/_3360/Vehicle.java | 55 +++++++++++++++++++ .../ap/test/bugs/_3360/VehicleDto.java | 45 +++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 9abed3ec44..dd3a86e2cf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -319,8 +319,10 @@ else if ( !method.isUpdateMethod() ) { handleUnmappedConstructorProperties(); // report errors on unmapped properties - reportErrorForUnmappedTargetPropertiesIfRequired(); - reportErrorForUnmappedSourcePropertiesIfRequired(); + if ( shouldHandledDefinedMappings ) { + reportErrorForUnmappedTargetPropertiesIfRequired(); + reportErrorForUnmappedSourcePropertiesIfRequired(); + } reportErrorForMissingIgnoredSourceProperties(); reportErrorForUnusedSourceParameters(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java new file mode 100644 index 0000000000..812d885489 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3360; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper( + subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + unmappedTargetPolicy = ReportingPolicy.ERROR, + unmappedSourcePolicy = ReportingPolicy.ERROR +) +public interface Issue3360Mapper { + + Issue3360Mapper INSTANCE = Mappers.getMapper( Issue3360Mapper.class ); + + @SubclassMapping(target = VehicleDto.Car.class, source = Vehicle.Car.class) + VehicleDto map(Vehicle vehicle); + + @Mapping(target = "model", source = "modelName") + @BeanMapping(ignoreUnmappedSourceProperties = "computedName") + VehicleDto.Car map(Vehicle.Car car); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java new file mode 100644 index 0000000000..307c2b265a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3360; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3360") +@WithClasses({ + Issue3360Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3360Test { + + @ProcessorTest + void shouldCompileWithoutErrorsAndWarnings() { + + Vehicle vehicle = new Vehicle.Car( "Test", "car", 4 ); + + VehicleDto target = Issue3360Mapper.INSTANCE.map( vehicle ); + + assertThat( target.getName() ).isEqualTo( "Test" ); + assertThat( target.getModel() ).isEqualTo( "car" ); + assertThat( target ).isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + assertThatThrownBy( () -> Issue3360Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test", "bike" ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Motorbike.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java new file mode 100644 index 0000000000..0cc2011bc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java @@ -0,0 +1,55 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + private final String modelName; + + protected Vehicle(String name, String modelName) { + this.name = name; + this.modelName = modelName; + } + + public String getName() { + return name; + } + + public String getModelName() { + return modelName; + } + + public String getComputedName() { + return null; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, String modelName, int numOfDoors) { + super( name, modelName ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + public Motorbike(String name, String modelName) { + super( name, modelName ); + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java new file mode 100644 index 0000000000..fc93a2fcae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private String name; + private String model; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static class Car extends VehicleDto { + + private int numOfDoors; + + public int getNumOfDoors() { + return numOfDoors; + } + + public void setNumOfDoors(int numOfDoors) { + this.numOfDoors = numOfDoors; + } + } + +} From 2c12e75bfcae80aed0aa2674ccdf248280a6f4dc Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 3 Mar 2024 18:59:32 +0100 Subject: [PATCH 022/214] #3485 Exception for Mapping to target = "." without source --- .../internal/model/source/MappingOptions.java | 3 ++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3485/ErroneousIssue3485Mapper.java | 35 +++++++++++++++++++ .../ap/test/bugs/_3485/Issue3485Test.java | 33 +++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index f60eba1931..e0994d746f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -260,6 +260,9 @@ else if ( gem.nullValuePropertyMappingStrategy().hasValue() else if ( ".".equals( gem.target().get() ) && gem.ignore().hasValue() && gem.ignore().getValue() ) { message = Message.PROPERTYMAPPING_TARGET_THIS_AND_IGNORE; } + else if ( ".".equals( gem.target().get() ) && !gem.source().hasValue() ) { + message = Message.PROPERTYMAPPING_TARGET_THIS_NO_SOURCE; + } if ( message == null ) { return true; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index ab8fa01010..68c079cb96 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -71,6 +71,7 @@ public enum Message { PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_TARGET_THIS_AND_IGNORE( "Using @Mapping( target = \".\", ignore = true ) is not allowed. You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore all non explicitly mapped target properties." ), + PROPERTYMAPPING_TARGET_THIS_NO_SOURCE( "Using @Mapping( target = \".\") requires a source property. Expression or constant cannot be used as a source."), PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED("Expression and a qualifier both defined in @Mapping, either define an expression or a qualifier."), PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java()\"." ), PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java()\"." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java new file mode 100644 index 0000000000..5714e04684 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3485; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author hduelme + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ErroneousIssue3485Mapper { + + ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + class Target { + private final String value; + + public Target( String value ) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + + @Mapping(target = ".") + Target targetFromExpression(String s); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java new file mode 100644 index 0000000000..cf7e0563af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3485; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author hduelme + */ +@IssueKey("3463") +public class Issue3485Test { + + @ProcessorTest + @WithClasses(ErroneousIssue3485Mapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue3485Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 33, + message = "Using @Mapping( target = \".\") requires a source property. Expression or " + + "constant cannot be used as a source.") + }) + void thisMappingWithoutSource() { + } +} From e815e3cb1eabfef2930accf7d5f2c442ff2a456e Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sun, 10 Mar 2024 09:15:37 +0100 Subject: [PATCH 023/214] #3524 Provide tests with Lombok style super builders --- .../ap/test/superbuilder/AbstractVehicle.java | 46 +++++ .../test/superbuilder/AbstractVehicleDto.java | 20 +++ .../mapstruct/ap/test/superbuilder/Car.java | 63 +++++++ .../ap/test/superbuilder/CarDto.java | 21 +++ .../ap/test/superbuilder/CarMapper.java | 43 +++++ .../superbuilder/ChainedAccessorsCar.java | 58 +++++++ .../superbuilder/ChainedAccessorsVehicle.java | 72 ++++++++ .../superbuilder/InheritedAbstractCar.java | 54 ++++++ .../superbuilder/InheritedAbstractCarDto.java | 21 +++ .../ap/test/superbuilder/MuscleCar.java | 52 ++++++ .../ap/test/superbuilder/MuscleCarDto.java | 21 +++ .../ap/test/superbuilder/Passenger.java | 50 ++++++ .../ap/test/superbuilder/PassengerDto.java | 20 +++ .../test/superbuilder/SuperBuilderTest.java | 160 ++++++++++++++++++ .../ap/test/superbuilder/Vehicle.java | 50 ++++++ .../ap/test/superbuilder/VehicleDto.java | 25 +++ 16 files changed, 776 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java new file mode 100644 index 0000000000..d9cf159888 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.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.superbuilder; + +public abstract class AbstractVehicle { + + private final int amountOfTires; + private final Passenger passenger; + + protected AbstractVehicle(AbstractVehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + this.passenger = b.passenger; + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public abstract static class AbstractVehicleBuilder> { + + private int amountOfTires; + private Passenger passenger; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java new file mode 100644 index 0000000000..0b97398ae0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.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.superbuilder; + +public abstract class AbstractVehicleDto { + + private final int amountOfTires; + + public AbstractVehicleDto(int amountOfTires) { + this.amountOfTires = amountOfTires; + } + + public int getAmountOfTires() { + return amountOfTires; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java new file mode 100644 index 0000000000..af15ee6631 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class Car extends Vehicle { + + private final String manufacturer; + private final Passenger passenger; + + protected Car(CarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + this.passenger = b.passenger; + } + + public static CarBuilder builder() { + return new CarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public abstract static class CarBuilder> extends VehicleBuilder { + + private String manufacturer; + private Passenger passenger; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class CarBuilderImpl extends CarBuilder { + private CarBuilderImpl() { + } + + protected CarBuilderImpl self() { + return this; + } + + public Car build() { + return new Car( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java new file mode 100644 index 0000000000..9877e27d71 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.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.superbuilder; + +public class CarDto extends VehicleDto { + + private final String manufacturer; + + public CarDto(int amountOfTires, String manufacturer, PassengerDto passenger) { + super( amountOfTires, passenger ); + this.manufacturer = manufacturer; + } + + public String getManufacturer() { + return manufacturer; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java new file mode 100644 index 0000000000..a3def64ead --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.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.superbuilder; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + @Mapping(target = "amountOfTires", source = "tireCount") + Car carDtoToCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToCar") + CarDto carToCarDto(Car source); + + @Mapping(target = "amountOfTires", source = "tireCount") + ChainedAccessorsCar carDtoToChainedAccessorsCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToChainedAccessorsCar") + CarDto chainedAccessorsCarToCarDto(ChainedAccessorsCar source); + + @Mapping(target = "amountOfTires", source = "tireCount") + InheritedAbstractCar carDtoToInheritedAbstractCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToInheritedAbstractCar") + CarDto inheritedAbstractCarToCarDto(InheritedAbstractCar source); + + @Mapping(target = "amountOfTires", source = "tireCount") + @Mapping(target = "horsePower", constant = "140.5f") + MuscleCar carDtoToMuscleCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToMuscleCar") + CarDto muscleCarToCarDto(MuscleCar source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java new file mode 100644 index 0000000000..48faa101c3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java @@ -0,0 +1,58 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class ChainedAccessorsCar extends ChainedAccessorsVehicle { + + private String manufacturer; + + protected ChainedAccessorsCar(ChainedAccessorsCarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + } + + public static ChainedAccessorsCarBuilder builder() { + return new ChainedAccessorsCarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public ChainedAccessorsCar setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return this; + } + + public abstract static class ChainedAccessorsCarBuilder> extends ChainedAccessorsVehicleBuilder { + + private String manufacturer; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class ChainedAccessorsCarBuilderImpl + extends ChainedAccessorsCarBuilder { + private ChainedAccessorsCarBuilderImpl() { + } + + protected ChainedAccessorsCarBuilderImpl self() { + return this; + } + + public ChainedAccessorsCar build() { + return new ChainedAccessorsCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java new file mode 100644 index 0000000000..3344fb26f9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java @@ -0,0 +1,72 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class ChainedAccessorsVehicle { + private int amountOfTires; + private Passenger passenger; + + protected ChainedAccessorsVehicle(ChainedAccessorsVehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + this.passenger = b.passenger; + } + + public static ChainedAccessorsVehicleBuilder builder() { + return new ChainedAccessorsVehicleBuilderImpl(); + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public ChainedAccessorsVehicle setAmountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return this; + } + + public ChainedAccessorsVehicle setPassenger(Passenger passenger) { + this.passenger = passenger; + return this; + } + + public abstract static class ChainedAccessorsVehicleBuilder> { + private int amountOfTires; + private Passenger passenger; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class ChainedAccessorsVehicleBuilderImpl + extends ChainedAccessorsVehicleBuilder { + private ChainedAccessorsVehicleBuilderImpl() { + } + + protected ChainedAccessorsVehicleBuilderImpl self() { + return this; + } + + public ChainedAccessorsVehicle build() { + return new ChainedAccessorsVehicle( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java new file mode 100644 index 0000000000..1d07106af8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class InheritedAbstractCar extends AbstractVehicle { + + private final String manufacturer; + + protected InheritedAbstractCar(InheritedAbstractCarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + } + + public static InheritedAbstractCarBuilder builder() { + return new InheritedAbstractCarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public abstract static class InheritedAbstractCarBuilder> + + extends AbstractVehicleBuilder { + private String manufacturer; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class InheritedAbstractCarBuilderImpl + extends InheritedAbstractCarBuilder { + private InheritedAbstractCarBuilderImpl() { + } + + protected InheritedAbstractCarBuilderImpl self() { + return this; + } + + public InheritedAbstractCar build() { + return new InheritedAbstractCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java new file mode 100644 index 0000000000..a9cc6bbdbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.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.superbuilder; + +public class InheritedAbstractCarDto extends AbstractVehicleDto { + + private final String manufacturer; + + public InheritedAbstractCarDto(int amountOfTires, String manufacturer) { + super( amountOfTires ); + this.manufacturer = manufacturer; + } + + public String getManufacturer() { + return manufacturer; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java new file mode 100644 index 0000000000..ec330536ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.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.superbuilder; + +public class MuscleCar extends Car { + + private final float horsePower; + + protected MuscleCar(MuscleCarBuilder b) { + super( b ); + this.horsePower = b.horsePower; + } + + public static MuscleCarBuilder builder() { + return new MuscleCarBuilderImpl(); + } + + public float getHorsePower() { + return this.horsePower; + } + + public abstract static class MuscleCarBuilder> + extends CarBuilder { + + private float horsePower; + + public B horsePower(float horsePower) { + this.horsePower = horsePower; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class MuscleCarBuilderImpl extends MuscleCarBuilder { + private MuscleCarBuilderImpl() { + } + + protected MuscleCarBuilderImpl self() { + return this; + } + + public MuscleCar build() { + return new MuscleCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java new file mode 100644 index 0000000000..7ffe150ecf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.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.superbuilder; + +public class MuscleCarDto extends CarDto { + + private final float horsePower; + + public MuscleCarDto(int amountOfTires, String manufacturer, PassengerDto passenger, float horsePower) { + super( amountOfTires, manufacturer, passenger ); + this.horsePower = horsePower; + } + + public float getHorsePower() { + return horsePower; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java new file mode 100644 index 0000000000..2b6740af78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class Passenger { + + private final String name; + + protected Passenger(PassengerBuilder b) { + this.name = b.name; + } + + public static PassengerBuilder builder() { + return new PassengerBuilderImpl(); + } + + public String getName() { + return this.name; + } + + public abstract static class PassengerBuilder> { + + private String name; + + public B name(String name) { + this.name = name; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class PassengerBuilderImpl extends PassengerBuilder { + private PassengerBuilderImpl() { + } + + protected PassengerBuilderImpl self() { + return this; + } + + public Passenger build() { + return new Passenger( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java new file mode 100644 index 0000000000..f63f6e0683 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.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.superbuilder; + +public class PassengerDto { + + private final String name; + + public PassengerDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java new file mode 100644 index 0000000000..93dfb746b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java @@ -0,0 +1,160 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the mapping of models annotated with @SuperBuilder as source and target. + * + * @author Oliver Erhart + */ +@WithClasses({ + AbstractVehicleDto.class, + CarDto.class, + InheritedAbstractCarDto.class, + MuscleCarDto.class, + PassengerDto.class, + VehicleDto.class, + AbstractVehicle.class, + Car.class, + ChainedAccessorsCar.class, + ChainedAccessorsVehicle.class, + InheritedAbstractCar.class, + MuscleCar.class, + Passenger.class, + Vehicle.class, + CarMapper.class +}) +@IssueKey("3524") +public class SuperBuilderTest { + + @ProcessorTest + public void simpleMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + Car car = CarMapper.INSTANCE.carDtoToCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void simpleMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + Car car = Car.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.carToCarDto( car ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void chainedMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + ChainedAccessorsCar car = CarMapper.INSTANCE.carDtoToChainedAccessorsCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void chainedMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + ChainedAccessorsCar chainedAccessorsCar = ChainedAccessorsCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.chainedAccessorsCarToCarDto( chainedAccessorsCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void inheritedAbstractMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + InheritedAbstractCar car = CarMapper.INSTANCE.carDtoToInheritedAbstractCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void inheritedAbstractMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + InheritedAbstractCar inheritedAbstractCar = InheritedAbstractCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.inheritedAbstractCarToCarDto( inheritedAbstractCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void secondLevelMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + MuscleCar car = CarMapper.INSTANCE.carDtoToMuscleCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getHorsePower() ).isEqualTo( 140.5f ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void secondLevelMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + MuscleCar muscleCar = MuscleCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.muscleCarToCarDto( muscleCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java new file mode 100644 index 0000000000..2f4a19d842 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.superbuilder; + +public class Vehicle { + + private final int amountOfTires; + + protected Vehicle(VehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + } + + public static VehicleBuilder builder() { + return new VehicleBuilderImpl(); + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public abstract static class VehicleBuilder> { + + private int amountOfTires; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class VehicleBuilderImpl extends VehicleBuilder { + private VehicleBuilderImpl() { + } + + protected VehicleBuilderImpl self() { + return this; + } + + public Vehicle build() { + return new Vehicle( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java new file mode 100644 index 0000000000..04dfefb205 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.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.superbuilder; + +public class VehicleDto { + + private final int tireCount; + private final PassengerDto passenger; + + public VehicleDto(int tireCount, PassengerDto passenger) { + this.tireCount = tireCount; + this.passenger = passenger; + } + + public int getTireCount() { + return tireCount; + } + + public PassengerDto getPassenger() { + return passenger; + } +} From 8e66445fe9f13b37ea66191548067a80bcc6d048 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:00:41 +0200 Subject: [PATCH 024/214] #3564 Correct issue key for `Issue3485Test` --- .../java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java index cf7e0563af..7427b63a4e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -15,7 +15,7 @@ /** * @author hduelme */ -@IssueKey("3463") +@IssueKey("3485") public class Issue3485Test { @ProcessorTest From 5fbd36c44392b7f910e24ceec35fa47a98abba89 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 28 Apr 2024 20:20:11 +0200 Subject: [PATCH 025/214] #3577 Improve `Mapping#ignoreByDefault` documentation --- core/src/main/java/org/mapstruct/BeanMapping.java | 6 ++++++ .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index a03546b07d..e94ff98f2d 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -19,6 +19,8 @@ /** * Configures the mapping between two bean types. *

    + * Unless otherwise specified these properties are inherited to the generated bean mapping methods. + *

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

    *

    Example: Determining the result type

    @@ -58,6 +60,8 @@ /** * Specifies the result type of the factory method to be used in case several factory methods qualify. + *

    + * NOTE: This property is not inherited to generated mapping methods * * @return the resultType to select */ @@ -145,6 +149,8 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() * source properties report. *

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

    + * NOTE: This property is not inherited to generated mapping methods * * @return The source properties that should be ignored when performing a report * diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index eb3d5a80ec..2174822359 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -39,7 +39,7 @@ The property name as defined in the http://www.oracle.com/technetwork/java/javas ==== [TIP] ==== -By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. +By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings (including nested ones) have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`. ==== [TIP] From 0a935c67a7c9ca17a3c6980b96d964c46be527a1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Apr 2024 19:04:57 +0200 Subject: [PATCH 026/214] #3565 Presence check methods should not be considered as valid mapping candidates --- .../creation/MappingResolverImpl.java | 3 + .../ap/test/bugs/_3565/Issue3565Mapper.java | 57 +++++++++++++++++++ .../ap/test/bugs/_3565/Issue3565Test.java | 33 +++++++++++ 3 files changed, 93 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index b290ff56bc..e6a26ba045 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -470,6 +470,9 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy } private boolean isCandidateForMapping(Method methodCandidate) { + if ( methodCandidate.isPresenceCheck() ) { + return false; + } return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java new file mode 100644 index 0000000000..b86e667e56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3565; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3565Mapper { + + Issue3565Mapper INSTANCE = Mappers.getMapper( Issue3565Mapper.class ); + + default T mapFromOptional(Optional value) { + return value.orElse( (T) null ); + } + + @Condition + default boolean isOptionalPresent(Optional value) { + return value.isPresent(); + } + + Target map(Source source); + + class Source { + + private final Boolean condition; + + public Source(Boolean condition) { + this.condition = condition; + } + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + } + + class Target { + private String condition; + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + + public void setCondition(String condition) { + this.condition = condition; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java new file mode 100644 index 0000000000..c4cd028d45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3565; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3565Mapper.class) +@IssueKey("3565") +class Issue3565Test { + + @ProcessorTest + void shouldGenerateValidCode() { + Issue3565Mapper.Target target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).isEmpty(); + + target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( false ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).hasValue( "false" ); + } +} From 0a2a0aa526fe1c970baba61c4f9dcc23e975c517 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 29 Apr 2024 08:05:52 +0200 Subject: [PATCH 027/214] #2610 Add support for conditions on source parameters + fix incorrect use of source parameter in presence check method (#3543) The new `@SourceParameterCondition` is also going to cover the problems in #3270 and #3459. The changes in the `MethodFamilySelector` are also fixing #3561 --- .../main/java/org/mapstruct/Condition.java | 40 ++-- .../java/org/mapstruct/ConditionStrategy.java | 23 ++ .../mapstruct/SourceParameterCondition.java | 74 +++++++ ...apter-10-advanced-mapping-options.asciidoc | 53 ++++- .../ap/internal/gem/ConditionStrategyGem.java | 15 ++ .../ap/internal/model/BeanMappingMethod.java | 34 ++- .../model/PresenceCheckMethodResolver.java | 92 ++++++-- .../ap/internal/model/common/Parameter.java | 16 +- .../model/common/ParameterBinding.java | 109 +++++++--- .../model/source/ConditionMethodOptions.java | 45 ++++ .../model/source/ConditionOptions.java | 170 +++++++++++++++ .../ap/internal/model/source/Method.java | 12 +- .../internal/model/source/SourceMethod.java | 43 ++-- .../selector/CreateOrUpdateSelector.java | 1 + .../source/selector/MethodFamilySelector.java | 18 +- .../source/selector/SelectionContext.java | 39 ++++ .../source/selector/SelectionCriteria.java | 12 ++ .../processor/MethodRetrievalProcessor.java | 46 +++- .../creation/MappingResolverImpl.java | 2 +- .../mapstruct/ap/internal/util/Message.java | 5 + .../ap/internal/util/MetaAnnotations.java | 85 ++++++++ .../basic/ConditionalMappingTest.java | 198 ++++++++++++++++++ .../ConditionalMethodForSourceBeanMapper.java | 64 ++++++ ...odForSourceParameterAndPropertyMapper.java | 85 ++++++++ ...ourceParameterConditionalMethodMapper.java | 28 +++ ...nditionalWithoutAppliesToMethodMapper.java | 24 +++ ...terConditionalWithMappingTargetMapper.java | 26 +++ ...nditionalWithSourcePropertyNameMapper.java | 26 +++ ...nditionalWithTargetPropertyNameMapper.java | 26 +++ ...ameterConditionalWithTargetTypeMapper.java | 26 +++ .../mapstruct/ap/test/gem/EnumGemsTest.java | 8 + 31 files changed, 1326 insertions(+), 119 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/ConditionStrategy.java create mode 100644 core/src/main/java/org/mapstruct/SourceParameterCondition.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 37f1553be1..1273deab0a 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -11,26 +11,35 @@ import java.lang.annotation.Target; /** - * This annotation marks a method as a presence check method to check check for presence in beans. + * This annotation marks a method as a presence check method to check for presence in beans + * or it can be used to define additional check methods for something like source parameters. *

    - * By default bean properties are checked against {@code null} or using a presence check method in the source bean. + * By default, bean properties are checked against {@code null} or using a presence check method in the source bean. * If a presence check method is available then it will be used instead. *

    * Presence check methods have to return {@code boolean}. * The following parameters are accepted for the presence check methods: *

      *
    • The parameter with the value of the source property. - * e.g. the value given by calling {@code getName()} for the name property of the source bean
    • + * e.g. the value given by calling {@code getName()} for the name property of the source bean + * - only possible when using the {@link ConditionStrategy#PROPERTIES} + * *
    • The mapping source parameter
    • *
    • {@code @}{@link Context} parameter
    • - *
    • {@code @}{@link TargetPropertyName} parameter
    • - *
    • {@code @}{@link SourcePropertyName} parameter
    • + *
    • + * {@code @}{@link TargetPropertyName} parameter - + * only possible when using the {@link ConditionStrategy#PROPERTIES} + *
    • + *
    • + * {@code @}{@link SourcePropertyName} parameter - + * only possible when using the {@link ConditionStrategy#PROPERTIES} + *
    • *
    * * Note: The usage of this annotation is mandatory * for a method to be considered as a presence check method. * - *
    
    + * 
    
      * public class PresenceCheckUtils {
      *
      *   @Condition
    @@ -45,11 +54,10 @@
      *     MovieDto map(Movie movie);
      * }
      * 
    - * + *

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

    - * 
    + * 
    
      * public class MovieMapperImpl implements MovieMapper {
      *
      *     @Override
    @@ -67,14 +75,22 @@
      *         return movieDto;
      *     }
      * }
    - * 
    - * 
    + *
    + *

    + * This annotation can also be used as a meta-annotation to define the condition strategy. * * @author Filip Hrisafov + * @see SourceParameterCondition * @since 1.5 */ -@Target({ ElementType.METHOD }) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.CLASS) public @interface Condition { + /** + * @return the places where the condition should apply to + * @since 1.6 + */ + ConditionStrategy[] appliesTo() default ConditionStrategy.PROPERTIES; + } diff --git a/core/src/main/java/org/mapstruct/ConditionStrategy.java b/core/src/main/java/org/mapstruct/ConditionStrategy.java new file mode 100644 index 0000000000..6b042017c2 --- /dev/null +++ b/core/src/main/java/org/mapstruct/ConditionStrategy.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Strategy for defining what to what a condition (check) method is applied to + * + * @author Filip Hrisafov + * @since 1.6 + */ +public enum ConditionStrategy { + /** + * The condition method should be applied whether a property should be mapped. + */ + PROPERTIES, + /** + * The condition method should be applied to check if a source parameters should be mapped. + */ + SOURCE_PARAMETERS, +} diff --git a/core/src/main/java/org/mapstruct/SourceParameterCondition.java b/core/src/main/java/org/mapstruct/SourceParameterCondition.java new file mode 100644 index 0000000000..8bff97abc4 --- /dev/null +++ b/core/src/main/java/org/mapstruct/SourceParameterCondition.java @@ -0,0 +1,74 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a method as a check method to check if a source parameter needs to be mapped. + *

    + * By default, source parameters are checked against {@code null}, unless they are primitives. + *

    + * Check methods have to return {@code boolean}. + * The following parameters are accepted for the presence check methods: + *

      + *
    • The mapping source parameter
    • + *
    • {@code @}{@link Context} parameter
    • + *
    + * + * Note: The usage of this annotation is mandatory + * for a method to be considered as a source check method. + * + *
    
    + * public class PresenceCheckUtils {
    + *
    + *   @SourceParameterCondition
    + *   public static boolean isDefined(Car car) {
    + *      return car != null && car.getId() != null;
    + *   }
    + * }
    + *
    + * @Mapper(uses = PresenceCheckUtils.class)
    + * public interface CarMapper {
    + *
    + *     CarDto map(Car car);
    + * }
    + * 
    + * + * The following implementation of {@code CarMapper} will be generated: + * + *
    
    + * public class CarMapperImpl implements CarMapper {
    + *
    + *     @Override
    + *     public CarDto map(Car car) {
    + *         if ( !PresenceCheckUtils.isDefined( car ) ) {
    + *             return null;
    + *         }
    + *
    + *         CarDto carDto = new CarDto();
    + *
    + *         carDto.setId( car.getId() );
    + *         // ...
    + *
    + *         return carDto;
    + *     }
    + * }
    + * 
    + * + * @author Filip Hrisafov + * @since 1.6 + * @see Condition @Condition + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.CLASS) +@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) +public @interface SourceParameterCondition { + +} diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index db13c0548b..1e2bd133d4 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -303,8 +303,10 @@ null check, regardless the value of the `NullValueCheckStrategy` to avoid additi Conditional Mapping is a type of <>. The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not. +Conditional mapping can also be used to check if a source parameter should be mapped or not. -A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. +A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. +A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)` e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like: @@ -484,6 +486,55 @@ Methods annotated with `@Condition` in addition to the value of the source prope <> is also valid for `@Condition` methods. In order to use a more specific condition method you will need to use one of `Mapping#conditionQualifiedByName` or `Mapping#conditionQualifiedBy`. +If we want to only map cars that have an id provided then we can do something like: + + +.Mapper using custom condition source parameter check method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car); + + @SourceParameterCondition + default boolean hasCar(Car car) { + return car != null && car.getId() != null; + } +} +---- +==== + +The generated mapper will look like: + +.Custom condition source parameter check generated implementation +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car) { + if ( !hasCar( car ) ) { + return null; + } + + CarDto carDto = new CarDto(); + + carDto.setOwner( car.getOwner() ); + + // Mapping of other properties + + return carDto; + } +} +---- +==== + [[exceptions]] === Exceptions diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java new file mode 100644 index 0000000000..adea4b4c1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.gem; + +/** + * @author Filip Hrisafov + */ +public enum ConditionStrategyGem { + + PROPERTIES, + SOURCE_PARAMETERS +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index dd3a86e2cf..cf5179f9cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -411,6 +411,26 @@ else if ( !method.isUpdateMethod() ) { removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); } + Map presenceChecksByParameter = new LinkedHashMap<>(); + for ( Parameter sourceParameter : method.getSourceParameters() ) { + PresenceCheck parameterPresenceCheck = PresenceCheckMethodResolver.getPresenceCheckForSourceParameter( + method, + selectionParameters, + sourceParameter, + ctx + ); + if ( parameterPresenceCheck != null ) { + presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { + presenceChecksByParameter.put( + sourceParameter.getName(), + new NullPresenceCheck( sourceParameter.getName() ) + ); + } + } + + return new BeanMappingMethod( method, getMethodAnnotations(), @@ -426,7 +446,8 @@ else if ( !method.isUpdateMethod() ) { afterMappingReferencesWithFinalizedReturnType, finalizeMethod, mappingReferences, - subclasses + subclasses, + presenceChecksByParameter ); } @@ -1891,7 +1912,8 @@ private BeanMappingMethod(Method method, List afterMappingReferencesWithFinalizedReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, - List subclassMappings) { + List subclassMappings, + Map presenceChecksByParameter) { super( method, annotations, @@ -1923,18 +1945,12 @@ private BeanMappingMethod(Method method, // parameter mapping. this.mappingsByParameter = new HashMap<>(); this.constantMappings = new ArrayList<>( propertyMappings.size() ); - this.presenceChecksByParameter = new LinkedHashMap<>(); + this.presenceChecksByParameter = presenceChecksByParameter; this.constructorMappingsByParameter = new LinkedHashMap<>(); this.constructorConstantMappings = new ArrayList<>(); Set sourceParameterNames = new HashSet<>(); for ( Parameter sourceParameter : getSourceParameters() ) { sourceParameterNames.add( sourceParameter.getName() ); - if ( !sourceParameter.getType().isPrimitive() ) { - presenceChecksByParameter.put( - sourceParameter.getName(), - new NullPresenceCheck( sourceParameter.getName() ) - ); - } } for ( PropertyMapping mapping : propertyMappings ) { if ( mapping.isConstructorMapping() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index a5c873743d..5906db8219 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.source.Method; @@ -18,6 +19,7 @@ import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; import org.mapstruct.ap.internal.model.source.selector.SelectionContext; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; /** @@ -34,36 +36,53 @@ public static PresenceCheck getPresenceCheck( SelectionParameters selectionParameters, MappingBuilderContext ctx ) { - SelectedMethod matchingMethod = findMatchingPresenceCheckMethod( + List> matchingMethods = findMatchingMethods( method, - selectionParameters, + SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ), ctx ); - if ( matchingMethod == null ) { + if ( matchingMethods.isEmpty() ) { + return null; + } + + if ( matchingMethods.size() > 1 ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD, + selectionParameters.getSourceRHS().getSourceType().describe(), + matchingMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + return null; } + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); return new MethodReferencePresenceCheck( methodReference ); } - private static SelectedMethod findMatchingPresenceCheckMethod( + public static PresenceCheck getPresenceCheckForSourceParameter( Method method, SelectionParameters selectionParameters, + Parameter sourceParameter, MappingBuilderContext ctx ) { - MethodSelectors selectors = new MethodSelectors( - ctx.getTypeUtils(), - ctx.getElementUtils(), - ctx.getMessager() - ); - - List> matchingMethods = selectors.getMatchingMethods( - getAllAvailableMethods( method, ctx.getSourceModel() ), - SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ) + List> matchingMethods = findMatchingMethods( + method, + SelectionContext.forSourceParameterPresenceCheckMethods( + method, + selectionParameters, + sourceParameter, + ctx.getTypeFactory() + ), + ctx ); if ( matchingMethods.isEmpty() ) { @@ -73,8 +92,8 @@ private static SelectedMethod findMatchingPresenceCheckMethod( if ( matchingMethods.size() > 1 ) { ctx.getMessager().printMessage( method.getExecutable(), - Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD, - selectionParameters.getSourceRHS().getSourceType().describe(), + Message.GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD, + sourceParameter.getType().describe(), matchingMethods.stream() .map( SelectedMethod::getMethod ) .map( Method::describe ) @@ -84,7 +103,29 @@ private static SelectedMethod findMatchingPresenceCheckMethod( return null; } - return matchingMethods.get( 0 ); + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); + + return new MethodReferencePresenceCheck( methodReference ); + + } + + private static List> findMatchingMethods( + Method method, + SelectionContext selectionContext, + MappingBuilderContext ctx + ) { + MethodSelectors selectors = new MethodSelectors( + ctx.getTypeUtils(), + ctx.getElementUtils(), + ctx.getMessager() + ); + + return selectors.getMatchingMethods( + getAllAvailableMethods( method, ctx.getSourceModel(), selectionContext.getSelectionCriteria() ), + selectionContext + ); } private static MethodReference getPresenceCheckMethodReference( @@ -116,7 +157,8 @@ private static MethodReference getPresenceCheckMethodReference( } } - private static List getAllAvailableMethods(Method method, List sourceModelMethods) { + private static List getAllAvailableMethods(Method method, List sourceModelMethods, + SelectionCriteria selectionCriteria) { ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); if ( contextProvidedMethods.isEmpty() ) { return sourceModelMethods; @@ -129,9 +171,19 @@ private static List getAllAvailableMethods(Method method, List( methodsProvidedByParams.size() + sourceModelMethods.size() ); for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) { - // add only methods from context that do have the @Condition annotation - if ( methodProvidedByParams.isPresenceCheck() ) { - availableMethods.add( methodProvidedByParams ); + if ( selectionCriteria.isPresenceCheckRequired() ) { + // add only methods from context that do have the @Condition for properties annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + availableMethods.add( methodProvidedByParams ); + } + } + else if ( selectionCriteria.isSourceParameterCheckRequired() ) { + // add only methods from context that do have the @Condition for source parameters annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + availableMethods.add( methodProvidedByParams ); + } } } availableMethods.addAll( sourceModelMethods ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 44ac0eb7f0..aaab7f46ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -133,6 +133,14 @@ public boolean isVarArgs() { return varArgs; } + public boolean isSourceParameter() { + return !isMappingTarget() && + !isTargetType() && + !isMappingContext() && + !isSourcePropertyName() && + !isTargetPropertyName(); + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -224,12 +232,4 @@ public static Parameter getTargetPropertyNameParameter(List parameter return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } - private static boolean isSourceParameter( Parameter parameter ) { - return !parameter.isMappingTarget() && - !parameter.isTargetType() && - !parameter.isMappingContext() && - !parameter.isSourcePropertyName() && - !parameter.isTargetPropertyName(); - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java index 0791ee626a..c1a594c73a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.model.common; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -19,23 +21,14 @@ public class ParameterBinding { private final Type type; private final String variableName; - private final boolean targetType; - private final boolean mappingTarget; - private final boolean mappingContext; - private final boolean sourcePropertyName; - private final boolean targetPropertyName; private final SourceRHS sourceRHS; + private final Collection bindingTypes; - private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, + private ParameterBinding(Type parameterType, String variableName, Collection bindingTypes, SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; - this.targetType = targetType; - this.mappingTarget = mappingTarget; - this.mappingContext = mappingContext; - this.sourcePropertyName = sourcePropertyName; - this.targetPropertyName = targetPropertyName; + this.bindingTypes = bindingTypes; this.sourceRHS = sourceRHS; } @@ -46,39 +39,47 @@ public String getVariableName() { return variableName; } + public boolean isSourceParameter() { + return bindingTypes.contains( BindingType.PARAMETER ); + } + /** * @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter. */ public boolean isTargetType() { - return targetType; + return bindingTypes.contains( BindingType.TARGET_TYPE ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter. */ public boolean isMappingTarget() { - return mappingTarget; + return bindingTypes.contains( BindingType.MAPPING_TARGET ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter. */ public boolean isMappingContext() { - return mappingContext; + return bindingTypes.contains( BindingType.CONTEXT ); + } + + public boolean isForSourceRhs() { + return bindingTypes.contains( BindingType.SOURCE_RHS ); } /** * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. */ public boolean isSourcePropertyName() { - return sourcePropertyName; + return bindingTypes.contains( BindingType.SOURCE_PROPERTY_NAME ); } /** * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. */ public boolean isTargetPropertyName() { - return targetPropertyName; + return bindingTypes.contains( BindingType.TARGET_PROPERTY_NAME ); } /** @@ -96,7 +97,7 @@ public SourceRHS getSourceRHS() { } public Set getImportTypes() { - if ( targetType ) { + if ( isTargetType() ) { return type.getImportTypes(); } @@ -112,14 +113,31 @@ public Set getImportTypes() { * @return a parameter binding reflecting the given parameter as being used as argument for a method call */ public static ParameterBinding fromParameter(Parameter parameter) { + EnumSet bindingTypes = EnumSet.of( BindingType.PARAMETER ); + if ( parameter.isMappingTarget() ) { + bindingTypes.add( BindingType.MAPPING_TARGET ); + } + + if ( parameter.isTargetType() ) { + bindingTypes.add( BindingType.TARGET_TYPE ); + } + + if ( parameter.isMappingContext() ) { + bindingTypes.add( BindingType.CONTEXT ); + } + + if ( parameter.isSourcePropertyName() ) { + bindingTypes.add( BindingType.SOURCE_PROPERTY_NAME ); + } + + if ( parameter.isTargetPropertyName() ) { + bindingTypes.add( BindingType.TARGET_PROPERTY_NAME ); + } + return new ParameterBinding( parameter.getType(), parameter.getName(), - parameter.isMappingTarget(), - parameter.isTargetType(), - parameter.isMappingContext(), - parameter.isSourcePropertyName(), - parameter.isTargetPropertyName(), + bindingTypes, null ); } @@ -136,11 +154,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame return new ParameterBinding( parameterType, parameterName, - false, - false, - false, - false, - false, + Collections.emptySet(), null ); } @@ -150,21 +164,31 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null ); + return new ParameterBinding( classTypeOf, null, Collections.singleton( BindingType.TARGET_TYPE ), null ); } /** * @return a parameter binding representing a target property name parameter */ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null ); + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.TARGET_PROPERTY_NAME ), + null + ); } /** * @return a parameter binding representing a source property name parameter */ public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null ); + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.SOURCE_PROPERTY_NAME ), + null + ); } /** @@ -172,7 +196,7 @@ public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, false, false, null ); + return new ParameterBinding( resultType, null, Collections.singleton( BindingType.MAPPING_TARGET ), null ); } /** @@ -180,10 +204,27 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, false, false, null ); + return new ParameterBinding( sourceType, null, Collections.singleton( BindingType.SOURCE_TYPE ), null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS ); + return new ParameterBinding( + sourceRHS.getSourceType(), + null, + Collections.singleton( BindingType.SOURCE_RHS ), + sourceRHS + ); + } + + enum BindingType { + PARAMETER, + FROM_TYPE_AND_NAME, + TARGET_TYPE, + TARGET_PROPERTY_NAME, + SOURCE_PROPERTY_NAME, + MAPPING_TARGET, + CONTEXT, + SOURCE_TYPE, + SOURCE_RHS } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java new file mode 100644 index 0000000000..dfb0865ecc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source; + +import java.util.Collection; +import java.util.Collections; + +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; + +/** + * Encapsulates all options specific for a condition check method. + * + * @author Filip Hrisafov + */ +public class ConditionMethodOptions { + + private static final ConditionMethodOptions EMPTY = new ConditionMethodOptions( Collections.emptyList() ); + + private final Collection conditionOptions; + + public ConditionMethodOptions(Collection conditionOptions) { + this.conditionOptions = conditionOptions; + } + + public boolean isStrategyApplicable(ConditionStrategyGem strategy) { + for ( ConditionOptions conditionOption : conditionOptions ) { + if ( conditionOption.getConditionStrategies().contains( strategy ) ) { + return true; + } + } + + return false; + } + + public boolean isAnyStrategyApplicable() { + return !conditionOptions.isEmpty(); + } + + public static ConditionMethodOptions empty() { + return EMPTY; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java new file mode 100644 index 0000000000..936d049af8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -0,0 +1,170 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +/** + * @author Filip Hrisafov + */ +public class ConditionOptions { + + private final Set conditionStrategies; + + private ConditionOptions(Set conditionStrategies) { + this.conditionStrategies = conditionStrategies; + } + + public Collection getConditionStrategies() { + return conditionStrategies; + } + + public static ConditionOptions getInstanceOn(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + if ( condition == null ) { + return null; + } + + TypeMirror returnType = method.getReturnType(); + TypeKind returnTypeKind = returnType.getKind(); + // We only allow methods that return boolean or Boolean to be condition methods + if ( returnTypeKind != TypeKind.BOOLEAN ) { + if ( returnTypeKind != TypeKind.DECLARED ) { + return null; + } + DeclaredType declaredType = (DeclaredType) returnType; + TypeElement returnTypeElement = (TypeElement) declaredType.asElement(); + if ( !returnTypeElement.getQualifiedName().contentEquals( Boolean.class.getCanonicalName() ) ) { + return null; + } + } + + Set strategies = condition.appliesTo().get() + .stream() + .map( ConditionStrategyGem::valueOf ) + .collect( Collectors.toCollection( () -> EnumSet.noneOf( ConditionStrategyGem.class ) ) ); + + if ( strategies.isEmpty() ) { + messager.printMessage( + method, + condition.mirror(), + condition.appliesTo().getAnnotationValue(), + Message.CONDITION_MISSING_APPLIES_TO_STRATEGY + ); + + return null; + } + + boolean allStrategiesValid = true; + + for ( ConditionStrategyGem strategy : strategies ) { + boolean isStrategyValid = isValid( strategy, condition, method, parameters, messager ); + allStrategiesValid &= isStrategyValid; + } + + return allStrategiesValid ? new ConditionOptions( strategies ) : null; + } + + protected static boolean isValid(ConditionStrategyGem strategy, ConditionGem condition, + ExecutableElement method, List parameters, + FormattingMessager messager) { + if ( strategy == ConditionStrategyGem.SOURCE_PARAMETERS ) { + return hasValidStrategyForSourceProperties( condition, method, parameters, messager ); + } + else if ( strategy == ConditionStrategyGem.PROPERTIES ) { + return hasValidStrategyForProperties( condition, method, parameters, messager ); + } + else { + throw new IllegalStateException( "Invalid condition strategy: " + strategy ); + } + } + + protected static boolean hasValidStrategyForSourceProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a source condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a source condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER, + parameter.describe() + ); + return false; + } + return true; + } + + protected static boolean hasValidStrategyForProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetType() ) { + // target type parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingTarget() ) { + // mapping target parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isSourcePropertyName() ) { + // source property name parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetPropertyName() ) { + // target property name parameter is a valid parameter for a property condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_PROPERTIES_INVALID_PARAMETER, + parameter + ); + return false; + } + return true; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index 0c60f41364..ad2882080a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -90,14 +90,6 @@ public interface Method { */ boolean isObjectFactory(); - /** - * Returns whether the method is designated as a presence check method - * @return {@code true} if it is a presence check method - */ - default boolean isPresenceCheck() { - return false; - } - /** * Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType } * @@ -187,6 +179,10 @@ default boolean isPresenceCheck() { */ MappingMethodOptions getOptions(); + default ConditionMethodOptions getConditionOptions() { + return ConditionMethodOptions.empty(); + } + /** * * @return true when @MappingTarget annotated parameter is the same type as the return type. The method has diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 42c318ca49..7103fb2858 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -15,7 +15,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import org.mapstruct.ap.internal.gem.ConditionGem; import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Parameter; @@ -50,11 +49,11 @@ public class SourceMethod implements Method { private final Parameter sourcePropertyNameParameter; private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; - private final boolean isPresenceCheck; private final Type returnType; private final Accessibility accessibility; private final List exceptionTypes; private final MappingMethodOptions mappingMethodOptions; + private final ConditionMethodOptions conditionMethodOptions; private final List prototypeMethods; private final Type mapperToImplement; @@ -95,6 +94,7 @@ public static class Builder { private List valueMappings; private EnumMappingOptions enumMappingOptions; private ParameterProvidedMethods contextProvidedMethods; + private Set conditionOptions; private List typeParameters; private Set subclassMappings; @@ -196,6 +196,11 @@ public Builder setContextProvidedMethods(ParameterProvidedMethods contextProvide return this; } + public Builder setConditionOptions(Set conditionOptions) { + this.conditionOptions = conditionOptions; + return this; + } + public Builder setVerboseLogging(boolean verboseLogging) { this.verboseLogging = verboseLogging; return this; @@ -223,17 +228,22 @@ public SourceMethod build() { subclassValidator ); + ConditionMethodOptions conditionMethodOptions = + conditionOptions != null ? new ConditionMethodOptions( conditionOptions ) : + ConditionMethodOptions.empty(); + this.typeParameters = this.executable.getTypeParameters() .stream() .map( Element::asType ) .map( typeFactory::getType ) .collect( Collectors.toList() ); - return new SourceMethod( this, mappingMethodOptions ); + return new SourceMethod( this, mappingMethodOptions, conditionMethodOptions ); } } - private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) { + private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions, + ConditionMethodOptions conditionMethodOptions) { this.declaringMapper = builder.declaringMapper; this.executable = builder.executable; this.parameters = builder.parameters; @@ -242,6 +252,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() ); this.mappingMethodOptions = mappingMethodOptions; + this.conditionMethodOptions = conditionMethodOptions; this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); @@ -254,7 +265,6 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); - this.isPresenceCheck = determineIfIsPresenceCheck(); this.typeUtils = builder.typeUtils; this.typeFactory = builder.typeFactory; @@ -274,19 +284,6 @@ private boolean determineIfIsObjectFactory() { && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } - private boolean determineIfIsPresenceCheck() { - if ( returnType.isPrimitive() ) { - if ( !returnType.getName().equals( "boolean" ) ) { - return false; - } - } - else if ( !returnType.getFullyQualifiedName().equals( Boolean.class.getCanonicalName() ) ) { - return false; - } - - return ConditionGem.instanceOn( executable ) != null; - } - @Override public Type getDeclaringMapper() { return declaringMapper; @@ -547,6 +544,11 @@ public MappingMethodOptions getOptions() { return mappingMethodOptions; } + @Override + public ConditionMethodOptions getConditionOptions() { + return conditionMethodOptions; + } + @Override public boolean isStatic() { return executable.getModifiers().contains( Modifier.STATIC ); @@ -567,11 +569,6 @@ public boolean isLifecycleCallbackMethod() { return Executables.isLifecycleCallbackMethod( getExecutable() ); } - @Override - public boolean isPresenceCheck() { - return isPresenceCheck; - } - public boolean isAfterMappingMethod() { return Executables.isAfterMappingMethod( getExecutable() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index 03a671de57..b6e5ca0a53 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -32,6 +32,7 @@ public List> getMatchingMethods(List> result = new ArrayList<>( methods.size() ); for ( SelectedMethod method : methods ) { - if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() + if ( criteria.isPresenceCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + result.add( method ); + } + } + else if ( criteria.isSourceParameterCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + result.add( method ); + } + } + else if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() && method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() - && method.getMethod().isPresenceCheck() == criteria.isPresenceCheckRequired() ) { result.add( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 800278a4c4..84bd04d7db 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -163,6 +163,45 @@ public static SelectionContext forPresenceCheckMethods(Method mappingMethod, ); } + public static SelectionContext forSourceParameterPresenceCheckMethods(Method mappingMethod, + SelectionParameters selectionParameters, + Parameter sourceParameter, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forSourceParameterCheckMethods( selectionParameters ); + Type booleanType = typeFactory.getType( Boolean.class ); + return new SelectionContext( + null, + criteria, + mappingMethod, + booleanType, + booleanType, + () -> getParameterBindingsForSourceParameterPresenceCheck( + mappingMethod, + booleanType, + sourceParameter, + typeFactory + ) + ); + } + + private static List getParameterBindingsForSourceParameterPresenceCheck(Method method, + Type targetType, + Parameter sourceParameter, + TypeFactory typeFactory) { + + List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); + + availableParams.add( ParameterBinding.fromParameter( sourceParameter ) ); + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + for ( Parameter parameter : method.getParameters() ) { + if ( !parameter.isSourceParameter( ) ) { + availableParams.add( ParameterBinding.fromParameter( parameter ) ); + } + } + + return availableParams; + } + private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType, SourceRHS sourceRHS, TypeFactory typeFactory) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index 2d288dd56e..fa5e1c29c0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -97,6 +97,13 @@ public boolean isPresenceCheckRequired() { return type == Type.PRESENCE_CHECK; } + /** + * @return {@code true} if source parameter check methods should be selected, {@code false} otherwise + */ + public boolean isSourceParameterCheckRequired() { + return type == Type.SOURCE_PARAMETER_CHECK; + } + public void setIgnoreQualifiers(boolean ignoreQualifiers) { this.ignoreQualifiers = ignoreQualifiers; } @@ -177,6 +184,10 @@ public static SelectionCriteria forPresenceCheckMethods(SelectionParameters sele return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); } + public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, null, Type.SOURCE_PARAMETER_CHECK ); + } + public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters, MappingControl mappingControl) { return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); @@ -187,6 +198,7 @@ public enum Type { OBJECT_FACTORY, LIFECYCLE_CALLBACK, PRESENCE_CHECK, + SOURCE_PARAMETER_CHECK, SELF_NOT_ALLOWED, } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index a9099ea179..4bd2ed48a5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -36,6 +36,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; +import org.mapstruct.ap.internal.model.source.ConditionOptions; import org.mapstruct.ap.internal.model.source.EnumMappingOptions; import org.mapstruct.ap.internal.model.source.IterableMappingOptions; import org.mapstruct.ap.internal.model.source.MapMappingOptions; @@ -53,6 +54,7 @@ import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.MetaAnnotations; import org.mapstruct.ap.internal.util.RepeatableAnnotations; import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.spi.EnumTransformationStrategy; @@ -73,6 +75,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextProvidedMethods = new ArrayList<>( contextParamMethods.size() ); for ( SourceMethod sourceMethod : contextParamMethods ) { if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() - || sourceMethod.isPresenceCheck() ) { + || sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) { contextProvidedMethods.add( sourceMethod ); } } @@ -393,6 +396,7 @@ private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType .setExceptionTypes( exceptionTypes ) .setTypeUtils( typeUtils ) .setTypeFactory( typeFactory ) + .setConditionOptions( getConditionOptions( method, parameters ) ) .setVerboseLogging( options.isVerbose() ) .build(); } @@ -633,6 +637,18 @@ private Set getSubclassMappings(List sourcePa .getProcessedAnnotations( method ); } + /** + * Retrieves the conditions configured via {@code @Condition} from the given method. + * + * @param method The method of interest + * @param parameters + * @return The condition options for the given method + */ + + private Set getConditionOptions(ExecutableElement method, List parameters) { + return new MetaConditions( parameters ).getProcessedAnnotations( method ); + } + private class RepeatableMappings extends RepeatableAnnotations { private BeanMappingOptions beanMappingOptions; @@ -774,4 +790,32 @@ protected void addInstances(ValueMappingsGem gems, Element source, Set { + + protected final List parameters; + + protected MetaConditions(List parameters) { + super( elementUtils, CONDITION_FQN ); + this.parameters = parameters; + } + + @Override + protected ConditionGem instanceOn(Element element) { + return ConditionGem.instanceOn( element ); + } + + @Override + protected void addInstance(ConditionGem gem, Element source, Set values) { + ConditionOptions options = ConditionOptions.getInstanceOn( + gem, + (ExecutableElement) source, + parameters, + messager + ); + if ( options != null ) { + values.add( options ); + } + } + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index e6a26ba045..d84ba974db 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -470,7 +470,7 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy } private boolean isCandidateForMapping(Method methodCandidate) { - if ( methodCandidate.isPresenceCheck() ) { + if ( methodCandidate.getConditionOptions().isAnyStrategyApplicable() ) { return false; } return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 68c079cb96..5887ee07f0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -46,6 +46,10 @@ public enum Message { BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), + CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), + CONDITION_PROPERTIES_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#PROPERTIES. Only source, @Context, @MappingTarget, @TargetType, @TargetPropertyName and @SourcePropertyName parameters are allowed for conditions applicable to properties." ), + PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ), PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ), PROPERTYMAPPING_SELECT_NOTE( "selecting property mapping: %s.", Diagnostic.Kind.NOTE ), @@ -143,6 +147,7 @@ public enum Message { GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD( "Ambiguous source parameter check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s: %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ), GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ), GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java new file mode 100644 index 0000000000..f2328a91cc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java @@ -0,0 +1,85 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.tools.gem.Gem; + +/** + * @author Filip Hrisafov + */ +public abstract class MetaAnnotations { + + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + + private final ElementUtils elementUtils; + private final String annotationFqn; + + protected MetaAnnotations(ElementUtils elementUtils, String annotationFqn) { + this.elementUtils = elementUtils; + this.annotationFqn = annotationFqn; + } + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @return The processed annotations for the given element + */ + public Set getProcessedAnnotations(Element source) { + return getValues( source, source, new LinkedHashSet<>(), new HashSet<>() ); + } + + protected abstract G instanceOn(Element element); + + protected abstract void addInstance(G gem, Element source, Set values); + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @param element Element of interest: method, or (meta) annotation + * @param values the set of values found so far + * @param handledElements The collection of already handled elements to handle recursion correctly. + * @return The processed annotations for the given element + */ + private Set getValues(Element source, Element element, Set values, Set handledElements) { + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element annotationElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( annotationElement, annotationFqn ) ) { + G gem = instanceOn( element ); + addInstance( gem, source, values ); + } + else if ( isNotJavaAnnotation( element ) && !handledElements.contains( annotationElement ) ) { + handledElements.add( annotationElement ); + getValues( source, annotationElement, values, handledElements ); + } + } + return values; + } + + private boolean isNotJavaAnnotation(Element element) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return !elementUtils.getPackageOf( element ).getQualifiedName().contentEquals( JAVA_LANG_ANNOTATION_PGK ); + } + return true; + } + + private boolean isAnnotation(Element element, String annotationFqn) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return ( (TypeElement) element ).getQualifiedName().contentEquals( annotationFqn ); + } + + return false; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java index 57a68a9526..ae309988c8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java @@ -208,6 +208,84 @@ public void conditionalMethodForCollection() { .containsExactly( "Test", "Test Vol. 2" ); } + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceBeanMapper.class + }) + public void conditionalMethodForSourceBean() { + ConditionalMethodForSourceBeanMapper mapper = ConditionalMethodForSourceBeanMapper.INSTANCE; + + ConditionalMethodForSourceBeanMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceBeanMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( null, "Tester" ) ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( "test-123", "Tester" ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "test-123" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceParameterAndPropertyMapper.class + }) + public void conditionalMethodForSourceParameterAndProperty() { + ConditionalMethodForSourceParameterAndPropertyMapper mapper = + ConditionalMethodForSourceParameterAndPropertyMapper.INSTANCE; + + ConditionalMethodForSourceParameterAndPropertyMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( null, "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( "2", "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNotNull(); + assertThat( employee.getManager().getId() ).isEqualTo( "2" ); + assertThat( employee.getManager().getName() ).isEqualTo( "Manager" ); + } + @ProcessorTest @WithClasses({ OptionalLikeConditionalMapper.class @@ -244,4 +322,124 @@ public void conditionalMethodWithMappingTarget() { assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" ); } + + @ProcessorTest + @WithClasses({ + ErroneousConditionalWithoutAppliesToMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousConditionalWithoutAppliesToMethodMapper.class, + line = 19, + message = "'appliesTo' has to have at least one value in @Condition" + ) + } + ) + public void emptyConditional() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithMappingTargetMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithMappingTargetMapper.class, + line = 21, + message = "Parameter \"@MappingTarget BasicEmployee employee\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithMappingTarget() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetTypeMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetTypeMapper.class, + line = 21, + message = "Parameter \"@TargetType Class targetClass\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetType() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class, + line = 21, + message = "Parameter \"@TargetPropertyName String targetProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetPropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class, + line = 21, + message = "Parameter \"@SourcePropertyName String sourceProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParametersConditionalWithSourcePropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousAmbiguousSourceParameterConditionalMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousAmbiguousSourceParameterConditionalMethodMapper.class, + line = 17, + message = "Ambiguous source parameter check methods found for checking BasicEmployeeDto: " + + "boolean hasName(BasicEmployeeDto value), " + + "boolean hasStrategy(BasicEmployeeDto value). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + ) + } + ) + public void ambiguousSourceParameterConditionalMethod() { + + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java new file mode 100644 index 0000000000..dd2fe4ac90 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceBeanMapper { + + ConditionalMethodForSourceBeanMapper INSTANCE = Mappers.getMapper( ConditionalMethodForSourceBeanMapper.class ); + + Employee map(EmployeeDto employee); + + @SourceParameterCondition + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + + public EmployeeDto(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } + + class Employee { + + private final String id; + private final String name; + + public Employee(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java new file mode 100644 index 0000000000..11a97b7239 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java @@ -0,0 +1,85 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceParameterAndPropertyMapper { + + ConditionalMethodForSourceParameterAndPropertyMapper INSTANCE = Mappers.getMapper( + ConditionalMethodForSourceParameterAndPropertyMapper.class ); + + Employee map(EmployeeDto employee); + + @Condition(appliesTo = { + ConditionStrategy.SOURCE_PARAMETERS, + ConditionStrategy.PROPERTIES + }) + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + private final EmployeeDto manager; + + public EmployeeDto(String id, String name) { + this( id, name, null ); + } + + public EmployeeDto(String id, String name, EmployeeDto manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployeeDto getManager() { + return manager; + } + } + + class Employee { + + private final String id; + private final String name; + private final Employee manager; + + public Employee(String id, String name, Employee manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Employee getManager() { + return manager; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java new file mode 100644 index 0000000000..39433e2641 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousSourceParameterConditionalMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @SourceParameterCondition + default boolean hasName(BasicEmployeeDto value) { + return value != null && value.getName() != null; + } + + @SourceParameterCondition + default boolean hasStrategy(BasicEmployeeDto value) { + return value != null && value.getStrategy() != null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java new file mode 100644 index 0000000000..76f62845d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousConditionalWithoutAppliesToMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = {}) + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java new file mode 100644 index 0000000000..b1206798d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithMappingTargetMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @MappingTarget BasicEmployee employee) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java new file mode 100644 index 0000000000..1bfd150afc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.SourcePropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithSourcePropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @SourcePropertyName String sourceProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java new file mode 100644 index 0000000000..e9dac1ece5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetPropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetPropertyName String targetProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java new file mode 100644 index 0000000000..9ff55597ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetTypeMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetType Class targetClass) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java index 0c92fc6bbe..f460180651 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 @@ -11,12 +11,14 @@ import org.junit.jupiter.api.Test; import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.ConditionStrategy; import org.mapstruct.InjectionStrategy; import org.mapstruct.MappingInheritanceStrategy; import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ReportingPolicy; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; @@ -67,6 +69,12 @@ public void injectionStrategyGemIsCorrect() { namesOf( InjectionStrategyGem.values() ) ); } + @Test + public void conditionStrategyGemIsCorrect() { + assertThat( namesOf( ConditionStrategy.values() ) ).isEqualTo( + namesOf( ConditionStrategyGem.values() ) ); + } + private static List namesOf(Enum[] values) { return Stream.of( values ) .map( Enum::name ) From b33942a0104af4b72554858ae2a21934ca759bc5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 29 Apr 2024 08:13:46 +0200 Subject: [PATCH 028/214] #3561 Add test case --- .../ap/test/bugs/_3561/Issue3561Mapper.java | 60 +++++++++++++++++++ .../ap/test/bugs/_3561/Issue3561Test.java | 35 +++++++++++ 2 files changed, 95 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java new file mode 100644 index 0000000000..fd1137137b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java @@ -0,0 +1,60 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3561; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3561Mapper { + + Issue3561Mapper INSTANCE = Mappers.getMapper( Issue3561Mapper.class ); + + @Mapping(target = "value", conditionQualifiedByName = "shouldMapValue") + Target map(Source source, @Context boolean shouldMapValue); + + @Condition + @Named("shouldMapValue") + default boolean shouldMapValue(@Context boolean shouldMapValue) { + return shouldMapValue; + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private String value; + private boolean valueInitialized; + + public String getValue() { + if ( valueInitialized ) { + return value; + } + + throw new IllegalStateException( "value is not initialized" ); + } + + public void setValue(String value) { + this.valueInitialized = true; + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java new file mode 100644 index 0000000000..7cd0c26694 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3561; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3561Mapper.class) +@IssueKey("3561") +class Issue3561Test { + + @ProcessorTest + void shouldCorrectlyUseConditionWithContext() { + + Issue3561Mapper.Source source = new Issue3561Mapper.Source(); + + assertThatThrownBy( () -> Issue3561Mapper.INSTANCE.map( source, true ) ) + .isInstanceOf( IllegalStateException.class ) + .hasMessage( "value is not initialized" ); + + Issue3561Mapper.Target target = Issue3561Mapper.INSTANCE.map( source, false ); + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } +} From 9a5e6b1892036d6485fe29e2c45bc68314d1300b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 08:27:20 +0200 Subject: [PATCH 029/214] #3602 Automate release with JReleaser Add JReleaser for automating the release and add a step for automating the publishing of the website --- .github/scripts/update-website.sh | 76 ++++++++++++++++ .github/workflows/release.yml | 123 ++++++++++++++++++++++++++ NEXT_RELEASE_CHANGELOG.md | 29 ++++++ parent/pom.xml | 142 ++++++++++++++++++++++-------- pom.xml | 13 ++- 5 files changed, 345 insertions(+), 38 deletions(-) create mode 100644 .github/scripts/update-website.sh create mode 100644 .github/workflows/release.yml create mode 100644 NEXT_RELEASE_CHANGELOG.md diff --git a/.github/scripts/update-website.sh b/.github/scripts/update-website.sh new file mode 100644 index 0000000000..8fa989a1dd --- /dev/null +++ b/.github/scripts/update-website.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# +# Copyright MapStruct Authors. +# +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 +# + +# env vars: +# VERSION +# GH_BOT_EMAIL + +# This script has been inspired by the JReleaser update-website.sh (https://github.com/jreleaser/jreleaser/blob/main/.github/scripts/update-website.sh) +set -e + +function computePlainVersion() { + echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2.\3/' +} + +function computeMajorMinorVersion() { + echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2/' +} + +function isStable() { + local PLAIN_VERSION=$(computePlainVersion $1) + if [ "${PLAIN_VERSION}" == "$1" ]; then + echo "yes" + else + echo "no" + fi +} + +STABLE=$(isStable $VERSION) +MAJOR_MINOR_VERSION=$(computeMajorMinorVersion $VERSION) + +DEV_VERSION=`grep devVersion config.toml | sed 's/.*"\(.*\)"/\1/'` +MAJOR_MINOR_DEV_VERSION=$(computeMajorMinorVersion $DEV_VERSION) +STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'` +MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION) + +echo "📝 Updating versions" +sed -i '' -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml + +if [ "${STABLE}" == "yes" ]; then + sed -i '' -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml + if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then + echo "📝 Updating new stable version" + # This means that we have a new stable version and we need to change the order of the releases. + sed -i '' -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml + NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2)) + sed -i '' -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + fi +elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then + echo "📝 Updating new dev version" + # This means that we are updating for a new dev version, but the last dev version is not the one that we are doing. + # Therefore, we need to update add the new data configuration + cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml + sed -i '' -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml +fi + +sed -i '' -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed -i '' -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml +git add data/releases/${MAJOR_MINOR_VERSION}.toml +git add config.toml + +echo "📝 Updating distribution resources" +tar -xf tmp/mapstruct-${VERSION}-dist.tar.gz --directory tmp +rm -rf static/documentation/${MAJOR_MINOR_VERSION} +cp -R tmp/mapstruct-${VERSION}/docs static/documentation/${MAJOR_MINOR_VERSION} +mv static/documentation/${MAJOR_MINOR_VERSION}/reference/html/mapstruct-reference-guide.html static/documentation/${MAJOR_MINOR_VERSION}/reference/html/index.html +git add static/documentation/${MAJOR_MINOR_VERSION} + +git config --global user.email "${GH_BOT_EMAIL}" +git config --global user.name "GitHub Action" +git commit -a -m "Releasing version ${VERSION}" +git push diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..5e4cddbeed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,123 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + next: + description: 'Next version' + required: false + +env: + JAVA_VERSION: '11' + JAVA_DISTRO: 'zulu' + +jobs: + release: + # This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: ${{ vars.JAVA_VERSION }} + distribution: ${{ vars.JAVA_DISTRO }} + cache: maven + + - name: Set release version + id: version + run: | + RELEASE_VERSION=${{ github.event.inputs.version }} + NEXT_VERSION=${{ github.event.inputs.next }} + PLAIN_VERSION=`echo ${RELEASE_VERSION} | awk 'match($0, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/) { print substr($0, RSTART, RLENGTH); }'` + COMPUTED_NEXT_VERSION="${PLAIN_VERSION}-SNAPSHOT" + if [ -z $NEXT_VERSION ] + then + NEXT_VERSION=$COMPUTED_NEXT_VERSION + fi + ./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.name "GitHub Action" + git commit -a -m "Releasing version $RELEASE_VERSION" + git push + echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV + echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_ENV + echo "PLAIN_VERSION=$PLAIN_VERSION" >> $GITHUB_ENV + + - name: Stage + run: | + export GPG_TTY=$(tty) + ./mvnw -ntp -B --file pom.xml \ + -Dmaven.site.skip=true -Drelease=true -Ppublication,stage + + - name: Release + env: + JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + run: | + ./mvnw -ntp -B --file pom.xml -pl :mapstruct-parent -Pjreleaser jreleaser:release + + - name: JReleaser output + if: always() + uses: actions/upload-artifact@v4 + with: + name: jreleaser-release + path: | + parent/target/jreleaser/trace.log + parent/target/jreleaser/output.properties + + - name: Set next version + run: | + ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false + sed -i -e "s@project.build.outputTimestamp>.*\${git.commit.author.time} UTF-8 + + mapstruct/mapstruct + /tmp/repository + 1.8 + ${java.version} + ${java.version} + + ${git.commit.author.time} + 1.0.0.Alpha3 3.4.1 3.2.2 @@ -30,7 +39,7 @@ 8.36.1 5.10.1 2.2.0 - + 1.12.0 1 3.24.2 @@ -392,10 +401,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - 1.8 - 1.8 - org.apache.maven.plugins @@ -410,11 +415,6 @@ true - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - org.apache.maven.plugins maven-enforcer-plugin @@ -461,21 +461,6 @@ 8 - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - -DskipTests ${add.release.arguments} - clean install - false - true - @{project.version} - true - false - release - - org.apache.maven.plugins maven-resources-plugin @@ -522,6 +507,11 @@ + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + org.eclipse.m2e lifecycle-mapping @@ -663,6 +653,7 @@ maven-settings.xml readme.md CONTRIBUTING.md + NEXT_RELEASE_CHANGELOG.md .gitattributes .gitignore .factorypath @@ -791,12 +782,21 @@ + + org.codehaus.mojo + versions-maven-plugin + - release + publication + + + release + + @@ -823,18 +823,86 @@ + + + + + stage + + local::file:${maven.multiModuleProjectDirectory}/target/staging-deploy + + + deploy + + + + jreleaser + + - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - + org.jreleaser + jreleaser-maven-plugin + ${jreleaser.plugin.version} + + true + + + Mapstruct + + https://mapstruct.org/ + https://mapstruct.org/documentation/stable/reference/html/ + + + + ALWAYS + true + + + false + + + + + + ALWAYS + https://oss.sonatype.org/service/local + https://oss.sonatype.org/content/repositories/snapshots/ + true + true + ${maven.multiModuleProjectDirectory}/target/staging-deploy + + + org.mapstruct + mapstruct-jdk8 + false + false + + + + + + + + + {{projectVersion}} + {{projectVersion}} + + ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + + + + + + ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.tar.gz + + + ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.zip + + + + + diff --git a/pom.xml b/pom.xml index 25a5ead6bc..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,6 @@ core core-jdk8 processor - integrationtest true @@ -71,5 +70,17 @@ distribution + + test + + + release + !true + + + + integrationtest + + From 8e53b4181fb76d992a5902d70afa5136768d4a41 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 08:47:02 +0200 Subject: [PATCH 030/214] #3602 Fix setup-java action for release workflow --- .github/workflows/release.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e4cddbeed..6dfd75a6f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,6 @@ on: description: 'Next version' required: false -env: - JAVA_VERSION: '11' - JAVA_DISTRO: 'zulu' - jobs: release: # This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml) @@ -26,8 +22,8 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: ${{ vars.JAVA_VERSION }} - distribution: ${{ vars.JAVA_DISTRO }} + java-version: 11 + distribution: 'zulu' cache: maven - name: Set release version @@ -99,12 +95,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.GIT_WEBSITE_ACCESS_TOKEN }} - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: ${{ vars.JAVA_VERSION }} - distribution: ${{ vars.JAVA_DISTRO }} - - name: Download assets shell: bash run: | From 21a8b88a0f3cab99673d0bf94f11e5f731f00b90 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 07:01:58 +0000 Subject: [PATCH 031/214] Releasing version 1.6.0.Beta2 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..95be5f0b90 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..8830420981 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..7c8059144a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..5c4a656d75 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..be0d7efe59 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..3e0fa3ec09 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..7acf7d5952 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-05-11T07:01:58Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..ab4476277d 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..7ce9548edf 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml From 8a679b325d677ef93f7bda65c5a3da13b6aff524 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 07:10:47 +0000 Subject: [PATCH 032/214] Next version 1.6.0-SNAPSHOT --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 95be5f0b90..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 8830420981..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 7c8059144a..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 5c4a656d75..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index be0d7efe59..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 3e0fa3ec09..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 7acf7d5952..e4109325f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-05-11T07:01:58Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index ab4476277d..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 7ce9548edf..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml From baa02bf37725c2be8ca6ec8534170172c84583d9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 09:32:27 +0200 Subject: [PATCH 033/214] #3602 Fix path for update-website.sh scrip in release workflow --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6dfd75a6f2..493fd2ad0c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,5 +109,5 @@ jobs: VERSION: ${{ github.event.inputs.version }} GH_BOT_EMAIL: ${{ vars.GH_BOT_EMAIL }} run: | - chmod +x update-website.sh - ./update-website.sh + chmod +x tmp/update-website.sh + tmp/update-website.sh From babb9dedd9cf3c9c75aa7e4c96a7bd24c40ed927 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 30 Jun 2024 14:47:27 +0200 Subject: [PATCH 034/214] #3602 Doing a release should reset NEXT_RELEASE_CHANGELOG.md --- .github/workflows/release.yml | 4 ++++ NEXT_RELEASE_CHANGELOG.md | 19 ------------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 493fd2ad0c..36cabf44f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,10 @@ jobs: parent/target/jreleaser/trace.log parent/target/jreleaser/output.properties + - name: Reset NEXT_RELEASE_CHANGELOG.md + run: | + echo -e "### Features\n\n### Enhancements\n\n### Bugs\n\n### Documentation\n\n### Build\n" > NEXT_RELEASE_CHANGELOG.md + - name: Set next version run: | ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index ed3f0e9c56..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,29 +1,10 @@ ### Features -* Support conditional mapping for source parameters (#2610, #3459, #3270) -* Add `@SourcePropertyName` to handle a property name of the source object (#3323) - Currently only applicable for `@Condition` methods - ### Enhancements -* Improve error message for mapping to `target = "."` using expression (#3485) -* Improve error messages for auto generated mappings (#2788) -* Remove unnecessary casts to long (#3400) - ### Bugs -* `@Condition` cannot be used only with `@Context` parameters (#3561) -* `@Condition` treated as ambiguous mapping for methods returning Boolean/boolean (#3565) -* Subclass mapping warns about unmapped property that is mapped in referenced mapper (#3360) -* Interface inherited build method is not found (#3463) -* Bean with getter returning Stream is treating the Stream as an alternative setter (#3462) -* Using `Mapping#expression` and `Mapping#conditionalQualifiedBy(Name)` should lead to compile error (#3413) -* Defined mappings for subclass mappings with runtime exception subclass exhaustive strategy not working if result type is abstract class (#3331) - ### Documentation -* Clarify that `Mapping#ignoreByDefault` is inherited in nested mappings in documentation (#3577) - ### Build -* Improve tests to show that Lombok `@SuperBuilder` is supported (#3524) -* Add Java 21 CI matrix build (#3473) From 69371708ee9371bdaf4267529f45b9122df80c62 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 6 Jul 2024 10:31:32 +0200 Subject: [PATCH 035/214] #3574 Respect only explicit mappings but fail on unmapped source fields * #3574 Respect only explicit mappings but fail on unmapped source fields This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. Source properties are anyway ignored, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy --- NEXT_RELEASE_CHANGELOG.md | 4 ++++ .../ap/internal/model/BeanMappingMethod.java | 4 ---- ...neousSourceTargetMapperWithIgnoreByDefault.java} | 5 +++-- .../IgnoreByDefaultSourcesTest.java | 13 +++++++++++-- 4 files changed, 18 insertions(+), 8 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/{SourceTargetMapper.java => ErroneousSourceTargetMapperWithIgnoreByDefault.java} (74%) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..a6c424b08e 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -2,6 +2,10 @@ ### Enhancements +* Breaking change:g (#3574) - +This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. +Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy + ### Bugs ### Documentation diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index cf5179f9cb..b437afadc0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1764,10 +1764,6 @@ private ReportingPolicyGem getUnmappedSourcePolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyGem.IGNORE; } - // If we have ignoreByDefault = true, unprocessed source properties are not an issue. - if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { - return ReportingPolicyGem.IGNORE; - } return method.getOptions().getBeanMapping().unmappedSourcePolicy(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java index e029bd7391..459e2f426a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java @@ -14,8 +14,9 @@ @Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.ERROR) -public interface SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); +public interface ErroneousSourceTargetMapperWithIgnoreByDefault { + ErroneousSourceTargetMapperWithIgnoreByDefault INSTANCE = Mappers.getMapper( + ErroneousSourceTargetMapperWithIgnoreByDefault.class ); @Mapping(source = "one", target = "one") @BeanMapping(ignoreByDefault = true) diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java index 98f2cde4c0..fce6d49648 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java @@ -18,8 +18,17 @@ public class IgnoreByDefaultSourcesTest { @ProcessorTest - @WithClasses({ SourceTargetMapper.class, Source.class, Target.class }) - public void shouldSucceed() { + @WithClasses({ ErroneousSourceTargetMapperWithIgnoreByDefault.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapperWithIgnoreByDefault.class, + kind = Kind.ERROR, + line = 23, + message = "Unmapped source property: \"other\".") + } + ) + public void shouldRaiseErrorDueToNonIgnoredSourcePropertyWithBeanMappingIgnoreByDefault() { } @ProcessorTest From 037da5a1e1f450f670fa934640ee40ed67f0f781 Mon Sep 17 00:00:00 2001 From: Connor McGowan <53909969+cmcgowanprovidertrust@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:19:38 -0500 Subject: [PATCH 036/214] #3635 Fix documentation of unmappedSourcePolicy default (#3637) --- documentation/src/main/asciidoc/chapter-2-set-up.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 666954c4b1..58759ff8e0 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -261,7 +261,7 @@ Supported values are: If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence. If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option. -|`WARN` +|`IGNORE` |`mapstruct. disableBuilders` From 8fa2f40944b3a143a987a17448473eec2f78f076 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 15 Jul 2024 23:18:32 +0200 Subject: [PATCH 037/214] Enforce whitespaces around the for colon with CheckStyle (#3642) --- build-config/src/main/resources/build-config/checkstyle.xml | 4 +++- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 2 +- .../ap/test/conversion/_enum/EnumToIntegerConversionTest.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index a1ff4af23a..71a7d35899 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -146,7 +146,9 @@ - + + + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index b437afadc0..6535b07012 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -152,7 +152,7 @@ public Builder forgedMethod(ForgedMethod forgedMethod) { method( forgedMethod ); mappingReferences = forgedMethod.getMappingReferences(); Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); - for ( MappingReference mappingReference: mappingReferences.getMappingReferences() ) { + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { SourceReference sourceReference = mappingReference.getSourceReference(); if ( sourceReference != null ) { mappingReference.setSourceReference( new SourceReference.BuilderFromSourceReference() diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java index 1302c22bc9..67d99dfbc6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java @@ -30,7 +30,7 @@ public class EnumToIntegerConversionTest { public void shouldApplyEnumToIntegerConversion() { EnumToIntegerSource source = new EnumToIntegerSource(); - for ( EnumToIntegerEnum value: EnumToIntegerEnum.values() ) { + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { source.setEnumValue( value ); EnumToIntegerTarget target = EnumToIntegerMapper.INSTANCE.sourceToTarget( source ); From eef3bdfca4037735507ef3776568b49201ca4906 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:54:06 +0200 Subject: [PATCH 038/214] #3639 fix documentation link --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 2174822359..ed64580eb4 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -448,7 +448,7 @@ E.g. If an object factory exists for our `PersonBuilder` then this factory would [NOTE] ====== -Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See chapter `Mapping customization with before-mapping and after-mapping methods` for more information. +Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See <> for more information. ====== .Person with Builder example From 52877d36c2e227e639fbca43d268174f4fa68bc6 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 7 Jul 2024 13:49:52 +0200 Subject: [PATCH 039/214] #3634 fix typo in experimental note --- .../java/org/mapstruct/ap/spi/EnumMappingStrategy.java | 2 +- .../org/mapstruct/ap/spi/EnumTransformationStrategy.java | 2 +- .../org/mapstruct/ap/spi/MappingExclusionProvider.java | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java index 97088cd8c6..8ad6737c63 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java @@ -17,7 +17,7 @@ * * @since 1.4 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface EnumMappingStrategy { /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java index 2c498fc3a8..796bdb4954 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java @@ -13,7 +13,7 @@ * @author Filip Hrisafov * @since 1.4 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface EnumTransformationStrategy { /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java index afd7e69511..9dae81cda2 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -12,10 +12,10 @@ /** * A service provider interface that is used to control if MapStruct is allowed to generate automatic sub-mapping for * a given {@link TypeElement}. - * + *

    * When generating the implementation of a mapping method, MapStruct will apply the following routine for each * attribute pair in the source and target object: - * + *

    *

      *
    • If source and target attribute have the same type, the value will be simply copied from source to target. * If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target @@ -30,14 +30,14 @@ *
    • If MapStruct could not create a name based mapping method an error will be raised at build time, * indicating the non-mappable attribute and its path.
    • *
    - * + *

    * With this SPI the last step before raising an error can be controlled. i.e. A user can control whether MapStruct * is allowed to generate such automatic sub-mapping method (for the source or target type) or not. * * @author Filip Hrisafov * @since 1.2 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface MappingExclusionProvider { /** @@ -46,7 +46,6 @@ public interface MappingExclusionProvider { * The given {@code typeElement} will be excluded from the automatic sub-mapping generation * * @param typeElement that needs to be checked - * * @return {@code true} if MapStruct should exclude the provided {@link TypeElement} from an automatic sub-mapping */ boolean isExcluded(TypeElement typeElement); From 66f4288842460f27b88ba4580cd5384551127d61 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 13:53:39 +0200 Subject: [PATCH 040/214] #3601 Always use SourceParameterCondition when checking source parameter This is a breaking change, with this change whenever a source parameter is used as a source for a target property the condition has to apply to source parameters and not properties --- NEXT_RELEASE_CHANGELOG.md | 68 +++++++++ .../source/selector/SelectionCriteria.java | 129 ++++++++++++------ .../ap/test/bugs/_3601/Issue3601Mapper.java | 50 +++++++ .../ap/test/bugs/_3601/Issue3601Test.java | 47 +++++++ ...itionalMethodWithSourceToTargetMapper.java | 6 +- 5 files changed, 252 insertions(+), 48 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index a6c424b08e..305bdda947 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -8,7 +8,75 @@ Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` sho ### Bugs +* Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) + ### Documentation ### Build +## Breaking changes + +### Presence checks for source parameters + +In 1.6, support for presence checks on source parameters has been added. +This means that even if you want to map a source parameter directly to some target property the new `@SourceParameterCondition` or `@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)` should be used. + +e.g. + +If we had the following in 1.5: +```java +@Mapper +public interface OrderMapper { + + @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order map(OrderDTO dto); + + @Condition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO dto) { + return dto != null && dto.getCustomerName() != null; + } + +} +``` + +Them MapStruct would generate + +```java +public class OrderMapperImpl implements OrderMapper { + + @Override + public Order map(OrderDTO dto) { + if ( dto == null ) { + return null; + } + + Order order = new Order(); + + if ( mapCustomerFromOrder( dto ) ) { + order.setCustomer( orderDtoToCustomer( orderDTO ) ); + } + + return order; + } +} +``` + +In order for the same to be generated in 1.6, the mapper needs to look like this: + +```java +@Mapper +public interface OrderMapper { + + @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order map(OrderDTO dto); + + @SourceParameterCondition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO dto) { + return dto != null && dto.getCustomerName() != null; + } + +} +``` + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index fa5e1c29c0..ecc9ac9e01 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.model.source.selector; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -22,50 +21,31 @@ */ public class SelectionCriteria { - private final List qualifiers = new ArrayList<>(); - private final List qualifiedByNames = new ArrayList<>(); + private final QualifyingInfo qualifyingInfo; private final String targetPropertyName; - private final TypeMirror qualifyingResultType; private final SourceRHS sourceRHS; private boolean ignoreQualifiers = false; private Type type; - private final boolean allowDirect; - private final boolean allowConversion; - private final boolean allowMappingMethod; - private final boolean allow2Steps; + private final MappingControl mappingControl; public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl, String targetPropertyName, Type type) { - if ( selectionParameters != null ) { - if ( type == Type.PRESENCE_CHECK ) { - qualifiers.addAll( selectionParameters.getConditionQualifiers() ); - qualifiedByNames.addAll( selectionParameters.getConditionQualifyingNames() ); - } - else { - qualifiers.addAll( selectionParameters.getQualifiers() ); - qualifiedByNames.addAll( selectionParameters.getQualifyingNames() ); - } - qualifyingResultType = selectionParameters.getResultType(); - sourceRHS = selectionParameters.getSourceRHS(); - } - else { - this.qualifyingResultType = null; - sourceRHS = null; - } - if ( mappingControl != null ) { - this.allowDirect = mappingControl.allowDirect(); - this.allowConversion = mappingControl.allowTypeConversion(); - this.allowMappingMethod = mappingControl.allowMappingMethod(); - this.allow2Steps = mappingControl.allowBy2Steps(); - } - else { - this.allowDirect = true; - this.allowConversion = true; - this.allowMappingMethod = true; - this.allow2Steps = true; - } + this( + QualifyingInfo.fromSelectionParameters( selectionParameters ), + selectionParameters != null ? selectionParameters.getSourceRHS() : null, + mappingControl, + targetPropertyName, + type + ); + } + + private SelectionCriteria(QualifyingInfo qualifyingInfo, SourceRHS sourceRHS, MappingControl mappingControl, + String targetPropertyName, Type type) { + this.qualifyingInfo = qualifyingInfo; this.targetPropertyName = targetPropertyName; + this.sourceRHS = sourceRHS; this.type = type; + this.mappingControl = mappingControl; } /** @@ -109,11 +89,11 @@ public void setIgnoreQualifiers(boolean ignoreQualifiers) { } public List getQualifiers() { - return ignoreQualifiers ? Collections.emptyList() : qualifiers; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiers(); } public List getQualifiedByNames() { - return ignoreQualifiers ? Collections.emptyList() : qualifiedByNames; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiedByNames(); } public String getTargetPropertyName() { @@ -121,7 +101,7 @@ public String getTargetPropertyName() { } public TypeMirror getQualifyingResultType() { - return qualifyingResultType; + return qualifyingInfo.qualifyingResultType(); } public boolean isPreferUpdateMapping() { @@ -137,23 +117,23 @@ public void setPreferUpdateMapping(boolean preferUpdateMapping) { } public boolean hasQualfiers() { - return !qualifiedByNames.isEmpty() || !qualifiers.isEmpty(); + return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty(); } public boolean isAllowDirect() { - return allowDirect; + return mappingControl == null || mappingControl.allowDirect(); } public boolean isAllowConversion() { - return allowConversion; + return mappingControl == null || mappingControl.allowTypeConversion(); } public boolean isAllowMappingMethod() { - return allowMappingMethod; + return mappingControl == null || mappingControl.allowMappingMethod(); } public boolean isAllow2Steps() { - return allow2Steps; + return mappingControl == null || mappingControl.allowBy2Steps(); } public boolean isSelfAllowed() { @@ -181,7 +161,22 @@ public static SelectionCriteria forLifecycleMethods(SelectionParameters selectio } public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) { - return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); + SourceRHS sourceRHS = selectionParameters.getSourceRHS(); + Type type; + QualifyingInfo qualifyingInfo = new QualifyingInfo( + selectionParameters.getConditionQualifiers(), + selectionParameters.getConditionQualifyingNames(), + selectionParameters.getResultType() + ); + if ( sourceRHS != null && sourceRHS.isSourceReferenceParameter() ) { + // If the source reference is for a source parameter, + // then the presence check should be for the source parameter + type = Type.SOURCE_PARAMETER_CHECK; + } + else { + type = Type.PRESENCE_CHECK; + } + return new SelectionCriteria( qualifyingInfo, sourceRHS, null, null, type ); } public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { @@ -193,6 +188,50 @@ public static SelectionCriteria forSubclassMappingMethods(SelectionParameters se return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); } + private static class QualifyingInfo { + + private static final QualifyingInfo EMPTY = new QualifyingInfo( + Collections.emptyList(), + Collections.emptyList(), + null + ); + + private final List qualifiers; + private final List qualifiedByNames; + private final TypeMirror qualifyingResultType; + + private QualifyingInfo(List qualifiers, List qualifiedByNames, + TypeMirror qualifyingResultType) { + this.qualifiers = qualifiers; + this.qualifiedByNames = qualifiedByNames; + this.qualifyingResultType = qualifyingResultType; + } + + public List qualifiers() { + return qualifiers; + } + + public List qualifiedByNames() { + return qualifiedByNames; + } + + public TypeMirror qualifyingResultType() { + return qualifyingResultType; + } + + private static QualifyingInfo fromSelectionParameters(SelectionParameters selectionParameters) { + if ( selectionParameters == null ) { + return EMPTY; + } + return new QualifyingInfo( + selectionParameters.getQualifiers(), + selectionParameters.getQualifyingNames(), + selectionParameters.getResultType() + ); + } + } + + public enum Type { PREFER_UPDATE_MAPPING, OBJECT_FACTORY, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java new file mode 100644 index 0000000000..851e353de7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3601; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3601Mapper { + + Issue3601Mapper INSTANCE = Mappers.getMapper( Issue3601Mapper.class ); + + @Mapping(target = "currentId", source = "source.uuid") + @Mapping(target = "targetIds", source = "sourceIds") + Target map(Source source, List sourceIds); + + @SourceParameterCondition + default boolean isNotEmpty(List elements) { + return elements != null && !elements.isEmpty(); + } + + class Source { + private final String uuid; + + public Source(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + } + + class Target { + //CHECKSTYLE:OFF + public String currentId; + public List targetIds; + //CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java new file mode 100644 index 0000000000..b9b68fb583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3601; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3601") +class Issue3601Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( Issue3601Mapper.class ) + void shouldUseSourceParameterPresenceCheckCorrectly() { + Issue3601Mapper.Target target = Issue3601Mapper.INSTANCE.map( + new Issue3601Mapper.Source( "test1" ), + Collections.emptyList() + ); + + assertThat( target ).isNotNull(); + assertThat( target.currentId ).isEqualTo( "test1" ); + assertThat( target.targetIds ).isNull(); + + target = Issue3601Mapper.INSTANCE.map( + null, + Collections.emptyList() + ); + + assertThat( target ).isNull(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java index 35864fe62c..6577a6fd95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java @@ -5,10 +5,10 @@ */ package org.mapstruct.ap.test.conditional.qualifier; -import org.mapstruct.Condition; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; +import org.mapstruct.SourceParameterCondition; import org.mapstruct.factory.Mappers; /** @@ -29,13 +29,13 @@ public interface ConditionalMethodWithSourceToTargetMapper { Address convertToAddress(OrderDTO orderDTO); - @Condition + @SourceParameterCondition @Named("mapCustomerFromOrder") default boolean mapCustomerFromOrder(OrderDTO orderDTO) { return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); } - @Condition + @SourceParameterCondition @Named("mapAddressFromOrder") default boolean mapAddressFromOrder(OrderDTO orderDTO) { return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); From df49ce5ff93fce8c062263a8d56594e776dc0a3a Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:06:49 +0300 Subject: [PATCH 041/214] #3609 Pass bean mapping ignored unmapped source properties to subclass forged methods Co-authored-by: thunderhook <8238759+thunderhook@users.noreply.github.com> --- .../ap/internal/model/ForgedMethod.java | 21 ++++++++- .../model/source/BeanMappingOptions.java | 11 +++++ .../model/source/MappingMethodOptions.java | 13 ++++++ .../ap/test/bugs/_3609/Issue3609Mapper.java | 43 +++++++++++++++++++ .../ap/test/bugs/_3609/Issue3609Test.java | 23 ++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java index a33bc7520e..2aa12687d8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -146,13 +146,30 @@ public static ForgedMethod forSubclassMapping(String name, Type sourceType, Type basedOn, history, mappingReferences == null ? MappingReferences.empty() : mappingReferences, - forgedNameBased + forgedNameBased, + MappingMethodOptions.getSubclassForgedMethodInheritedOptions( basedOn.getOptions() ) ); } private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, boolean forgedNameBased) { + this( + name, + sourceType, + returnType, + additionalParameters, + basedOn, + history, + mappingReferences, + forgedNameBased, + MappingMethodOptions.getForgedMethodInheritedOptions( basedOn.getOptions() ) + ); + } + + private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, + Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, + boolean forgedNameBased, MappingMethodOptions options) { // establish name String sourceParamSafeName; @@ -185,7 +202,7 @@ private ForgedMethod(String name, Type sourceType, Type returnType, List Date: Sat, 20 Jul 2024 16:19:59 +0200 Subject: [PATCH 042/214] #3591 Fix duplicate method generation with recursive auto mapping --- .../internal/model/AbstractBaseBuilder.java | 30 ++++--- .../model/ContainerMappingMethod.java | 3 +- .../ap/internal/model/PropertyMapping.java | 9 +- .../model/source/BeanMappingOptions.java | 4 +- .../model/source/IterableMappingOptions.java | 13 ++- .../model/source/MapMappingOptions.java | 4 +- .../internal/model/source/MappingOptions.java | 2 +- .../model/source/SelectionParameters.java | 15 ++++ .../mapstruct/ap/test/bugs/_3591/Bean.java | 36 ++++++++ .../mapstruct/ap/test/bugs/_3591/BeanDto.java | 30 +++++++ .../ap/test/bugs/_3591/BeanMapper.java | 21 +++++ .../ap/test/bugs/_3591/ContainerBean.java | 47 ++++++++++ .../ap/test/bugs/_3591/ContainerBeanDto.java | 40 +++++++++ .../test/bugs/_3591/ContainerBeanMapper.java | 22 +++++ .../ap/test/bugs/_3591/Issue3591Test.java | 79 +++++++++++++++++ .../bugs/_3591/ContainerBeanMapperImpl.java | 85 +++++++++++++++++++ 16 files changed, 416 insertions(+), 24 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index ba13854667..311cc00368 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model; +import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.model.common.Assignment; @@ -74,16 +75,9 @@ private boolean isDisableSubMappingMethodsGeneration() { */ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) { - if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { - return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); - } - else { - ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); - } - - MappingMethod forgedMappingMethod; + Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { - forgedMappingMethod = new ValueMappingMethod.Builder() + forgedMappingMethodCreator = () -> new ValueMappingMethod.Builder() .method( forgedMethod ) .valueMappings( forgedMethod.getOptions().getValueMappings() ) .enumMapping( forgedMethod.getOptions().getEnumMappingOptions() ) @@ -91,15 +85,31 @@ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, .build(); } else { - forgedMappingMethod = new BeanMappingMethod.Builder() + forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() .forgedMethod( forgedMethod ) .returnTypeBuilder( builderType ) .mappingContext( ctx ) .build(); } + return getOrCreateForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethodCreator ); + } + + Assignment getOrCreateForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod, + Supplier mappingMethodCreator) { + + if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { + return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); + } + else { + ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); + } + + MappingMethod forgedMappingMethod = mappingMethodCreator.get(); + Assignment forgedAssignment = createForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethod ); ctx.getForgedMethodsUnderCreation().remove( forgedMethod ); + return forgedAssignment; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java index 7ecb7b0ed1..9e7e64fefa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -46,7 +46,8 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { afterMappingReferences ); this.elementAssignment = parameterAssignment; this.loopVariableName = loopVariableName; - this.selectionParameters = selectionParameters; + this.selectionParameters = selectionParameters != null ? selectionParameters : SelectionParameters.empty(); + this.index1Name = Strings.getSafeVariableName( "i", existingVariables ); this.index2Name = Strings.getSafeVariableName( "j", existingVariables ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 2f30957962..db14ccbd8e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.gem.BuilderGem; @@ -746,7 +747,7 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou targetType = targetType.withoutBounds(); ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "[]" ); - ContainerMappingMethod iterableMappingMethod = builder + Supplier mappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .selectionParameters( selectionParameters ) @@ -754,7 +755,7 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou .positionHint( positionHint ) .build(); - return createForgedAssignment( source, methodRef, iterableMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mappingMethodCreator ); } private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, String suffix) { @@ -772,12 +773,12 @@ private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS s ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); - MapMappingMethod mapMappingMethod = builder + Supplier mapMappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .build(); - return createForgedAssignment( source, methodRef, mapMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mapMappingMethodCreator ); } private Assignment forgeMapping(SourceRHS sourceRHS) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index a31000e8b4..bc19860bb5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -58,7 +58,7 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping, public static BeanMappingOptions forForgedMethods(BeanMappingOptions beanMapping) { BeanMappingOptions options = new BeanMappingOptions( beanMapping.selectionParameters != null ? - SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : null, + SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : SelectionParameters.empty(), Collections.emptyList(), beanMapping.beanMapping, beanMapping @@ -78,7 +78,7 @@ public static BeanMappingOptions forSubclassForgedMethods(BeanMappingOptions bea } public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) { - return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions ); + return new BeanMappingOptions( SelectionParameters.empty(), Collections.emptyList(), null, delegatingOptions ); } public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, MapperOptions mapperOptions, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java index 4affcd93e2..c4770efde6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -8,14 +8,14 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.gem.IterableMappingGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -34,7 +34,12 @@ public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, FormattingMessager messager, TypeUtils typeUtils) { if ( iterableMapping == null || !isConsistent( iterableMapping, method, messager ) ) { - IterableMappingOptions options = new IterableMappingOptions( null, null, null, mapperOptions ); + IterableMappingOptions options = new IterableMappingOptions( + null, + SelectionParameters.empty(), + null, + mapperOptions + ); return options; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java index ca8f1ea544..fd1758d8d2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -38,9 +38,9 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions if ( mapMapping == null || !isConsistent( mapMapping, method, messager ) ) { MapMappingOptions options = new MapMappingOptions( null, + SelectionParameters.empty(), null, - null, - null, + SelectionParameters.empty(), null, mapperOptions ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index e0994d746f..24a94137f1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -182,7 +182,7 @@ public static MappingOptions forIgnore(String targetName) { null, true, null, - null, + SelectionParameters.empty(), Collections.emptySet(), null, null, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java index 7237668e2c..4706d219cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java @@ -21,6 +21,16 @@ */ public class SelectionParameters { + private static final SelectionParameters EMPTY = new SelectionParameters( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + null, + null, + null + ); + private final List qualifiers; private final List qualifyingNames; private final List conditionQualifiers; @@ -225,4 +235,9 @@ public static SelectionParameters forSourceRHS(SourceRHS sourceRHS) { sourceRHS ); } + + public static SelectionParameters empty() { + return EMPTY; + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java new file mode 100644 index 0000000000..7da423c906 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.List; + +public class Bean { + private List beans; + private String value; + + public Bean() { + } + + public Bean(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java new file mode 100644 index 0000000000..00e70ff228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.List; + +public class BeanDto { + + private List beans; + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java new file mode 100644 index 0000000000..eeb54b4710 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(source = "beans", target = "beans") + BeanDto map(Bean bean, @MappingTarget BeanDto beanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java new file mode 100644 index 0000000000..b0e68ecfcf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBean { + + private String value; + private Map beanMap; + private Stream beanStream; + + public ContainerBean() { + } + + public ContainerBean(String value) { + this.value = value; + } + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java new file mode 100644 index 0000000000..86bb0193ac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBeanDto { + + private String value; + private Map beanMap; + private Stream beanStream; + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java new file mode 100644 index 0000000000..0da338aadf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ContainerBeanMapper { + + ContainerBeanMapper INSTANCE = Mappers.getMapper( ContainerBeanMapper.class ); + + @Mapping(source = "beanMap", target = "beanMap") + @Mapping(source = "beanStream", target = "beanStream") + ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, @MappingTarget ContainerBeanDto containerBeanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java new file mode 100644 index 0000000000..15bb191ffc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java @@ -0,0 +1,79 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3591") +class Issue3591Test { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + BeanDto.class, + Bean.class, + BeanMapper.class + }) + void mapNestedBeansWithMappingAnnotation() { + Bean bean = new Bean( "parent" ); + Bean child = new Bean( "child" ); + bean.setBeans( Collections.singletonList( child ) ); + + BeanDto beanDto = BeanMapper.INSTANCE.map( bean, new BeanDto() ); + + assertThat( beanDto ).isNotNull(); + assertThat( beanDto.getValue() ).isEqualTo( "parent" ); + assertThat( beanDto.getBeans() ) + .extracting( BeanDto::getValue ) + .containsExactly( "child" ); + } + + @ProcessorTest + @WithClasses({ + ContainerBean.class, + ContainerBeanDto.class, + ContainerBeanMapper.class, + }) + void shouldMapNestedMapAndStream() { + generatedSource.addComparisonToFixtureFor( ContainerBeanMapper.class ); + + ContainerBean containerBean = new ContainerBean( "parent" ); + Map beanMap = new HashMap<>(); + beanMap.put( "child", new ContainerBean( "mapChild" ) ); + containerBean.setBeanMap( beanMap ); + + Stream streamChild = Stream.of( new ContainerBean( "streamChild" ) ); + containerBean.setBeanStream( streamChild ); + + ContainerBeanDto dto = ContainerBeanMapper.INSTANCE.mapWithMapMapping( containerBean, new ContainerBeanDto() ); + + assertThat( dto ).isNotNull(); + + assertThat( dto.getBeanMap() ) + .extractingByKey( "child" ) + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "mapChild" ); + + assertThat( dto.getBeanStream() ) + .singleElement() + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "streamChild" ); + } + +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java new file mode 100644 index 0000000000..47baa689c8 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-05-25T14:23:23+0200", + comments = "version: , compiler: javac, environment: Java 17.0.11 (N/A)" +) +public class ContainerBeanMapperImpl implements ContainerBeanMapper { + + @Override + public ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, ContainerBeanDto containerBeanDto) { + if ( containerBean == null ) { + return containerBeanDto; + } + + if ( containerBeanDto.getBeanMap() != null ) { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.getBeanMap().clear(); + containerBeanDto.getBeanMap().putAll( map ); + } + else { + containerBeanDto.setBeanMap( null ); + } + } + else { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.setBeanMap( map ); + } + } + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Stream containerBeanStreamToContainerBeanDtoStream(Stream stream) { + if ( stream == null ) { + return null; + } + + return stream.map( containerBean -> containerBeanToContainerBeanDto( containerBean ) ); + } + + protected ContainerBeanDto containerBeanToContainerBeanDto(ContainerBean containerBean) { + if ( containerBean == null ) { + return null; + } + + ContainerBeanDto containerBeanDto = new ContainerBeanDto(); + + containerBeanDto.setBeanMap( stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ) ); + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Map stringContainerBeanMapToStringContainerBeanDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = new LinkedHashMap( Math.max( (int) ( map.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + String key = entry.getKey(); + ContainerBeanDto value = containerBeanToContainerBeanDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} From e2edb1a0864b1a2b1a39341a7113a63f40650e92 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 14:04:47 +0200 Subject: [PATCH 043/214] #3504 Add example classes for the passing target type documentation --- .../chapter-5-data-type-conversions.asciidoc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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 30430fe97e..bc406cf0b0 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -401,6 +401,39 @@ When having a custom mapper hooked into the generated mapper with `@Mapper#uses( For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances. +e.g. + +.Example classes for the passing target type example +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Car { + + private Person owner; + // ... +} + +public class Person extends BaseEntity { + + // ... +} + +public class Reference { + + private String pk; + // ... +} + +public class CarDto { + + private Reference owner; + // ... +} +---- +==== + + .Mapping method expecting mapping target type as parameter ==== [source, java, linenums] From 5ce9c537e963709c33f0fa830b6c1da8653bc885 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 16:27:04 +0200 Subject: [PATCH 044/214] Add release notes --- NEXT_RELEASE_CHANGELOG.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 305bdda947..39f23af6fc 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,19 +1,26 @@ -### Features - ### Enhancements -* Breaking change:g (#3574) - +* Breaking change: (#3574) - This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy ### Bugs * Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) +* Fix `@SubclassMapping` not working with `@BeanMapping#ignoreUnmappedSourceProperties` (#3609) +* Fix duplicate method generation with recursive auto mapping (#3591) ### Documentation +* Fix documentation of `unmappedSourcePolicy` default value (#3635) +* Fix documentation link of before and after mapping when using builders (#3639) +* Fix typo in experimental note (#3634) +* Add example classes for the passing target type documentation (#3504) + ### Build +* Enforce whitespaces around the for colon with CheckStyle (#3642) + ## Breaking changes ### Presence checks for source parameters From bbb9bb403c93e1c938907523ce9fcdeb67c2819c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 17:20:31 +0200 Subject: [PATCH 045/214] Fix typo in changelog --- NEXT_RELEASE_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 39f23af6fc..f08c6a2fd9 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -47,7 +47,7 @@ public interface OrderMapper { } ``` -Them MapStruct would generate +Then MapStruct would generate ```java public class OrderMapperImpl implements OrderMapper { From 6ef64ea3aa8320c52a924375d3bc6edf60f0c86d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:36:11 +0000 Subject: [PATCH 046/214] Releasing version 1.6.0.RC1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..ff56bd7953 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..d2806fa413 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..478ef7308f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..4bd4bc5dc4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..d98933a414 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..10850bc821 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..2b3b8e65a6 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-07-20T15:36:10Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..571623372b 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..e33e84ba65 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml From 6365a606c1b7f2c8d2dfb1583c74d46fb122eb1d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:45:11 +0000 Subject: [PATCH 047/214] Next version 1.6.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 83 +-------------------------------------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 +- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 12 insertions(+), 91 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index f08c6a2fd9..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,89 +1,10 @@ -### Enhancements +### Features -* Breaking change: (#3574) - -This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. -Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy +### Enhancements ### Bugs -* Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) -* Fix `@SubclassMapping` not working with `@BeanMapping#ignoreUnmappedSourceProperties` (#3609) -* Fix duplicate method generation with recursive auto mapping (#3591) - ### Documentation -* Fix documentation of `unmappedSourcePolicy` default value (#3635) -* Fix documentation link of before and after mapping when using builders (#3639) -* Fix typo in experimental note (#3634) -* Add example classes for the passing target type documentation (#3504) - ### Build -* Enforce whitespaces around the for colon with CheckStyle (#3642) - -## Breaking changes - -### Presence checks for source parameters - -In 1.6, support for presence checks on source parameters has been added. -This means that even if you want to map a source parameter directly to some target property the new `@SourceParameterCondition` or `@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)` should be used. - -e.g. - -If we had the following in 1.5: -```java -@Mapper -public interface OrderMapper { - - @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") - Order map(OrderDTO dto); - - @Condition - @Named("mapCustomerFromOrder") - default boolean mapCustomerFromOrder(OrderDTO dto) { - return dto != null && dto.getCustomerName() != null; - } - -} -``` - -Then MapStruct would generate - -```java -public class OrderMapperImpl implements OrderMapper { - - @Override - public Order map(OrderDTO dto) { - if ( dto == null ) { - return null; - } - - Order order = new Order(); - - if ( mapCustomerFromOrder( dto ) ) { - order.setCustomer( orderDtoToCustomer( orderDTO ) ); - } - - return order; - } -} -``` - -In order for the same to be generated in 1.6, the mapper needs to look like this: - -```java -@Mapper -public interface OrderMapper { - - @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") - Order map(OrderDTO dto); - - @SourceParameterCondition - @Named("mapCustomerFromOrder") - default boolean mapCustomerFromOrder(OrderDTO dto) { - return dto != null && dto.getCustomerName() != null; - } - -} -``` - diff --git a/build-config/pom.xml b/build-config/pom.xml index ff56bd7953..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index d2806fa413..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 478ef7308f..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4bd4bc5dc4..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d98933a414..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 10850bc821..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 2b3b8e65a6..e4109325f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-07-20T15:36:10Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 571623372b..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e33e84ba65..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml From 0f24633d04c0a568ab879e629a4720f37a51616b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 17:56:48 +0200 Subject: [PATCH 048/214] Fix update website script to be able to run Linux --- .github/scripts/update-website.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/scripts/update-website.sh b/.github/scripts/update-website.sh index 8fa989a1dd..7c92b8b43b 100644 --- a/.github/scripts/update-website.sh +++ b/.github/scripts/update-website.sh @@ -38,16 +38,22 @@ STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'` MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION) echo "📝 Updating versions" -sed -i '' -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml + +SEDOPTION="-i" +if [[ "$OSTYPE" == "darwin"* ]]; then + SEDOPTION="-i ''" +fi + +sed $SEDOPTION -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml if [ "${STABLE}" == "yes" ]; then - sed -i '' -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml + sed $SEDOPTION -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then echo "📝 Updating new stable version" # This means that we have a new stable version and we need to change the order of the releases. - sed -i '' -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2)) - sed -i '' -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml fi elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then @@ -55,11 +61,11 @@ elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then # This means that we are updating for a new dev version, but the last dev version is not the one that we are doing. # Therefore, we need to update add the new data configuration cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml - sed -i '' -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml fi -sed -i '' -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml -sed -i '' -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed $SEDOPTION -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed $SEDOPTION -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml git add data/releases/${MAJOR_MINOR_VERSION}.toml git add config.toml From 81ca739040022ab06991cf3d1d4d12fb8fa68e5b Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 21 Jul 2024 22:59:41 +0200 Subject: [PATCH 049/214] #3638 Remove deprecation note of enum mapping via @Mapping --- core/src/main/java/org/mapstruct/Mapping.java | 5 +---- .../src/main/asciidoc/chapter-8-mapping-values.asciidoc | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 42384152d0..8b0c4adb0c 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -20,7 +20,7 @@ import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; /** - * Configures the mapping of one bean attribute or enum constant. + * Configures the mapping of one bean attribute. *

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

    * - * IMPORTANT NOTE: the enum mapping capability is deprecated and replaced by {@link ValueMapping} it - * will be removed in subsequent versions. - * * @author Gunnar Morling */ diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc index 965479541e..fcb353010d 100644 --- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -189,12 +189,6 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper { ---- ==== - -[WARNING] -==== -The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead. -==== - === Mapping enum-to-String or String-to-enum MapStruct supports enum to a String mapping along the same lines as is described in <>. There are similarities and differences: From 38ec5c53350905fb8902935306b42fd1845c40c0 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:59:31 +0000 Subject: [PATCH 050/214] Releasing version 1.6.0 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..3848c6f465 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..bc88fdbb71 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..f002c4dd28 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..a44578dc47 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..f82f10d339 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..4ee4319a23 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..a47b1fad15 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-08-12T20:59:31Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..361be72a33 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..589e37007e 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml From 96d06984171f56ac6a910c144fd8a30cd42f1e3d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:08:07 +0000 Subject: [PATCH 051/214] Next version 1.7.0-SNAPSHOT --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 3848c6f465..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index bc88fdbb71..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index f002c4dd28..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index a44578dc47..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index f82f10d339..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4ee4319a23..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index a47b1fad15..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-08-12T20:59:31Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 361be72a33..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 589e37007e..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml From b452d7f2c8cc48c384d61b87ad530d2f9c882fa1 Mon Sep 17 00:00:00 2001 From: Stefan Simon <35351956+Hypnagokali@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:46:35 +0200 Subject: [PATCH 052/214] #3652 Inverse Inheritance should be possible for ignore-mappings without source --- NEXT_RELEASE_CHANGELOG.md | 2 ++ .../internal/model/source/MappingOptions.java | 5 ++- .../org/mapstruct/ap/test/bugs/_3652/Bar.java | 30 ++++++++++++++++ .../org/mapstruct/ap/test/bugs/_3652/Foo.java | 21 +++++++++++ .../ap/test/bugs/_3652/FooBarConfig.java | 24 +++++++++++++ .../ap/test/bugs/_3652/FooBarMapper.java | 21 +++++++++++ .../ap/test/bugs/_3652/Issue3652Test.java | 35 +++++++++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..b4a6b4a75b 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -4,6 +4,8 @@ ### Bugs +* Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) + ### Documentation ### Build diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 24a94137f1..22f9ccdc2f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -479,13 +479,12 @@ public MappingControl getMappingControl(ElementUtils elementUtils) { } /** - * mapping can only be inversed if the source was not a constant nor an expression nor a nested property - * and the mapping is not a 'target-source-ignore' mapping + * Mapping can only be inversed if the source was not a constant nor an expression * * @return true when the above applies */ public boolean canInverse() { - return constant == null && javaExpression == null && !( isIgnored && sourceName == null ); + return constant == null && javaExpression == null; } public MappingOptions copyForInverseInheritance(SourceMethod templateMethod, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java new file mode 100644 index 0000000000..3ac8b595ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3652; + +public class Bar { + + private int secret; + private int doesNotExistInFoo; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + + public int getDoesNotExistInFoo() { + return doesNotExistInFoo; + } + + public void setDoesNotExistInFoo(int doesNotExistInFoo) { + this.doesNotExistInFoo = doesNotExistInFoo; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java new file mode 100644 index 0000000000..02b5b6e8b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3652; + +public class Foo { + + private int secret; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java new file mode 100644 index 0000000000..3cf19dfbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3652; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; + +@MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG) +public interface FooBarConfig { + + @Mapping(target = "doesNotExistInFoo", ignore = true) + @Mapping(target = "secret", ignore = true) + Bar toBar(Foo foo); + + @InheritInverseConfiguration(name = "toBar") + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java new file mode 100644 index 0000000000..d58be74f1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3652; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(config = FooBarConfig.class) +public interface FooBarMapper { + + FooBarMapper INSTANCE = Mappers.getMapper( FooBarMapper.class ); + + Bar toBar(Foo foo); + + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java new file mode 100644 index 0000000000..aa8a64ada8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3652; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3652") +public class Issue3652Test { + + @WithClasses({ + Bar.class, + Foo.class, + FooBarConfig.class, + FooBarMapper.class, + }) + @ProcessorTest + void ignoreMappingsWithoutSourceShouldBeInvertible() { + Bar bar = new Bar(); + bar.setSecret( 123 ); + bar.setDoesNotExistInFoo( 6 ); + + Foo foo = FooBarMapper.INSTANCE.toFoo( bar ); + + assertThat( foo.getSecret() ).isEqualTo( 0 ); + } + +} From 60cd0a442026a3385385dbfb0fc8cd43289992fa Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Aug 2024 11:27:52 +0200 Subject: [PATCH 053/214] #3670 Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` --- NEXT_RELEASE_CHANGELOG.md | 1 + .../NestedTargetPropertyMappingHolder.java | 8 +- .../ap/test/bugs/_3670/Issue3670Mapper.java | 74 +++++++++++++++++++ .../ap/test/bugs/_3670/Issue3670Test.java | 22 ++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index b4a6b4a75b..a3ea4e5026 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -5,6 +5,7 @@ ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) +* Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) ### Documentation diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 20825c2dd9..92da1f4509 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -362,7 +362,13 @@ private GroupedTargetReferences groupByTargetReferences( ) { Map> singleTargetReferences = new LinkedHashMap<>(); for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { TargetReference targetReference = mapping.getTargetReference(); - String property = first( targetReference.getPropertyEntries() ); + List propertyEntries = targetReference.getPropertyEntries(); + if ( propertyEntries.isEmpty() ) { + // This can happen if the target property is target = ".", + // this usually happens when doing a reverse mapping + continue; + } + String property = first( propertyEntries ); MappingReference newMapping = mapping.popTargetReference(); if ( newMapping != null ) { // group properties on current name. diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java new file mode 100644 index 0000000000..a014a7d52d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java @@ -0,0 +1,74 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3670; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3670Mapper { + + @Mapping(target = "name", source = ".", qualifiedByName = "nestedName") + Target map(Source source); + + @InheritInverseConfiguration + @Mapping(target = "nested.nestedName", source = "name") + Source map(Target target); + + @Named("nestedName") + default String mapNestedName(Source source) { + if ( source == null ) { + return null; + } + + Nested nested = source.getNested(); + + return nested != null ? nested.getNestedName() : null; + } + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Nested { + private String nestedName; + + public String getNestedName() { + return nestedName; + } + + public void setNestedName(String nestedName) { + this.nestedName = nestedName; + } + } + + class Source { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java new file mode 100644 index 0000000000..fc3929b685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3670; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3670") +@WithClasses(Issue3670Mapper.class) +class Issue3670Test { + + @ProcessorTest + void shouldCompile() { + } +} From 6c8a2e184bd0254d8fac8bc1e04382da56750a60 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 18 Aug 2024 23:47:35 +0200 Subject: [PATCH 054/214] #3667, #3673 MappingReference should custom MappingOption equality instead of the default only target name based one --- .../model/beanmapping/MappingReference.java | 32 ++++++++- .../ap/test/bugs/_3667/Issue3667Mapper.java | 22 ++++++ .../ap/test/bugs/_3667/Issue3667Test.java | 43 ++++++++++++ .../mapstruct/ap/test/bugs/_3667/Source.java | 51 ++++++++++++++ .../mapstruct/ap/test/bugs/_3667/Target.java | 32 +++++++++ .../mapstruct/ap/test/bugs/_3673/Animal.java | 45 ++++++++++++ .../org/mapstruct/ap/test/bugs/_3673/Cat.java | 19 ++++++ .../mapstruct/ap/test/bugs/_3673/Details.java | 19 ++++++ .../org/mapstruct/ap/test/bugs/_3673/Dog.java | 19 ++++++ .../bugs/_3673/Issue3673ConstantMapper.java | 25 +++++++ .../bugs/_3673/Issue3673ExpressionMapper.java | 25 +++++++ .../ap/test/bugs/_3673/Issue3673Test.java | 68 +++++++++++++++++++ 12 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index e2501ea952..d013a77a7a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -71,7 +71,37 @@ public boolean equals(Object o) { return false; } MappingReference that = (MappingReference) o; - return mapping.equals( that.mapping ); + if ( ".".equals( that.mapping.getTargetName() ) ) { + // target this will never be equal to any other target this or any other. + return false; + } + + if (!Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getConstant(), that.mapping.getConstant() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getJavaExpression(), that.mapping.getJavaExpression() ) ) { + return false; + } + + if ( sourceReference == null ) { + return that.sourceReference == null; + } + + if ( that.sourceReference == null ) { + return false; + } + + + if (!Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { + return false; + } + + return true; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java new file mode 100644 index 0000000000..d4f2dff0af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3667; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3667Mapper { + + Issue3667Mapper INSTANCE = Mappers.getMapper( Issue3667Mapper.class ); + + @Mapping(target = "nested.value", source = "nested.nested1.value") + Target mapFirst(Source source); + + @Mapping(target = "nested.value", source = "nested.nested2.value") + Target mapSecond(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java new file mode 100644 index 0000000000..1e91de00d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3667; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3667") +@WithClasses({ + Issue3667Mapper.class, + Source.class, + Target.class +}) +class Issue3667Test { + + @ProcessorTest + void shouldCorrectlyMapNestedProperty() { + Source source = new Source( + new Source.Nested( + new Source.NestedNested( "value1" ), + new Source.NestedNested( "value2" ) + ) + ); + + Target target1 = Issue3667Mapper.INSTANCE.mapFirst( source ); + Target target2 = Issue3667Mapper.INSTANCE.mapSecond( source ); + + assertThat( target1 ).isNotNull(); + assertThat( target1.getNested() ).isNotNull(); + assertThat( target1.getNested().getValue() ).isEqualTo( "value1" ); + + assertThat( target2 ).isNotNull(); + assertThat( target2.getNested() ).isNotNull(); + assertThat( target2.getNested().getValue() ).isEqualTo( "value2" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java new file mode 100644 index 0000000000..ede78edf0d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3667; + +public class Source { + + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + + private final NestedNested nested1; + private final NestedNested nested2; + + public Nested(NestedNested nested1, NestedNested nested2) { + this.nested1 = nested1; + this.nested2 = nested2; + } + + public NestedNested getNested1() { + return nested1; + } + + public NestedNested getNested2() { + return nested2; + } + } + + public static class NestedNested { + + private final String value; + + public NestedNested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java new file mode 100644 index 0000000000..e0c8d23296 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3667; + +public class Target { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java new file mode 100644 index 0000000000..9fe97e0e79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +public class Animal { + + private AnimalDetails details; + + public AnimalDetails getDetails() { + return details; + } + + public void setDetails(AnimalDetails details) { + this.details = details; + } + + public enum Type { + CAT, + DOG + } + + public static class AnimalDetails { + private Type type; + private String name; + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java new file mode 100644 index 0000000000..63e5391690 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +public class Cat { + + private final Details details; + + public Cat(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java new file mode 100644 index 0000000000..8526793015 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +public class Details { + + private final String name; + + public Details(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java new file mode 100644 index 0000000000..a021a5d579 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +public class Dog { + + private final Details details; + + public Dog(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java new file mode 100644 index 0000000000..d74a954f9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ConstantMapper { + + Issue3673ConstantMapper INSTANCE = Mappers.getMapper( Issue3673ConstantMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "DOG") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "CAT") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java new file mode 100644 index 0000000000..3aca966807 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ExpressionMapper { + + Issue3673ExpressionMapper INSTANCE = Mappers.getMapper( Issue3673ExpressionMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.DOG)") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.CAT)") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java new file mode 100644 index 0000000000..2d796b4670 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java @@ -0,0 +1,68 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3673; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3673") +@WithClasses({ + Cat.class, + Dog.class, + Details.class, + Animal.class +}) +class Issue3673Test { + + @ProcessorTest + @WithClasses(Issue3673ConstantMapper.class) + void shouldCorrectlyMapNestedPropertyConstant() { + + Animal cat = Issue3673ConstantMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ConstantMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } + + @ProcessorTest + @WithClasses(Issue3673ExpressionMapper.class) + void shouldCorrectlyMapNestedPropertyExpression() { + + Animal cat = Issue3673ExpressionMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ExpressionMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } +} From c89b616f8c213f3210f3b5d5a6f7560d665b145b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Aug 2024 12:22:37 +0200 Subject: [PATCH 055/214] #3668 Do not apply implicit mappings when using `SubclassExhaustiveStrategy#RUNTIME_EXCEPTION` and return type is abstract --- NEXT_RELEASE_CHANGELOG.md | 1 + .../ap/internal/model/BeanMappingMethod.java | 4 ++- .../mapstruct/ap/test/bugs/_3668/Child.java | 23 +++++++++++++ .../ap/test/bugs/_3668/ChildDto.java | 23 +++++++++++++ .../ap/test/bugs/_3668/ChildMapper.java | 31 +++++++++++++++++ .../ap/test/bugs/_3668/Issue3668Test.java | 30 +++++++++++++++++ .../mapstruct/ap/test/bugs/_3668/Parent.java | 33 +++++++++++++++++++ .../ap/test/bugs/_3668/ParentDto.java | 33 +++++++++++++++++++ .../ap/test/bugs/_3668/ParentMapper.java | 31 +++++++++++++++++ 9 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index a3ea4e5026..438f4e3a6f 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -5,6 +5,7 @@ ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) +* Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) * Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) ### Documentation diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 6535b07012..1bec5038c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -295,7 +295,9 @@ else if ( !method.isUpdateMethod() ) { } } - boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings(); + // If defined mappings should not be handled then we should not apply implicit mappings + boolean applyImplicitMappings = + shouldHandledDefinedMappings && !mappingReferences.isRestrictToDefinedMappings(); if ( applyImplicitMappings ) { applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java new file mode 100644 index 0000000000..3c10c4d470 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +public abstract class Child { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildA extends Child { } + + public static class ChildB extends Child { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java new file mode 100644 index 0000000000..458cc57a9a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +public abstract class ChildDto { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildDtoA extends ChildDto { } + + public static class ChildDtoB extends ChildDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java new file mode 100644 index 0000000000..c381a99fcd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ChildMapper { + + @SubclassMapping(target = Child.ChildA.class, source = ChildDto.ChildDtoA.class) + @SubclassMapping(target = Child.ChildB.class, source = ChildDto.ChildDtoB.class) + Child toEntity(ChildDto childDto); + + @SubclassMapping(target = ChildDto.ChildDtoA.class, source = Child.ChildA.class) + @SubclassMapping(target = ChildDto.ChildDtoB.class, source = Child.ChildB.class) + ChildDto toDto(Child child); + + Child.ChildA toEntity(ChildDto.ChildDtoA childDto); + + ChildDto.ChildDtoA toDto(Child.ChildA child); + + Child.ChildB toEntity(ChildDto.ChildDtoB childDto); + + ChildDto.ChildDtoB toDto(Child.ChildB child); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java new file mode 100644 index 0000000000..a35ac70dce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3668") +@WithClasses({ + Child.class, + ChildDto.class, + ChildMapper.class, + Parent.class, + ParentDto.class, + ParentMapper.class, +}) +class Issue3668Test { + + @ProcessorTest + void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java new file mode 100644 index 0000000000..900a7fa68b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +public abstract class Parent { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentA extends Parent { } + + public static class ParentB extends Parent { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java new file mode 100644 index 0000000000..f4736ceef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +public abstract class ParentDto { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentDtoA extends ParentDto { } + + public static class ParentDtoB extends ParentDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java new file mode 100644 index 0000000000..484ddbc930 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(uses = { ChildMapper.class }, subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ParentMapper { + + @SubclassMapping(target = Parent.ParentA.class, source = ParentDto.ParentDtoA.class) + @SubclassMapping(target = Parent.ParentB.class, source = ParentDto.ParentDtoB.class) + Parent toEntity(ParentDto parentDto); + + @SubclassMapping(target = ParentDto.ParentDtoA.class, source = Parent.ParentA.class) + @SubclassMapping(target = ParentDto.ParentDtoB.class, source = Parent.ParentB.class) + ParentDto toDto(Parent parent); + + Parent.ParentA toEntity(ParentDto.ParentDtoA parentDto); + + ParentDto.ParentDtoA toDto(Parent.ParentA parent); + + Parent.ParentB toEntity(ParentDto.ParentDtoB parentDto); + + ParentDto.ParentDtoB toDto(Parent.ParentB parent); + +} From 58dcb9d81387963caa2f74a8c4b836afaef471b9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 28 Aug 2024 11:56:21 +0200 Subject: [PATCH 056/214] Update latest version and remove some obsolete badges --- readme.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 21d820dc58..6e5debbb78 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,11 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.5.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) -[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.0-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.0) +[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) [![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=main)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amain+workflow%3ACI) [![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct/tree/main) -[![Gitter](https://img.shields.io/gitter/room/mapstruct/mapstruct.svg)](https://gitter.im/mapstruct/mapstruct-users) -[![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/context:java) -[![Total Alerts](https://img.shields.io/lgtm/alerts/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/alerts) * [What is MapStruct?](#what-is-mapstruct) * [Requirements](#requirements) @@ -68,7 +65,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.5.Final + 1.6.0 ... @@ -84,10 +81,10 @@ For Maven-based projects, add the following to your POM file in order to use Map org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 - 1.8 - 1.8 + 17 + 1<7/target> org.mapstruct @@ -114,10 +111,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.5.5.Final' + implementation 'org.mapstruct:mapstruct:1.6.0' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' // if you are using mapstruct in test code } ... ``` From c6010c917a6ee239c5c0d949b7460a6a7451df1f Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 31 Aug 2024 16:31:51 +0200 Subject: [PATCH 057/214] Fix typo in readme Maven plugin config --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6e5debbb78..c0f798af83 100644 --- a/readme.md +++ b/readme.md @@ -84,7 +84,7 @@ For Maven-based projects, add the following to your POM file in order to use Map 3.13.0 17 - 1<7/target> + 17 org.mapstruct From 1e89d7497b1327c91cf782b80cb484adfdf87192 Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:44:17 +0300 Subject: [PATCH 058/214] Fix method name typo (#3622) --- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 4 ++-- .../ap/internal/model/source/BeanMappingOptions.java | 2 +- .../ap/internal/processor/MapperCreationProcessor.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 1bec5038c4..4ccf2bb49a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -299,7 +299,7 @@ else if ( !method.isUpdateMethod() ) { boolean applyImplicitMappings = shouldHandledDefinedMappings && !mappingReferences.isRestrictToDefinedMappings(); if ( applyImplicitMappings ) { - applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault(); + applyImplicitMappings = beanMapping == null || !beanMapping.isIgnoredByDefault(); } if ( applyImplicitMappings ) { @@ -1699,7 +1699,7 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { return ReportingPolicyGem.IGNORE; } // If we have ignoreByDefault = true, unprocessed target properties are not an issue. - if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { + if ( method.getOptions().getBeanMapping().isIgnoredByDefault() ) { return ReportingPolicyGem.IGNORE; } if ( method.getOptions().getBeanMapping() != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index bc19860bb5..ac27dfff00 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -223,7 +223,7 @@ public SelectionParameters getSelectionParameters() { return selectionParameters; } - public boolean isignoreByDefault() { + public boolean isIgnoredByDefault() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::ignoreByDefault ) .map( GemValue::get ) .orElse( false ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 24dd528f6b..5fc1b07823 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -565,7 +565,7 @@ else if ( applicableReversePrototypeMethods.size() > 1 ) { } // @BeanMapping( ignoreByDefault = true ) - if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isignoreByDefault() ) { + if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isIgnoredByDefault() ) { mappingOptions.applyIgnoreAll( method, typeFactory, mappingContext.getMessager() ); } From 23f4802374ae8e4703e8f127d85e332bf2e682fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EC=84=9C?= <81108344+rlarltj@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:05:01 +0900 Subject: [PATCH 059/214] Fix method name typo (#3691) --- .../java/org/mapstruct/ap/internal/util/NativeTypesTest.java | 4 ++-- .../mapstruct/ap/test/accessibility/AccessibilityTest.java | 2 +- .../ap/test/accessibility/DefaultSourceTargetMapperIfc.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java index 9c68a1de45..cd96592839 100644 --- a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java @@ -122,7 +122,7 @@ public void testIntegerLiteralFromJLS() { .isNotNull(); // most negative int: dec / octal / int / binary - // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can disssapear (java8) + // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can dissapear (java8) // and the function will be true to what the compiler shows. assertThat( getLiteral( int.class.getCanonicalName(), "-2147483648" ) ).isNotNull(); assertThat( getLiteral( int.class.getCanonicalName(), "0x8000_0000" ) ).isNotNull(); @@ -177,7 +177,7 @@ public void testIntegerLiteralFromJLS() { * The following example shows other ways you can use the underscore in numeric literals: */ @Test - public void testFloatingPoingLiteralFromJLS() { + public void testFloatingPointLiteralFromJLS() { // The largest positive finite literal of type float is 3.4028235e38f. assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e38f" ) ).isNotNull(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java index d338cadedd..bab9ca9b71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java @@ -41,7 +41,7 @@ public void testGeneratedModifiersFromInterfaceAreCorrect() throws Exception { assertTrue( isDefault( defaultFromIfc.getModifiers() ) ); - assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSoureToTarget" ) ) ); + assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSourceToTarget" ) ) ); } private static Class loadForMapper(Class mapper) throws ClassNotFoundException { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java index 84e9aebbbc..027d60b9ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java @@ -12,5 +12,5 @@ */ @Mapper interface DefaultSourceTargetMapperIfc { - Target implicitlyPublicSoureToTarget(Source source); + Target implicitlyPublicSourceToTarget(Source source); } From 4d9894ba25ba4e17c76211409f951f4cce956b3e Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:26:48 +0300 Subject: [PATCH 060/214] #3113 Use LinkedHashSet, LinkedHashSet new factory methods for java >= 19 --- .../model/common/ImplementationType.java | 25 +- .../ap/internal/model/common/TypeFactory.java | 26 +- .../DefaultModelElementProcessorContext.java | 3 +- .../processor/DefaultVersionInformation.java | 7 + .../internal/version/VersionInformation.java | 2 + .../ap/internal/model/IterableCreation.ftl | 12 +- .../testutil/runner/CompilationRequest.java | 4 + .../ap/testutil/runner/GeneratedSource.java | 21 +- .../test/bugs/_1453/Issue1453MapperImpl.java | 139 ++++++++++ .../bugs/_3591/ContainerBeanMapperImpl.java | 85 ++++++ .../DomainDtoWithNcvsAlwaysMapperImpl.java | 214 +++++++++++++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 245 +++++++++++++++++ .../_913/DomainDtoWithNvmsNullMapperImpl.java | 248 +++++++++++++++++ .../DomainDtoWithPresenceCheckMapperImpl.java | 214 +++++++++++++++ .../SourceTargetMapperImpl.java | 259 ++++++++++++++++++ .../updatemethods/CompanyMapper1Impl.java | 120 ++++++++ 16 files changed, 1605 insertions(+), 19 deletions(-) create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java index 45b73c492f..af8c7ecfb3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java @@ -16,23 +16,34 @@ public class ImplementationType { private final Type type; private final boolean initialCapacityConstructor; private final boolean loadFactorAdjustment; + private final String factoryMethodName; - private ImplementationType(Type type, boolean initialCapacityConstructor, boolean loadFactorAdjustment) { + private ImplementationType( + Type type, + boolean initialCapacityConstructor, + boolean loadFactorAdjustment, + String factoryMethodName + ) { this.type = type; this.initialCapacityConstructor = initialCapacityConstructor; this.loadFactorAdjustment = loadFactorAdjustment; + this.factoryMethodName = factoryMethodName; } public static ImplementationType withDefaultConstructor(Type type) { - return new ImplementationType( type, false, false ); + return new ImplementationType( type, false, false, null ); } public static ImplementationType withInitialCapacity(Type type) { - return new ImplementationType( type, true, false ); + return new ImplementationType( type, true, false, null ); } public static ImplementationType withLoadFactorAdjustment(Type type) { - return new ImplementationType( type, true, true ); + return new ImplementationType( type, true, true, null ); + } + + public static ImplementationType withFactoryMethod(Type type, String factoryMethodName) { + return new ImplementationType( type, true, false, factoryMethodName ); } /** @@ -44,7 +55,7 @@ public static ImplementationType withLoadFactorAdjustment(Type type) { * @return a new implementation type with the given {@code type} */ public ImplementationType createNew(Type type) { - return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment ); + return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment, factoryMethodName ); } /** @@ -71,4 +82,8 @@ public boolean hasInitialCapacityConstructor() { public boolean isLoadFactorAdjustment() { return loadFactorAdjustment; } + + public String getFactoryMethodName() { + return factoryMethodName; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index f28e0c687e..d65070426b 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 @@ -38,12 +38,11 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Extractor; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.JavaStreamConstants; @@ -51,13 +50,16 @@ import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.RoundContext; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; +import static org.mapstruct.ap.internal.model.common.ImplementationType.withFactoryMethod; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoadFactorAdjustment; @@ -82,6 +84,8 @@ public class TypeFactory { sb.append( ')' ); return sb.toString(); }; + private static final String LINKED_HASH_SET_FACTORY_METHOD_NAME = "newLinkedHashSet"; + private static final String LINKED_HASH_MAP_FACTORY_METHOD_NAME = "newLinkedHashMap"; private final ElementUtils elementUtils; private final TypeUtils typeUtils; @@ -100,7 +104,8 @@ public class TypeFactory { private final boolean loggingVerbose; public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, - RoundContext roundContext, Map notToBeImportedTypes, boolean loggingVerbose) { + RoundContext roundContext, Map notToBeImportedTypes, boolean loggingVerbose, + VersionInformation versionInformation) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; @@ -118,11 +123,22 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( List.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); - implementationTypes.put( Set.class.getName(), withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) ); + boolean sourceVersionAtLeast19 = versionInformation.isSourceVersionAtLeast19(); + implementationTypes.put( + Set.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); implementationTypes.put( SortedSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); implementationTypes.put( NavigableSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); - implementationTypes.put( Map.class.getName(), withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) ); + implementationTypes.put( + Map.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); implementationTypes.put( SortedMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( NavigableMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index 51ea5dd786..8ac1cc067e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -62,7 +62,8 @@ public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvir messager, roundContext, notToBeImported, - options.isVerbose() + options.isVerbose(), + versionInformation ); this.options = options; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index c4baf1bf5f..055bfe6095 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -41,6 +41,7 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -53,6 +54,7 @@ public class DefaultVersionInformation implements VersionInformation { this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @Override @@ -80,6 +82,11 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast19() { + return sourceVersionAtLeast19; + } + @Override public boolean isEclipseJDTCompiler() { return eclipseJDT; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 94e3520ad0..5e1972fcae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -21,6 +21,8 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast19(); + boolean isEclipseJDTCompiler(); boolean isJavacCompiler(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl index d49397a985..d083bd113d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl @@ -11,13 +11,15 @@ <@includeModel object=factoryMethod targetType=resultType/> <#elseif enumSet> EnumSet.noneOf( <@includeModel object=enumSetElementType raw=true/>.class ) - <#else> - new - <#if resultType.implementationType??> - <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#elseif resultType.implementation??> + <#if resultType.implementation.factoryMethodName?? && ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize> + <@includeModel object=resultType.implementationType raw=true />.${resultType.implementation.factoryMethodName}( <@sizeForCreation /> ) <#else> - <@includeModel object=resultType/>() + new <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#else> + new <@includeModel object=resultType/>() + <#macro sizeForCreation> <@compress single_line=true> diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 60e9a30072..9f1b78caa0 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -76,4 +76,8 @@ public Map, Class> getServices() { public Collection getTestDependencies() { return testDependencies; } + + public Compiler getCompiler() { + return compiler; + } } 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 c0993ea098..18a53b4971 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 @@ -44,12 +44,15 @@ public class GeneratedSource implements BeforeTestExecutionCallback, AfterTestEx */ private ThreadLocal sourceOutputDir = new ThreadLocal<>(); + private Compiler compiler; + private List> fixturesFor = new ArrayList<>(); @Override public void beforeTestExecution(ExtensionContext context) throws Exception { CompilationRequest compilationRequest = context.getStore( NAMESPACE ) .get( context.getUniqueId() + "-compilationRequest", CompilationRequest.class ); + this.compiler = compilationRequest.getCompiler(); setSourceOutputDir( context.getStore( NAMESPACE ) .get( compilationRequest, CompilationCache.class ) .getLastSourceOutputDir() ); @@ -118,13 +121,13 @@ public JavaFileAssert forJavaFile(String path) { private void handleFixtureComparison() throws UnsupportedEncodingException { for ( Class fixture : fixturesFor ) { - String expectedFixture = FIXTURES_ROOT + getMapperName( fixture ); - URL expectedFile = getClass().getClassLoader().getResource( expectedFixture ); + String fixtureName = getMapperName( fixture ); + URL expectedFile = getExpectedResource( fixtureName ); if ( expectedFile == null ) { fail( String.format( "No reference file could be found for Mapper %s. You should create a file %s", fixture.getName(), - expectedFixture + FIXTURES_ROOT + fixtureName ) ); } else { @@ -135,4 +138,16 @@ private void handleFixtureComparison() throws UnsupportedEncodingException { } } + + private URL getExpectedResource( String fixtureName ) { + ClassLoader classLoader = getClass().getClassLoader(); + for ( int version = Runtime.version().feature(); version >= 11 && compiler != Compiler.ECLIPSE; version-- ) { + URL resource = classLoader.getResource( FIXTURES_ROOT + "/" + version + "/" + fixtureName ); + if ( resource != null ) { + return resource; + } + } + + return classLoader.getResource( FIXTURES_ROOT + fixtureName ); + } } diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java new file mode 100644 index 0000000000..78c3bea0d7 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java @@ -0,0 +1,139 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1453; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class Issue1453MapperImpl implements Issue1453Mapper { + + @Override + public AuctionDto map(Auction auction) { + if ( auction == null ) { + return null; + } + + AuctionDto auctionDto = new AuctionDto(); + + auctionDto.setPayments( paymentListToPaymentDtoList( auction.getPayments() ) ); + auctionDto.setOtherPayments( paymentListToPaymentDtoList( auction.getOtherPayments() ) ); + auctionDto.setMapPayments( paymentPaymentMapToPaymentDtoPaymentDtoMap( auction.getMapPayments() ) ); + auctionDto.setMapSuperPayments( paymentPaymentMapToPaymentDtoPaymentDtoMap( auction.getMapSuperPayments() ) ); + + return auctionDto; + } + + @Override + public List mapExtend(List auctions) { + if ( auctions == null ) { + return null; + } + + List list = new ArrayList( auctions.size() ); + for ( Auction auction : auctions ) { + list.add( map( auction ) ); + } + + return list; + } + + @Override + public List mapSuper(List auctions) { + if ( auctions == null ) { + return null; + } + + List list = new ArrayList( auctions.size() ); + for ( Auction auction : auctions ) { + list.add( map( auction ) ); + } + + return list; + } + + @Override + public Map mapExtend(Map auctions) { + if ( auctions == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( auctions.size() ); + + for ( java.util.Map.Entry entry : auctions.entrySet() ) { + AuctionDto key = map( entry.getKey() ); + AuctionDto value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map mapSuper(Map auctions) { + if ( auctions == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( auctions.size() ); + + for ( java.util.Map.Entry entry : auctions.entrySet() ) { + AuctionDto key = map( entry.getKey() ); + AuctionDto value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + protected PaymentDto paymentToPaymentDto(Payment payment) { + if ( payment == null ) { + return null; + } + + PaymentDto paymentDto = new PaymentDto(); + + paymentDto.setPrice( payment.getPrice() ); + + return paymentDto; + } + + protected List paymentListToPaymentDtoList(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList( list.size() ); + for ( Payment payment : list ) { + list1.add( paymentToPaymentDto( payment ) ); + } + + return list1; + } + + protected Map paymentPaymentMapToPaymentDtoPaymentDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + PaymentDto key = paymentToPaymentDto( entry.getKey() ); + PaymentDto value = paymentToPaymentDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java new file mode 100644 index 0000000000..af3d293f7d --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3591; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-05-25T14:23:23+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class ContainerBeanMapperImpl implements ContainerBeanMapper { + + @Override + public ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, ContainerBeanDto containerBeanDto) { + if ( containerBean == null ) { + return containerBeanDto; + } + + if ( containerBeanDto.getBeanMap() != null ) { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.getBeanMap().clear(); + containerBeanDto.getBeanMap().putAll( map ); + } + else { + containerBeanDto.setBeanMap( null ); + } + } + else { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.setBeanMap( map ); + } + } + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Stream containerBeanStreamToContainerBeanDtoStream(Stream stream) { + if ( stream == null ) { + return null; + } + + return stream.map( containerBean -> containerBeanToContainerBeanDto( containerBean ) ); + } + + protected ContainerBeanDto containerBeanToContainerBeanDto(ContainerBean containerBean) { + if ( containerBean == null ) { + return null; + } + + ContainerBeanDto containerBeanDto = new ContainerBeanDto(); + + containerBeanDto.setBeanMap( stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ) ); + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Map stringContainerBeanMapToStringContainerBeanDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + String key = entry.getKey(); + ContainerBeanDto value = containerBeanToContainerBeanDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java new file mode 100644 index 0000000000..d5cfd24723 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java @@ -0,0 +1,214 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-19T11:20:01+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class DomainDtoWithNcvsAlwaysMapperImpl implements DomainDtoWithNcvsAlwaysMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(DtoWithPresenceCheck source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + if ( source.hasStrings() ) { + List list = source.getStrings(); + domain.setStrings( new LinkedHashSet( list ) ); + } + if ( source.hasStrings() ) { + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + } + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + if ( source.hasStringsInitialized() ) { + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java new file mode 100644 index 0000000000..76de94378b --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -0,0 +1,245 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefaultMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(Dto source) { + + Domain domain = new Domain(); + + if ( source != null ) { + List list = source.getStrings(); + if ( list != null ) { + domain.setStrings( new LinkedHashSet( list ) ); + } + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return domain; + } + + @Override + public void update(Dto source, Domain target) { + + if ( source != null ) { + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( new LinkedHashSet() ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( new LinkedHashSet() ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + } + + @Override + public Domain updateWithReturn(Dto source, Domain target) { + + if ( source != null ) { + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( new LinkedHashSet() ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( new LinkedHashSet() ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return new LinkedHashSet(); + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java new file mode 100644 index 0000000000..c61c2c68e8 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java @@ -0,0 +1,248 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class DomainDtoWithNvmsNullMapperImpl implements DomainDtoWithNvmsNullMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(Dto source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + List list = source.getStrings(); + if ( list != null ) { + domain.setStrings( new LinkedHashSet( list ) ); + } + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(Dto source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( null ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( null ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( null ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( null ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(Dto source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( null ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( null ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( null ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( null ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java new file mode 100644 index 0000000000..6d01a91ec5 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java @@ -0,0 +1,214 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-19T11:03:59+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class DomainDtoWithPresenceCheckMapperImpl implements DomainDtoWithPresenceCheckMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(DtoWithPresenceCheck source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + if ( source.hasStrings() ) { + List list = source.getStrings(); + domain.setStrings( new LinkedHashSet( list ) ); + } + if ( source.hasStrings() ) { + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + } + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + if ( source.hasStringsInitialized() ) { + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..3f0bee535a --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java @@ -0,0 +1,259 @@ +/* + * Copyright MapStruct 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + if ( target.getFooListNoSetter() != null ) { + List list = sourceFoosToTargetFoos( source.getFooList() ); + if ( list != null ) { + target.getFooListNoSetter().addAll( list ); + } + } + + return target; + } + + @Override + public TargetFoo sourceFooToTargetFoo(SourceFoo sourceFoo) { + if ( sourceFoo == null ) { + return null; + } + + TargetFoo targetFoo = new TargetFoo(); + + targetFoo.setName( sourceFoo.getName() ); + + return targetFoo; + } + + @Override + public List sourceFoosToTargetFoos(List foos) { + if ( foos == null ) { + return null; + } + + List list = new ArrayList( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + list.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return list; + } + + @Override + public Set sourceFoosToTargetFoos(Set foos) { + if ( foos == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + set.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return set; + } + + @Override + public Collection sourceFoosToTargetFoos(Collection foos) { + if ( foos == null ) { + return null; + } + + Collection collection = new ArrayList( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + collection.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return collection; + } + + @Override + public Iterable sourceFoosToTargetFoos(Iterable foos) { + if ( foos == null ) { + return null; + } + + ArrayList iterable = new ArrayList(); + for ( SourceFoo sourceFoo : foos ) { + iterable.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return iterable; + } + + @Override + public void sourceFoosToTargetFoosUsingTargetParameter(List targetFoos, Iterable sourceFoos) { + if ( sourceFoos == null ) { + return; + } + + targetFoos.clear(); + for ( SourceFoo sourceFoo : sourceFoos ) { + targetFoos.add( sourceFooToTargetFoo( sourceFoo ) ); + } + } + + @Override + public Iterable sourceFoosToTargetFoosUsingTargetParameterAndReturn(Iterable sourceFoos, List targetFoos) { + if ( sourceFoos == null ) { + return targetFoos; + } + + targetFoos.clear(); + for ( SourceFoo sourceFoo : sourceFoos ) { + targetFoos.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return targetFoos; + } + + @Override + public SortedSet sourceFoosToTargetFooSortedSet(Collection foos) { + if ( foos == null ) { + return null; + } + + SortedSet sortedSet = new TreeSet(); + for ( SourceFoo sourceFoo : foos ) { + sortedSet.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return sortedSet; + } + + @Override + public NavigableSet sourceFoosToTargetFooNavigableSet(Collection foos) { + if ( foos == null ) { + return null; + } + + NavigableSet navigableSet = new TreeSet(); + for ( SourceFoo sourceFoo : foos ) { + navigableSet.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return navigableSet; + } + + @Override + public Map sourceFooMapToTargetFooMap(Map foos) { + if ( foos == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( foos.size() ); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public SortedMap sourceFooMapToTargetFooSortedMap(Map foos) { + if ( foos == null ) { + return null; + } + + SortedMap sortedMap = new TreeMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + sortedMap.put( key, value ); + } + + return sortedMap; + } + + @Override + public NavigableMap sourceFooMapToTargetFooNavigableMap(Map foos) { + if ( foos == null ) { + return null; + } + + NavigableMap navigableMap = new TreeMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + navigableMap.put( key, value ); + } + + return navigableMap; + } + + @Override + public ConcurrentMap sourceFooMapToTargetFooConcurrentMap(Map foos) { + if ( foos == null ) { + return null; + } + + ConcurrentMap concurrentMap = new ConcurrentHashMap( Math.max( (int) ( foos.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + concurrentMap.put( key, value ); + } + + return concurrentMap; + } + + @Override + public ConcurrentNavigableMap sourceFooMapToTargetFooConcurrentNavigableMap(Map foos) { + if ( foos == null ) { + return null; + } + + ConcurrentNavigableMap concurrentNavigableMap = new ConcurrentSkipListMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + concurrentNavigableMap.put( key, value ); + } + + return concurrentNavigableMap; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java new file mode 100644 index 0000000000..f34495cb28 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java @@ -0,0 +1,120 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.updatemethods; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class CompanyMapper1Impl implements CompanyMapper1 { + + private final DepartmentEntityFactory departmentEntityFactory = new DepartmentEntityFactory(); + + @Override + public void toCompanyEntity(UnmappableCompanyDto dto, CompanyEntity entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + if ( dto.getDepartment() != null ) { + if ( entity.getDepartment() == null ) { + entity.setDepartment( departmentEntityFactory.createDepartmentEntity() ); + } + unmappableDepartmentDtoToDepartmentEntity( dto.getDepartment(), entity.getDepartment() ); + } + else { + entity.setDepartment( null ); + } + } + + @Override + public void toInBetween(UnmappableDepartmentDto dto, DepartmentInBetween entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + } + + @Override + public void toDepartmentEntity(DepartmentInBetween dto, DepartmentEntity entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + } + + protected SecretaryEntity secretaryDtoToSecretaryEntity(SecretaryDto secretaryDto) { + if ( secretaryDto == null ) { + return null; + } + + SecretaryEntity secretaryEntity = new SecretaryEntity(); + + secretaryEntity.setName( secretaryDto.getName() ); + + return secretaryEntity; + } + + protected EmployeeEntity employeeDtoToEmployeeEntity(EmployeeDto employeeDto) { + if ( employeeDto == null ) { + return null; + } + + EmployeeEntity employeeEntity = new EmployeeEntity(); + + employeeEntity.setName( employeeDto.getName() ); + + return employeeEntity; + } + + protected Map secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + SecretaryEntity key = secretaryDtoToSecretaryEntity( entry.getKey() ); + EmployeeEntity value = employeeDtoToEmployeeEntity( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } + + protected void unmappableDepartmentDtoToDepartmentEntity(UnmappableDepartmentDto unmappableDepartmentDto, DepartmentEntity mappingTarget) { + if ( unmappableDepartmentDto == null ) { + return; + } + + mappingTarget.setName( unmappableDepartmentDto.getName() ); + if ( mappingTarget.getSecretaryToEmployee() != null ) { + Map map = secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap( unmappableDepartmentDto.getSecretaryToEmployee() ); + if ( map != null ) { + mappingTarget.getSecretaryToEmployee().clear(); + mappingTarget.getSecretaryToEmployee().putAll( map ); + } + else { + mappingTarget.setSecretaryToEmployee( null ); + } + } + else { + Map map = secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap( unmappableDepartmentDto.getSecretaryToEmployee() ); + if ( map != null ) { + mappingTarget.setSecretaryToEmployee( map ); + } + } + } +} From 5232df2707b2c396f6d8bf6453a9f292684ef0f3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Sep 2024 15:06:36 +0200 Subject: [PATCH 061/214] Try to stabilise MapMappingTest and CarMapperTest --- .../org/mapstruct/ap/test/collection/map/MapMappingTest.java | 2 ++ .../test/java/org/mapstruct/ap/test/complex/CarMapperTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index d0dd66d22b..1261525d4f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.collection.map.other.ImportedType; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -26,6 +27,7 @@ */ @WithClasses({ SourceTargetMapper.class, CustomNumberMapper.class, Source.class, Target.class, ImportedType.class }) @IssueKey("44") +@DefaultTimeZone("UTC") public class MapMappingTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java index 5eaf44765d..a1881a7613 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java @@ -11,6 +11,7 @@ import java.util.GregorianCalendar; import java.util.List; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.complex._target.CarDto; import org.mapstruct.ap.test.complex._target.PersonDto; import org.mapstruct.ap.test.complex.other.DateMapper; @@ -31,6 +32,7 @@ Category.class, DateMapper.class }) +@DefaultTimeZone("UTC") public class CarMapperTest { @ProcessorTest From 796dd9467486ec6ec91bdd919f663bde098896df Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Sep 2024 15:33:08 +0200 Subject: [PATCH 062/214] Update next release changelog with latest changes --- NEXT_RELEASE_CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 438f4e3a6f..19571780cd 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -2,11 +2,16 @@ ### Enhancements +* Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) + ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) * Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) * Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) +* Deep mapping with multiple mappings broken in 1.6.0 (#3667) +* Two different constants are ignored in 1.6.0 (#3673) +* Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) ### Documentation From 12c9c6c1f067fde585a5f1aa4e7327d2770c7ef8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 6 Sep 2024 16:35:37 +0300 Subject: [PATCH 063/214] Use email variable for GitHub Bot git email --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36cabf44f8..f18a12b813 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: NEXT_VERSION=$COMPUTED_NEXT_VERSION fi ./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.email "${{ vars.GH_BOT_EMAIL }}" git config --global user.name "GitHub Action" git commit -a -m "Releasing version $RELEASE_VERSION" git push From 2686e852b6bd85295f723f705450d401e99f560d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Sep 2024 01:17:45 +0200 Subject: [PATCH 064/214] #3661 Use correct type for the Record component read accessors --- .../itest/tests/MavenIntegrationTest.java | 7 +++ .../module-1/pom.xml | 22 +++++++++ .../records/module1/NestedInterface.java | 10 +++++ .../itest/records/module1/RootInterface.java | 10 +++++ .../records/module1/SourceNestedRecord.java | 11 +++++ .../records/module1/SourceRootRecord.java | 11 +++++ .../module-2/pom.xml | 30 +++++++++++++ .../module2/RecordInterfaceIssueMapper.java | 18 ++++++++ .../records/module2/TargetNestedRecord.java | 11 +++++ .../records/module2/TargetRootRecord.java | 11 +++++ .../itest/records/module2/RecordsTest.java | 26 +++++++++++ .../recordsCrossModuleInterfaceTest/pom.xml | 26 +++++++++++ .../itest/records/mapper/RecordsTest.java | 0 .../ap/internal/model/common/Type.java | 4 +- .../mapstruct/ap/internal/util/Filters.java | 33 +++++--------- .../util/accessor/ElementAccessor.java | 45 +++++++++++++++++++ .../internal/util/accessor/ReadAccessor.java | 12 ++++- ...cessor.java => RecordElementAccessor.java} | 7 +-- 18 files changed, 265 insertions(+), 29 deletions(-) create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml rename integrationtest/src/test/resources/recordsCrossModuleTest/mapper/{ => src}/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java (100%) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java rename processor/src/main/java/org/mapstruct/ap/internal/util/accessor/{FieldElementAccessor.java => RecordElementAccessor.java} (77%) 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 7e9175dd92..0bef2994f6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -131,6 +131,13 @@ void recordsTest() { void recordsCrossModuleTest() { } + @ProcessorTest(baseDir = "recordsCrossModuleInterfaceTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + @EnabledForJreRange(min = JRE.JAVA_17) + void recordsCrossModuleInterfaceTest() { + } + @ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml new file mode 100644 index 0000000000..72df10f62c --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + recordsCrossModuleInterfaceTest + org.mapstruct + 1.0.0 + + + records-cross-module-1 + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java new file mode 100644 index 0000000000..ffa53f88b1 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public interface NestedInterface { + String field(); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java new file mode 100644 index 0000000000..fb23ffe157 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public interface RootInterface { + NestedInterface nested(); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java new file mode 100644 index 0000000000..6a0ddb86af --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public record SourceNestedRecord( + String field +) implements NestedInterface { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java new file mode 100644 index 0000000000..151ad5208d --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public record SourceRootRecord( + SourceNestedRecord nested +) implements RootInterface { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml new file mode 100644 index 0000000000..5f42efd18e --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + + recordsCrossModuleInterfaceTest + org.mapstruct + 1.0.0 + + + records-cross-module-2 + + + + + org.mapstruct + records-cross-module-1 + 1.0.0 + + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java new file mode 100644 index 0000000000..a763359a98 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +import org.mapstruct.itest.records.module1.SourceRootRecord; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface RecordInterfaceIssueMapper { + + RecordInterfaceIssueMapper INSTANCE = Mappers.getMapper(RecordInterfaceIssueMapper.class); + + TargetRootRecord map(SourceRootRecord source); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java new file mode 100644 index 0000000000..d02a4b58e0 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +public record TargetNestedRecord( + String field +) { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java new file mode 100644 index 0000000000..09a69f1bf1 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +public record TargetRootRecord( + TargetNestedRecord nested +) { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java new file mode 100644 index 0000000000..5f7a99273a --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.records.module1.SourceRootRecord; +import org.mapstruct.itest.records.module1.SourceNestedRecord; + +public class RecordsTest { + + @Test + public void shouldMap() { + SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) ); + TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.nested() ).isNotNull(); + assertThat( target.nested().field() ).isEqualTo( "test" ); + } + +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml new file mode 100644 index 0000000000..120c849dca --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + recordsCrossModuleInterfaceTest + pom + + + module-1 + module-2 + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java similarity index 100% rename from integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java rename to integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 54305cffc1..6520834066 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -50,7 +50,7 @@ import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -1047,7 +1047,7 @@ private List getAlternativeTargetAccessors() { List setterMethods = getSetters(); List readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); // All the fields are also alternative accessors - readAccessors.addAll( filters.fieldsIn( getAllFields(), FieldElementAccessor::new ) ); + readAccessors.addAll( filters.fieldsIn( getAllFields(), ElementAccessor::new ) ); // there could be a read accessor (field or method) for a list/map that is not present as setter. // an accessor could substitute the setter in that case and act as setter. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 123f72832b..5f7fe74bf2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -39,22 +39,16 @@ public class Filters { private static final Method RECORD_COMPONENTS_METHOD; - private static final Method RECORD_COMPONENT_ACCESSOR_METHOD; static { Method recordComponentsMethod; - Method recordComponentAccessorMethod; try { recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" ); - recordComponentAccessorMethod = Class.forName( "javax.lang.model.element.RecordComponentElement" ) - .getMethod( "getAccessor" ); } - catch ( NoSuchMethodException | ClassNotFoundException e ) { + catch ( NoSuchMethodException e ) { recordComponentsMethod = null; - recordComponentAccessorMethod = null; } RECORD_COMPONENTS_METHOD = recordComponentsMethod; - RECORD_COMPONENT_ACCESSOR_METHOD = recordComponentAccessorMethod; } private final AccessorNamingUtils accessorNaming; @@ -89,25 +83,18 @@ public List recordComponentsIn(TypeElement typeElement) { } public Map recordAccessorsIn(Collection recordComponents) { - if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) { + if ( recordComponents.isEmpty() ) { return java.util.Collections.emptyMap(); } - try { - Map recordAccessors = new LinkedHashMap<>(); - for ( Element recordComponent : recordComponents ) { - ExecutableElement recordExecutableElement = - (ExecutableElement) RECORD_COMPONENT_ACCESSOR_METHOD.invoke( recordComponent ); - recordAccessors.put( - recordComponent.getSimpleName().toString(), - ReadAccessor.fromGetter( recordExecutableElement, getReturnType( recordExecutableElement ) ) - ); - } - - return recordAccessors; - } - catch ( IllegalAccessException | InvocationTargetException e ) { - return java.util.Collections.emptyMap(); + Map recordAccessors = new LinkedHashMap<>(); + for ( Element recordComponent : recordComponents ) { + recordAccessors.put( + recordComponent.getSimpleName().toString(), + ReadAccessor.fromRecordComponent( recordComponent ) + ); } + + return recordAccessors; } private TypeMirror getReturnType(ExecutableElement executableElement) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java new file mode 100644 index 0000000000..24e71cc85f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util.accessor; + +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} that wraps a {@link VariableElement}. + * + * @author Filip Hrisafov + */ +public class ElementAccessor extends AbstractAccessor { + + private final AccessorType accessorType; + + public ElementAccessor(VariableElement variableElement) { + this( variableElement, AccessorType.FIELD ); + } + + public ElementAccessor(Element element, AccessorType accessorType) { + super( element ); + this.accessorType = accessorType; + } + + @Override + public TypeMirror getAccessedType() { + return element.asType(); + } + + @Override + public String toString() { + return element.toString(); + } + + @Override + public AccessorType getAccessorType() { + return accessorType; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index a790a3361b..5177bfc75b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.util.accessor; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -17,7 +18,7 @@ public interface ReadAccessor extends Accessor { String getReadValueSource(); static ReadAccessor fromField(VariableElement variableElement) { - return new ReadDelegateAccessor( new FieldElementAccessor( variableElement ) ) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { @Override public String getReadValueSource() { return getSimpleName(); @@ -25,6 +26,15 @@ public String getReadValueSource() { }; } + static ReadAccessor fromRecordComponent(Element element) { + return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { + @Override + public String getReadValueSource() { + return getSimpleName() + "()"; + } + }; + } + static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java similarity index 77% rename from processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java rename to processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java index 5174900532..d163f462f9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.util.accessor; +import javax.lang.model.element.Element; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -13,9 +14,9 @@ * * @author Filip Hrisafov */ -public class FieldElementAccessor extends AbstractAccessor { +public class RecordElementAccessor extends AbstractAccessor { - public FieldElementAccessor(VariableElement element) { + public RecordElementAccessor(Element element) { super( element ); } @@ -31,7 +32,7 @@ public String toString() { @Override public AccessorType getAccessorType() { - return AccessorType.FIELD; + return AccessorType.GETTER; } } From 4c1df35ba676814d06336ddf036f4408d9e0526d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Sep 2024 23:06:56 +0200 Subject: [PATCH 065/214] #3703 Use include model instead of manually writing the type name for return type for afterMappingReferencesWithFinalizedReturnType --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../ap/test/bugs/_3703/Issue3703Mapper.java | 37 +++++++++++++++++++ .../ap/test/bugs/_3703/Issue3703Test.java | 24 ++++++++++++ .../ap/test/bugs/_3703/dto/Contact.java | 37 +++++++++++++++++++ .../ap/test/bugs/_3703/entity/Contact.java | 37 +++++++++++++++++++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 8fb5bfdef0..61d9cc1837 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -136,7 +136,7 @@ <#if finalizerMethod??> <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> - ${returnType.name} ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + <@includeModel object=returnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; <#list afterMappingReferencesWithFinalizedReturnType as callback> <#if callback_index = 0> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java new file mode 100644 index 0000000000..287e7655fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3703; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.bugs._3703.dto.Contact; + +@Mapper +public interface Issue3703Mapper { + + Contact map(org.mapstruct.ap.test.bugs._3703.entity.Contact contact); + + org.mapstruct.ap.test.bugs._3703.entity.Contact map(Contact contact); + + @AfterMapping + default void afterMapping(@MappingTarget Contact target, org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget Contact.Builder targetBuilder, + org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact target, Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact.Builder targetBuilder, + Contact contact) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java new file mode 100644 index 0000000000..ac5cae3959 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3703; + +import org.mapstruct.ap.test.bugs._3703.dto.Contact; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("3703") +@WithClasses({ + Contact.class, + org.mapstruct.ap.test.bugs._3703.entity.Contact.class, + Issue3703Mapper.class +}) +public class Issue3703Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java new file mode 100644 index 0000000000..a949864791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3703.dto; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java new file mode 100644 index 0000000000..31fde373fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3703.entity; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} From 3011dd77d7ee2de7a7a5d93d3e8e965bad971a43 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Sep 2024 10:28:20 +0200 Subject: [PATCH 066/214] #3678 before / after mapping for type using builder should only be kept if they are using the actual type in `@TargetType` or `@MappingTarget` --- .../ap/internal/model/BeanMappingMethod.java | 33 ++++- .../ap/test/bugs/_3678/Issue3678Mapper.java | 128 ++++++++++++++++++ .../ap/test/bugs/_3678/Issue3678Test.java | 50 +++++++ 3 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 4ccf2bb49a..e6ac5b5bcf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -408,9 +408,8 @@ else if ( !method.isUpdateMethod() ) { existingVariableNames ) ); - // remove methods without parameters as they are already being invoked - removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType ); - removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType ); } Map presenceChecksByParameter = new LinkedHashMap<>(); @@ -453,8 +452,32 @@ else if ( !sourceParameter.getType().isPrimitive() ) { ); } - private void removeMappingReferencesWithoutSourceParameters(List references) { - references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() ); + private void keepMappingReferencesUsingTarget(List references, Type type) { + references.removeIf( reference -> { + List bindings = reference.getParameterBindings(); + if ( bindings.isEmpty() ) { + return true; + } + for ( ParameterBinding binding : bindings ) { + if ( binding.isMappingTarget() ) { + if ( type.isAssignableTo( binding.getType() ) ) { + // If the mapping target matches the type then we need to keep this + return false; + } + } + else if ( binding.isTargetType() ) { + Type targetType = binding.getType(); + List targetTypeTypeParameters = targetType.getTypeParameters(); + if ( targetTypeTypeParameters.size() == 1 ) { + if ( type.isAssignableTo( targetTypeTypeParameters.get( 0 ) ) ) { + return false; + } + } + } + } + + return true; + } ); } private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java new file mode 100644 index 0000000000..374fbf931a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java @@ -0,0 +1,128 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3678; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3678Mapper { + + Issue3678Mapper INSTANCE = Mappers.getMapper( Issue3678Mapper.class ); + + @Mapping(target = "name", source = "sourceA.name") + @Mapping(target = "description", source = "sourceB.description") + Target map(SourceA sourceA, SourceB sourceB, @Context MappingContext context); + + @Mapping(target = "description", constant = "some description") + Target map(SourceA sourceA, @Context MappingContext context); + + class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeMappingSourceA(SourceA sourceA) { + invokedMethods.add( "beforeMappingSourceA" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceA sourceA) { + invokedMethods.add( "afterMappingSourceA" ); + } + + @BeforeMapping + public void beforeMappingSourceB(SourceB sourceB) { + invokedMethods.add( "beforeMappingSourceB" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceB sourceB) { + invokedMethods.add( "afterMappingSourceB" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } + } + + class SourceA { + + private final String name; + + public SourceA(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class SourceB { + + private final String description; + + public SourceB(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + final class Target { + + private final String name; + private final String description; + + private Target(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String name; + private String description; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Target build() { + return new Target( this.name, this.description ); + } + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java new file mode 100644 index 0000000000..25e7e21622 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3678; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3678") +@WithClasses(Issue3678Mapper.class) +public class Issue3678Test { + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForTwoSources() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" ); + Issue3678Mapper.INSTANCE.map( sourceA, sourceB, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "beforeMappingSourceB", + "afterMappingSourceA", + "afterMappingSourceB" + ); + } + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForSingleSource() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.INSTANCE.map( sourceA, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "afterMappingSourceA" + ); + } + +} From c36f9ae5d10baa1fbae63df8a51e937db0569c07 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Sep 2024 17:11:24 +0200 Subject: [PATCH 067/214] Prepare release notes for 1.6.1 --- NEXT_RELEASE_CHANGELOG.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 19571780cd..5337e70daf 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,5 +1,3 @@ -### Features - ### Enhancements * Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) @@ -12,8 +10,6 @@ * Deep mapping with multiple mappings broken in 1.6.0 (#3667) * Two different constants are ignored in 1.6.0 (#3673) * Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) - -### Documentation - -### Build - +* Fix cross module records with interfaces not recognizing accessors (#3661) +* `@AfterMapping` methods are called twice when using target with builder (#3678) +* Compile error when using `@AfterMapping` method with Builder and TargetObject (#3703) From 10d69878a197c1bff1e8743a3d056e36eda856d3 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:52:17 +0000 Subject: [PATCH 068/214] Releasing version 1.6.1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..3e347b5c93 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..ca326ef6f0 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..da17fa59dc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..405f489865 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..5044ed7689 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..96f86c6f0e 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..8c36b4b6ea 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-09-15T15:52:17Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..a0e55059da 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..e605c32560 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml From c74e62a94c4c420ec5aa52fc177c56ea0483c50b Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Sep 2024 16:01:43 +0000 Subject: [PATCH 069/214] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 17 ++++++----------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 16 insertions(+), 21 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 5337e70daf..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,15 +1,10 @@ -### Enhancements +### Features -* Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) +### Enhancements ### Bugs -* Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) -* Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) -* Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) -* Deep mapping with multiple mappings broken in 1.6.0 (#3667) -* Two different constants are ignored in 1.6.0 (#3673) -* Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) -* Fix cross module records with interfaces not recognizing accessors (#3661) -* `@AfterMapping` methods are called twice when using target with builder (#3678) -* Compile error when using `@AfterMapping` method with Builder and TargetObject (#3703) +### Documentation + +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index 3e347b5c93..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index ca326ef6f0..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index da17fa59dc..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 405f489865..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 5044ed7689..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 96f86c6f0e..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8c36b4b6ea..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-09-15T15:52:17Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index a0e55059da..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e605c32560..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml From a3b4139070a3111872c3d17e0cb4dbcbaf8f4bf1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Sep 2024 09:19:39 +0200 Subject: [PATCH 070/214] #3717 Fix ClassCastException when getting thrown types for a record accessor --- .../itest/records/nested/Address.java | 13 ++++++++++ .../itest/records/nested/CareProvider.java | 13 ++++++++++ .../itest/records/nested/CareProviderDto.java | 13 ++++++++++ .../records/nested/CareProviderMapper.java | 25 ++++++++++++++++++ .../records/nested/NestedRecordsTest.java | 26 +++++++++++++++++++ .../ap/internal/model/common/TypeFactory.java | 6 ++++- 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java new file mode 100644 index 0000000000..fb857e90ca --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record Address(String street, String city) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java new file mode 100644 index 0000000000..a0ce13c0ba --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record CareProvider(String externalId, Address address) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java new file mode 100644 index 0000000000..d7ce7229e9 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record CareProviderDto(String id, String street, String city) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java new file mode 100644 index 0000000000..89ed688976 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface CareProviderMapper { + + CareProviderMapper INSTANCE = Mappers.getMapper( CareProviderMapper.class ); + + @Mapping(target = "id", source = "externalId") + @Mapping(target = "street", source = "address.street") + @Mapping(target = "city", source = "address.city") + CareProviderDto map(CareProvider source); +} diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java new file mode 100644 index 0000000000..c8ccaf1a65 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class NestedRecordsTest { + + @Test + public void shouldMapRecord() { + CareProvider source = new CareProvider( "kermit", new Address( "Sesame Street", "New York" ) ); + CareProviderDto target = CareProviderMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.id() ).isEqualTo( "kermit" ); + assertThat( target.street() ).isEqualTo( "Sesame Street" ); + assertThat( target.city() ).isEqualTo( "New York" ); + } +} diff --git a/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 d65070426b..ceb190a332 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 @@ -491,7 +491,11 @@ public List getThrownTypes(Accessor accessor) { if (accessor.getAccessorType().isFieldAssignment()) { return new ArrayList<>(); } - return extractTypes( ( (ExecutableElement) accessor.getElement() ).getThrownTypes() ); + Element element = accessor.getElement(); + if ( element instanceof ExecutableElement ) { + return extractTypes( ( (ExecutableElement) element ).getThrownTypes() ); + } + return new ArrayList<>(); } private List extractTypes(List typeMirrors) { From 4fd22d6b267f845038786364f46f16c5b0903d0a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Sep 2024 09:54:30 +0200 Subject: [PATCH 071/214] Prepare release notes for 1.6.2 --- NEXT_RELEASE_CHANGELOG.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..dd5c9b2349 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,3 @@ -### Features - -### Enhancements - ### Bugs -### Documentation - -### Build - +* Regression from 1.6.1: ClassCastException when using records (#3717) From 212607b4470c9e0deb8b6ad1fed56d016d58aa08 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:55:31 +0000 Subject: [PATCH 072/214] Releasing version 1.6.2 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..fa50066316 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..648c193e39 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..37ea12be4d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..4539528a91 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..dfac83cca8 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..ab8dcb26b7 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..94f614ecbb 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-09-16T07:55:31Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..919e2d6460 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..e0052bf5d4 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml From 4e0d73db1d17c40d251d39812550206607a16824 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:06:43 +0000 Subject: [PATCH 073/214] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 9 ++++++++- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 18 insertions(+), 11 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index dd5c9b2349..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,3 +1,10 @@ +### Features + +### Enhancements + ### Bugs -* Regression from 1.6.1: ClassCastException when using records (#3717) +### Documentation + +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index fa50066316..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 648c193e39..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 37ea12be4d..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4539528a91..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index dfac83cca8..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index ab8dcb26b7..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94f614ecbb..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-09-16T07:55:31Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 919e2d6460..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e0052bf5d4..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml From 26c5bcd923c7206ad161e93237f841a79ca85efd Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 27 Sep 2024 09:15:17 +0200 Subject: [PATCH 074/214] Update readme with 1.6.2 --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index c0f798af83..7903ddeb84 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.0-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.0) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.2-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.2) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) @@ -65,7 +65,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.6.0 + 1.6.2 ... @@ -111,10 +111,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.6.0' + implementation 'org.mapstruct:mapstruct:1.6.2' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' // if you are using mapstruct in test code } ... ``` From 32f1fea7b50ab583b87e3634e25ac92752380682 Mon Sep 17 00:00:00 2001 From: Srimathi-S <75327301+Srimathi-S@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:22:52 +0530 Subject: [PATCH 075/214] #3370 Prevent stack overflow error for Immutables with custom builder --- .../ap/spi/DefaultBuilderProvider.java | 9 +- .../ap/spi/ImmutablesBuilderProvider.java | 27 +- .../bugs/_3370/Issue3370BuilderProvider.java | 25 ++ .../ap/test/bugs/_3370/Issue3370Test.java | 55 +++ .../ap/test/bugs/_3370/ItemMapper.java | 19 ++ .../test/bugs/_3370/domain/ImmutableItem.java | 316 ++++++++++++++++++ .../ap/test/bugs/_3370/domain/Item.java | 27 ++ .../ap/test/bugs/_3370/dto/ItemDTO.java | 26 ++ 8 files changed, 496 insertions(+), 8 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 6395364a35..92cd1ed80d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -176,6 +176,10 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { * @throws MoreThanOneBuilderCreationMethodException if there are multiple builder creation methods */ protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + return findBuilderInfo( typeElement, true ); + } + + protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkParent) { if ( shouldIgnore( typeElement ) ) { return null; } @@ -203,7 +207,10 @@ else if ( builderInfo.size() > 1 ) { throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); } - return findBuilderInfo( typeElement.getSuperclass() ); + if ( checkParent ) { + return findBuilderInfo( typeElement.getSuperclass() ); + } + return null; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java index d4ccd029ef..3431808418 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java @@ -35,18 +35,31 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement) { if ( name.length() == 0 || JAVA_JAVAX_PACKAGE.matcher( name ).matches() ) { return null; } + + // First look if there is a builder defined in my own type + BuilderInfo info = findBuilderInfo( typeElement, false ); + if ( info != null ) { + return info; + } + + // Check for a builder in the generated immutable type + BuilderInfo immutableInfo = findBuilderInfoForImmutables( typeElement ); + if ( immutableInfo != null ) { + return immutableInfo; + } + + return super.findBuilderInfo( typeElement.getSuperclass() ); + } + + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { TypeElement immutableAnnotation = elementUtils.getTypeElement( IMMUTABLE_FQN ); if ( immutableAnnotation != null ) { - BuilderInfo info = findBuilderInfoForImmutables( + return findBuilderInfoForImmutables( typeElement, immutableAnnotation ); - if ( info != null ) { - return info; - } } - - return super.findBuilderInfo( typeElement ); + return null; } protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, @@ -55,7 +68,7 @@ protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, if ( typeUtils.isSameType( annotationMirror.getAnnotationType(), immutableAnnotation.asType() ) ) { TypeElement immutableElement = asImmutableElement( typeElement ); if ( immutableElement != null ) { - return super.findBuilderInfo( immutableElement ); + return super.findBuilderInfo( immutableElement, false ); } else { // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java new file mode 100644 index 0000000000..55a3f783c3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370; + +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +public class Issue3370BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + return super.findBuilderInfo( asImmutableElement( typeElement ) ); + } + return super.findBuilderInfo( typeElement ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java new file mode 100644 index 0000000000..0389de5b46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java @@ -0,0 +1,55 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._3370.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, +}) +@IssueKey("3370") +@WithServiceImplementations({ + @WithServiceImplementation(provides = BuilderProvider.class, value = Issue3370BuilderProvider.class), + @WithServiceImplementation(provides = AccessorNamingStrategy.class, + value = ImmutablesAccessorNamingStrategy.class), +}) +public class Issue3370Test { + + @ProcessorTest + public void shouldUseBuilderOfImmutableSuperClass() { + + Map attributesMap = new HashMap<>(); + attributesMap.put( "a", "b" ); + attributesMap.put( "c", "d" ); + + ItemDTO item = new ItemDTO( "test", attributesMap ); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + assertThat( target.getAttributes() ).isEqualTo( attributesMap ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java new file mode 100644 index 0000000000..5583e191b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java new file mode 100644 index 0000000000..9258914c0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java @@ -0,0 +1,316 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link Item}. + *

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

    {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem} instances. + */ + public Builder() { + if ( !( this instanceof Item.Builder ) ) { + throw new UnsupportedOperationException( "Use: new Item.Builder()" ); + } + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder from(Item instance) { + Objects.requireNonNull( instance, "instance" ); + id( instance.getId() ); + putAllAttributes( instance.getAttributes() ); + return (Item.Builder) this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder id(String id) { + this.id = Objects.requireNonNull( id, "id" ); + initBits &= ~INIT_BIT_ID; + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. + * + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull( key, "attributes key" ), + Objects.requireNonNull( value, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder attributes(Map attributes) { + this.attributes.clear(); + return putAllAttributes( attributes ); + } + + /** + * Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAllAttributes(Map attributes) { + for ( Map.Entry entry : attributes.entrySet() ) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + } + return (Item.Builder) this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItem( id, createUnmodifiableMap( false, false, attributes ) ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, + Map map) { + switch ( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + if ( skipNulls && ( k == null || v == null ) ) { + return Collections.emptyMap(); + } + return Collections.singletonMap( k, v ); + } + default: { + Map linkedMap = new LinkedHashMap( map.size() ); + if ( skipNulls || checkNulls ) { + for ( Map.Entry e : map.entrySet() ) { + K k = e.getKey(); + V v = e.getValue(); + if ( skipNulls ) { + if ( k == null || v == null ) { + continue; + } + } + else if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + linkedMap.put( k, v ); + } + } + else { + linkedMap.putAll( map ); + } + return Collections.unmodifiableMap( linkedMap ); + } + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java new file mode 100644 index 0000000000..a78c3bc007 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370.domain; + +import java.util.Collections; +import java.util.Map; + +public abstract class Item { + + public abstract String getId(); + + public abstract Map getAttributes(); + + public static Item.Builder builder() { + return new Item.Builder(); + } + + public static class Builder extends ImmutableItem.Builder { + + public ImmutableItem.Builder addSomeData(String key, String data) { + return super.attributes( Collections.singletonMap( key, data ) ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java new file mode 100644 index 0000000000..70dba87fe4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3370.dto; + +import java.util.Map; + +public class ItemDTO { + private final String id; + private final Map attributes; + + public ItemDTO(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + public String getId() { + return id; + } + + public Map getAttributes() { + return attributes; + } +} From 21fdaa0f824fc358efc05507c591f86767fa3245 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 3 Nov 2024 12:54:37 +0100 Subject: [PATCH 076/214] #3747 Do not generate redundant if condition with constructor mapping and RETURN_DEFAULT null value mapping strategy --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../ap/test/bugs/_3747/Issue3747Mapper.java | 42 +++++++++++++++++++ .../ap/test/bugs/_3747/Issue3747Test.java | 28 +++++++++++++ .../test/bugs/_3747/Issue3747MapperImpl.java | 29 +++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 61d9cc1837..3036e4a2c8 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -116,7 +116,7 @@ - <#else> + <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java new file mode 100644 index 0000000000..04addc43f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java @@ -0,0 +1,42 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3747; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface Issue3747Mapper { + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java new file mode 100644 index 0000000000..57650e4dc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3747; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3747") +@WithClasses(Issue3747Mapper.class) +class Issue3747Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void shouldNotGenerateObsoleteCode() { + generatedSource.addComparisonToFixtureFor( Issue3747Mapper.class ); + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java new file mode 100644 index 0000000000..adc937dab1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3747; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-11-03T12:50:02+0100", + comments = "version: , compiler: javac, environment: Java 21.0.3 (N/A)" +) +public class Issue3747MapperImpl implements Issue3747Mapper { + + @Override + public Target map(Source source) { + + String value = null; + if ( source != null ) { + value = source.getValue(); + } + + Target target = new Target( value ); + + return target; + } +} From c2bd847599c1cdfad562eb02ca47b16e8dfa56d6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 10:15:33 +0100 Subject: [PATCH 077/214] #3732 Do not generate obsolete imports for LocalDateTime <-> LocalDate conversion --- ...avaLocalDateTimeToLocalDateConversion.java | 16 ------- .../ap/test/bugs/_3732/Issue3732Mapper.java | 47 +++++++++++++++++++ .../ap/test/bugs/_3732/Issue3732Test.java | 22 +++++++++ 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java index ae571ad6b0..9116bada4c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java @@ -7,11 +7,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Collections; /** * SimpleConversion for mapping {@link LocalDateTime} to @@ -25,22 +22,9 @@ protected String getToExpression(ConversionContext conversionContext) { return ".toLocalDate()"; } - @Override - protected Set getToConversionImportTypes(ConversionContext conversionContext) { - return Collections.asSet( - conversionContext.getTypeFactory().getType( LocalDate.class ) - ); - } - @Override protected String getFromExpression(ConversionContext conversionContext) { return ".atStartOfDay()"; } - @Override - protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return Collections.asSet( - conversionContext.getTypeFactory().getType( LocalDateTime.class ) - ); - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java new file mode 100644 index 0000000000..79b13aa8ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3732; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3732Mapper { + + Target map(Source source); + + Source map(Target source); + + class Source { + private LocalDateTime value; + + public LocalDateTime getValue() { + return value; + } + + public void setValue(LocalDateTime value) { + this.value = value; + } + } + + class Target { + + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java new file mode 100644 index 0000000000..9dbd9c0bb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3732; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3732") +@WithClasses({ Issue3732Mapper.class }) +class Issue3732Test { + + @ProcessorTest + void shouldGenerateCorrectMapper() { + } +} From efdf435770dd4361ccfa6e64d643cc88f156e79e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 10:44:54 +0100 Subject: [PATCH 078/214] #3751 Improve readme to include support for Java 16+ records --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7903ddeb84..6932f4d748 100644 --- a/readme.md +++ b/readme.md @@ -19,7 +19,10 @@ ## What is MapStruct? -MapStruct is a Java [annotation processor](https://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html) for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior. +MapStruct is a Java [annotation processor](https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html#annotation-processing) designed to generate type-safe and high-performance mappers for Java bean classes, including support for Java 16+ records. +By automating the creation of mappings, MapStruct eliminates the need for tedious and error-prone manual coding. +The generator provides sensible defaults and built-in type conversions, allowing it to handle standard mappings effortlessly, while also offering flexibility for custom configurations or specialized mapping behaviors. +With seamless integration into modern Java projects, MapStruct can map between conventional beans, records, and even complex hierarchies, making it an adaptable tool for diverse Java applications. Compared to mapping frameworks working at runtime, MapStruct offers the following advantages: From 772fae4c77ccd0d35c33afa7f04431090a964692 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 12:20:14 +0100 Subject: [PATCH 079/214] Prepare release notes for 1.6.3 --- NEXT_RELEASE_CHANGELOG.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..dca36c820a 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,9 @@ -### Features - -### Enhancements - ### Bugs -### Documentation +* Redundant if condition in Java record mapping with `RETURN_DEFAULT` strategy (#3747) +* Stackoverflow with Immutables custom builder (#3370) +* Unused import of `java.time.LocalDate` when mapping source `LocalDateTime` to target `LocalDate` (#3732) -### Build +### Documentation +* Add section to README.md comparing mapstruct with Java Records (#3751) From b4e25e49deae707b50ce061172e114292b414a23 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:31:12 +0000 Subject: [PATCH 080/214] Releasing version 1.6.3 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..e60cda2afe 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..adec24c93b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..e276a889ce 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..4d820495cd 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..45f4eb19c6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..d686c5a368 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..ded84caf2e 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-11-09T11:31:12Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..9df5b02892 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..7410fd0a35 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml From 5bf2b152afcb5e3f4335f35ea9e0d747963e4298 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:40:01 +0000 Subject: [PATCH 081/214] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 11 ++++++----- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 16 insertions(+), 15 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index dca36c820a..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,9 +1,10 @@ -### Bugs +### Features + +### Enhancements -* Redundant if condition in Java record mapping with `RETURN_DEFAULT` strategy (#3747) -* Stackoverflow with Immutables custom builder (#3370) -* Unused import of `java.time.LocalDate` when mapping source `LocalDateTime` to target `LocalDate` (#3732) +### Bugs ### Documentation -* Add section to README.md comparing mapstruct with Java Records (#3751) +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index e60cda2afe..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index adec24c93b..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e276a889ce..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4d820495cd..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 45f4eb19c6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index d686c5a368..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index ded84caf2e..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-11-09T11:31:12Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 9df5b02892..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 7410fd0a35..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml From 0df3f6af957c1468ae4baaf85b528131fbde9711 Mon Sep 17 00:00:00 2001 From: cussle <109949453+cussle@users.noreply.github.com> Date: Thu, 14 Nov 2024 05:22:57 +0900 Subject: [PATCH 082/214] docs & refactor: fix typos and improve readability in multiple classes (#3767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AnnotatedConstructor: Fixed a variable name typo (noArgConstructorToInBecluded → noArgConstructorToBeIncluded). - AbstractBaseBuilder: Improved Javadoc by fixing typos and clarifying wording. - SourceRhsSelector: Corrected a typo in the class-level Javadoc. - InheritanceSelector: Enhanced readability by fixing typos and refining comments. --- .../ap/internal/model/AbstractBaseBuilder.java | 4 ++-- .../ap/internal/model/AnnotatedConstructor.java | 8 ++++---- .../model/source/selector/InheritanceSelector.java | 10 +++++----- .../model/source/selector/SourceRhsSelector.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 311cc00368..56a1831d52 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -41,7 +41,7 @@ public B method(Method sourceMethod) { } /** - * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and @{code + * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and {@code * targetType}. * This will evaluate to {@code true}, when: *

  • @@ -66,7 +66,7 @@ private boolean isDisableSubMappingMethodsGeneration() { /** * Creates a forged assignment from the provided {@code sourceRHS} and {@code forgedMethod}. If a mapping method - * for the {@code forgedMethod} already exists, then this method used for the assignment. + * for the {@code forgedMethod} already exists, this method will be used for the assignment. * * @param sourceRHS that needs to be used for the assignment * @param forgedMethod the forged method for which we want to create an {@link Assignment} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java index 1c38b1062b..889d602cdb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java @@ -36,14 +36,14 @@ public static AnnotatedConstructor forComponentModels(String name, if ( constructor instanceof NoArgumentConstructor ) { noArgumentConstructor = (NoArgumentConstructor) constructor; } - NoArgumentConstructor noArgConstructorToInBecluded = null; + NoArgumentConstructor noArgConstructorToBeIncluded = null; Set fragmentsToBeIncluded = Collections.emptySet(); if ( includeNoArgConstructor ) { if ( noArgumentConstructor != null ) { - noArgConstructorToInBecluded = noArgumentConstructor; + noArgConstructorToBeIncluded = noArgumentConstructor; } else { - noArgConstructorToInBecluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); + noArgConstructorToBeIncluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); } } else if ( noArgumentConstructor != null ) { @@ -53,7 +53,7 @@ else if ( noArgumentConstructor != null ) { name, mapperReferences, annotations, - noArgConstructorToInBecluded, + noArgConstructorToBeIncluded, fragmentsToBeIncluded ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index 297dcaa4a3..9a4fba6e30 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -33,7 +33,7 @@ public List> getMatchingMethods(List int addToCandidateListIfMinimal(List> candidatesWithBestMathingType, + private int addToCandidateListIfMinimal(List> candidatesWithBestMatchingType, int bestMatchingTypeDistance, SelectedMethod method, int currentTypeDistance) { if ( currentTypeDistance == bestMatchingTypeDistance ) { - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.add( method ); } else if ( currentTypeDistance < bestMatchingTypeDistance ) { bestMatchingTypeDistance = currentTypeDistance; - candidatesWithBestMathingType.clear(); - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.clear(); + candidatesWithBestMatchingType.add( method ); } return bestMatchingTypeDistance; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java index fb797f5808..91a30902e0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java @@ -12,7 +12,7 @@ import org.mapstruct.ap.internal.model.source.Method; /** - * Selector that tries to resolve an ambiquity between methods that contain source parameters and + * Selector that tries to resolve an ambiguity between methods that contain source parameters and * {@link org.mapstruct.ap.internal.model.common.SourceRHS SourceRHS} type parameters. * @author Filip Hrisafov */ From 8de18e5a65a353e6ff12f6a8f130a08173bf9672 Mon Sep 17 00:00:00 2001 From: hsjni0110 <92289935+hsjni0110@users.noreply.github.com> Date: Thu, 14 Nov 2024 05:35:20 +0900 Subject: [PATCH 083/214] fix typos in method and variable names (#3766) --- .../internal/model/source/SourceMethod.java | 2 +- .../processor/MapperCreationProcessor.java | 26 +++++++++---------- .../processor/MethodRetrievalProcessor.java | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 7103fb2858..8427dd3eca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -186,7 +186,7 @@ public Builder setPrototypeMethods(List prototypeMethods) { return this; } - public Builder setDefininingType(Type definingType) { + public Builder setDefiningType(Type definingType) { this.definingType = definingType; return this; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 5fc1b07823..3b38a05b59 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -195,7 +195,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< addAllFieldsIn( mappingContext.getUsedSupportedMappings(), supportingFieldSet ); fields.addAll( supportingFieldSet ); - // handle constructorfragments + // handle constructor fragments Set constructorFragments = new LinkedHashSet<>(); addAllFragmentsIn( mappingContext.getUsedSupportedMappings(), constructorFragments ); @@ -632,7 +632,7 @@ else if ( nameFilteredcandidates.size() > 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inverseConfiguration ); } else { - reportErrorWhenAmbigousReverseMapping( candidates, method, inverseConfiguration ); + reportErrorWhenAmbiguousReverseMapping( candidates, method, inverseConfiguration ); } } } @@ -702,21 +702,21 @@ else if ( sourceMethod.getName().equals( name ) ) { else if ( candidates.size() > 1 ) { // ambiguity: find a matching method that matches configuredBy - List nameFilteredcandidates = new ArrayList<>(); + List nameFilteredCandidates = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { if ( candidate.getName().equals( name ) ) { - nameFilteredcandidates.add( candidate ); + nameFilteredCandidates.add( candidate ); } } - if ( nameFilteredcandidates.size() == 1 ) { - resultMethod = first( nameFilteredcandidates ); + if ( nameFilteredCandidates.size() == 1 ) { + resultMethod = first( nameFilteredCandidates ); } - else if ( nameFilteredcandidates.size() > 1 ) { - reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inheritConfiguration ); + else if ( nameFilteredCandidates.size() > 1 ) { + reportErrorWhenSeveralNamesMatch( nameFilteredCandidates, method, inheritConfiguration ); } else { - reportErrorWhenAmbigousMapping( candidates, method, inheritConfiguration ); + reportErrorWhenAmbiguousMapping( candidates, method, inheritConfiguration ); } } } @@ -729,8 +729,8 @@ private AnnotationMirror getAnnotationMirror(InheritConfigurationGem inheritConf return inheritConfiguration == null ? null : inheritConfiguration.mirror(); } - private void reportErrorWhenAmbigousReverseMapping(List candidates, SourceMethod method, - InheritInverseConfigurationGem inverseGem) { + private void reportErrorWhenAmbiguousReverseMapping(List candidates, SourceMethod method, + InheritInverseConfigurationGem inverseGem) { List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { @@ -780,8 +780,8 @@ private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMe ); } - private void reportErrorWhenAmbigousMapping(List candidates, SourceMethod method, - InheritConfigurationGem gem) { + private void reportErrorWhenAmbiguousMapping(List candidates, SourceMethod method, + InheritConfigurationGem gem) { List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 4bd2ed48a5..2c8ad5dcb9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -389,7 +389,7 @@ private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType return new SourceMethod.Builder() .setDeclaringMapper( usedMapper.equals( mapperToImplement ) ? null : usedMapperAsType ) - .setDefininingType( definingType ) + .setDefiningType( definingType ) .setExecutable( method ) .setParameters( parameters ) .setReturnType( returnType ) From bee983cd3c627333a613a58be555090ac754da95 Mon Sep 17 00:00:00 2001 From: Minji Kim <101392857+alsswl@users.noreply.github.com> Date: Sat, 16 Nov 2024 06:13:36 +0900 Subject: [PATCH 084/214] Fix typos in comments (#3769) --- distribution/pom.xml | 2 +- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 6 +++--- .../mapstruct/ap/internal/model/MappingBuilderContext.java | 2 +- .../internal/model/NestedTargetPropertyMappingHolder.java | 4 ++-- .../org/mapstruct/ap/internal/model/ValueMappingMethod.java | 4 ++-- .../java/org/mapstruct/ap/spi/AccessorNamingStrategy.java | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..db654236e4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -130,7 +130,7 @@ enumToStringMapping(Method method, Type sourceType ) return mappings; } - // Start to fill the mappings with the defined valuemappings + // Start to fill the mappings with the defined valueMappings for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); unmappedSourceConstants.remove( valueMapping.getSource() ); @@ -305,7 +305,7 @@ private List stringToEnumMapping(Method method, Type targetType ) } Set mappedSources = new LinkedHashSet<>(); - // Start to fill the mappings with the defined valuemappings + // Start to fill the mappings with the defined value mappings for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { mappedSources.add( valueMapping.getSource() ); mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java index c9d0734753..b4dde64324 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java @@ -68,7 +68,7 @@ default void init(MapStructProcessingEnvironment processingEnvironment) { * * @return getter name for collection properties * - * @deprecated MapStuct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to + * @deprecated MapStruct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to * determine the {@link MethodType}. When collections somehow need to be treated special, it should be done in * {@link #getMethodType(ExecutableElement) } as well. In the future, this method will be removed. */ From 737af6b50a1641d936c8a5b152bb97bf1f843d18 Mon Sep 17 00:00:00 2001 From: Roman Obolonskyi <65775868+Obolrom@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:46:59 +0200 Subject: [PATCH 085/214] #3628 Add support for locale parameter for numberFormat and dateFormat --- .../java/org/mapstruct/IterableMapping.java | 19 + .../main/java/org/mapstruct/MapMapping.java | 27 + core/src/main/java/org/mapstruct/Mapping.java | 19 + .../BigDecimalToStringConversion.java | 20 +- .../BigIntegerToStringConversion.java | 20 +- .../internal/conversion/ConversionUtils.java | 12 + .../conversion/CreateDecimalFormat.java | 37 +- .../conversion/DateToStringConversion.java | 35 +- .../PrimitiveToStringConversion.java | 39 +- .../conversion/WrapperToStringConversion.java | 39 +- .../model/common/ConversionContext.java | 2 + .../common/DefaultConversionContext.java | 7 + .../model/common/FormattingParameters.java | 10 +- .../model/source/IterableMappingOptions.java | 3 +- .../model/source/MapMappingOptions.java | 14 +- .../internal/model/source/MappingOptions.java | 9 +- .../conversion/CreateDecimalFormat.ftl | 7 +- .../common/DefaultConversionContextTest.java | 6 +- .../conversion/date/DateConversionTest.java | 120 ++++ .../conversion/date/SourceTargetMapper.java | 24 +- .../numbers/NumberFormatConversionTest.java | 126 ++++- .../numbers/SourceTargetMapper.java | 41 +- .../numbers/SourceTargetMapperImpl.java | 525 ++++++++++++++++++ .../numbers/SourceTargetMapperImpl.java | 525 ++++++++++++++++++ 24 files changed, 1631 insertions(+), 55 deletions(-) create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java diff --git a/core/src/main/java/org/mapstruct/IterableMapping.java b/core/src/main/java/org/mapstruct/IterableMapping.java index 76153127bd..d644dfe03b 100644 --- a/core/src/main/java/org/mapstruct/IterableMapping.java +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -66,19 +66,38 @@ /** * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from an iterable of * {@code String} to an iterable {@link Date} or vice-versa. Will be ignored for all other element types. + *

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

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

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

    + * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers. + */ + String locale() default ""; + /** * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple * mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' error. diff --git a/core/src/main/java/org/mapstruct/MapMapping.java b/core/src/main/java/org/mapstruct/MapMapping.java index 271272bb45..093099cf5a 100644 --- a/core/src/main/java/org/mapstruct/MapMapping.java +++ b/core/src/main/java/org/mapstruct/MapMapping.java @@ -56,8 +56,12 @@ /** * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type * {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types. + *

    + * If the {@link #locale()} is specified, the format will consider the specified locale when processing the date. + * Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String keyDateFormat() default ""; @@ -65,27 +69,50 @@ * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value * type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value * types. + *

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

    + * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String keyNumberFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other value types. + *

    + * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String valueNumberFormat() default ""; + /** + * Specifies the locale to be used when processing {@link SimpleDateFormat} or {@link DecimalFormat} for key or + * value mappings in maps. The locale should be a plain tag representing the language, such as "en" for English, + * "de" for German, etc. + *

    + * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers in maps. + */ + String locale() default ""; + /** * A key value qualifier can be specified to aid the selection process of a suitable mapper. This is useful in * case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 8b0c4adb0c..6c95b0db2c 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -175,19 +175,38 @@ /** * A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to * {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants. + *

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

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

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

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

    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java index 92a93e3893..384013a7b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java @@ -14,8 +14,9 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; -import static org.mapstruct.ap.internal.util.Collections.asSet; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigDecimal} and {@link String}. @@ -64,18 +65,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java index df0a48a673..540d89db58 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java @@ -15,9 +15,10 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; -import static org.mapstruct.ap.internal.util.Collections.asSet; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigInteger} and {@link String}. @@ -72,18 +73,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index aa01a73276..96960c4a11 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -11,6 +11,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; @@ -279,4 +280,15 @@ public static String url(ConversionContext conversionContext) { return typeReferenceName( conversionContext, URL.class ); } + /** + * Name for {@link java.text.DecimalFormatSymbols}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String decimalFormatSymbols(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, DecimalFormatSymbols.class ); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java index 77c59445ab..d1b49cff69 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -5,9 +5,11 @@ */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; @@ -16,6 +18,8 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.MappingMethodOptions; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * HelperMethod that creates a {@link java.text.DecimalFormat} * @@ -27,13 +31,30 @@ public class CreateDecimalFormat extends HelperMethod { private final Parameter parameter; + private final Parameter localeParameter; private final Type returnType; private final Set importTypes; - public CreateDecimalFormat(TypeFactory typeFactory) { + public CreateDecimalFormat(TypeFactory typeFactory, boolean withLocale) { this.parameter = new Parameter( "numberFormat", typeFactory.getType( String.class ) ); + this.localeParameter = withLocale ? new Parameter( "locale", typeFactory.getType( Locale.class ) ) : null; this.returnType = typeFactory.getType( DecimalFormat.class ); - this.importTypes = asSet( parameter.getType(), returnType ); + if ( withLocale ) { + this.importTypes = asSet( + parameter.getType(), + returnType, + typeFactory.getType( DecimalFormatSymbols.class ), + typeFactory.getType( Locale.class ) + ); + } + else { + this.importTypes = asSet( parameter.getType(), returnType ); + } + } + + @Override + public String getName() { + return localeParameter == null ? "createDecimalFormat" : "createDecimalFormatWithLocale"; } @Override @@ -60,4 +81,12 @@ public MappingMethodOptions getOptions() { public String describe() { return null; } + + @Override + public List getParameters() { + if ( localeParameter == null ) { + return super.getParameters(); + } + return Arrays.asList( getParameter(), localeParameter ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java index 361ce9d9ab..35c7f88d74 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java @@ -10,15 +10,18 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.TypeConversion; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; -import static java.util.Arrays.asList; -import static org.mapstruct.ap.internal.util.Collections.asSet; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.simpleDateFormat; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link String} and {@link Date}. @@ -29,7 +32,7 @@ public class DateToStringConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), Collections.emptyList(), getConversionExpression( conversionContext, "format" ) ); @@ -37,8 +40,8 @@ public Assignment to(ConversionContext conversionContext) { @Override public Assignment from(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - asList( conversionContext.getTypeFactory().getType( ParseException.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.singletonList( conversionContext.getTypeFactory().getType( ParseException.class ) ), getConversionExpression( conversionContext, "parse" ) ); } @@ -48,6 +51,17 @@ public List getRequiredHelperMethods(ConversionContext conversionC return Collections.emptyList(); } + private Set getImportTypes(ConversionContext conversionContext) { + if ( conversionContext.getLocale() == null ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ); + } + + return asSet( + conversionContext.getTypeFactory().getType( SimpleDateFormat.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + private String getConversionExpression(ConversionContext conversionContext, String method) { StringBuilder conversionString = new StringBuilder( "new " ); conversionString.append( simpleDateFormat( conversionContext ) ); @@ -56,7 +70,16 @@ private String getConversionExpression(ConversionContext conversionContext, Stri if ( conversionContext.getDateFormat() != null ) { conversionString.append( " \"" ); conversionString.append( conversionContext.getDateFormat() ); - conversionString.append( "\" " ); + conversionString.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + conversionString.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + conversionString.append( conversionContext.getLocale() ); + conversionString.append( "\" ) " ); + } + else { + conversionString.append( " " ); + } } conversionString.append( ")." ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java index fcc7241290..909ce8c0f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.conversion; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.Collections; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; @@ -15,6 +17,9 @@ import org.mapstruct.ap.internal.util.Strings; import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between primitive types such as {@code byte} or {@code long} and @@ -53,9 +58,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -80,9 +91,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -97,6 +114,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } } sb.append( " )" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java index 300c901216..dd7b6bac8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.conversion; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.Collections; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; @@ -15,6 +17,9 @@ import org.mapstruct.ap.internal.util.Strings; import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between wrapper types such as {@link Integer} and {@link String}. @@ -52,9 +57,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -79,9 +90,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -96,6 +113,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } } sb.append( " )" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index c2aa73f307..96d3d6fe78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -32,6 +32,8 @@ public interface ConversionContext { String getNumberFormat(); + String getLocale(); + TypeFactory getTypeFactory(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index f5a9fcc761..159f1663e2 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -21,6 +21,7 @@ public class DefaultConversionContext implements ConversionContext { private final FormattingParameters formattingParameters; private final String dateFormat; private final String numberFormat; + private final String locale; private final TypeFactory typeFactory; public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager messager, Type sourceType, @@ -32,6 +33,7 @@ public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager mess this.formattingParameters = formattingParameters; this.dateFormat = this.formattingParameters.getDate(); this.numberFormat = this.formattingParameters.getNumber(); + this.locale = this.formattingParameters.getLocale(); validateDateFormat(); } @@ -64,6 +66,11 @@ public String getNumberFormat() { return numberFormat; } + @Override + public String getLocale() { + return locale != null ? locale.toString() : null; + } + @Override public String getDateFormat() { return dateFormat; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java index e21f5d74fa..48cee4bbe5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java @@ -15,21 +15,23 @@ */ public class FormattingParameters { - public static final FormattingParameters EMPTY = new FormattingParameters( null, null, null, null, null ); + public static final FormattingParameters EMPTY = new FormattingParameters( null, null, null, null, null, null ); private final String date; private final String number; private final AnnotationMirror mirror; private final AnnotationValue dateAnnotationValue; private final Element element; + private final String locale; public FormattingParameters(String date, String number, AnnotationMirror mirror, - AnnotationValue dateAnnotationValue, Element element) { + AnnotationValue dateAnnotationValue, Element element, String locale) { this.date = date; this.number = number; this.mirror = mirror; this.dateAnnotationValue = dateAnnotationValue; this.element = element; + this.locale = locale; } public String getDate() { @@ -51,4 +53,8 @@ public AnnotationValue getDateAnnotationValue() { public Element getElement() { return element; } + + public String getLocale() { + return locale; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java index c4770efde6..50fca3e4d2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -55,7 +55,8 @@ public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, iterableMapping.numberFormat().get(), iterableMapping.mirror(), iterableMapping.dateFormat().getAnnotationValue(), - method + method, + iterableMapping.locale().getValue() ); IterableMappingOptions options = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java index fd1758d8d2..9f3d12faf3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -8,14 +8,14 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -47,6 +47,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions return options; } + String locale = mapMapping.locale().getValue(); + SelectionParameters keySelection = new SelectionParameters( mapMapping.keyQualifiedBy().get(), mapMapping.keyQualifiedByName().get(), @@ -66,7 +68,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions mapMapping.keyNumberFormat().get(), mapMapping.mirror(), mapMapping.keyDateFormat().getAnnotationValue(), - method + method, + locale ); FormattingParameters valueFormatting = new FormattingParameters( @@ -74,7 +77,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions mapMapping.valueNumberFormat().get(), mapMapping.mirror(), mapMapping.valueDateFormat().getAnnotationValue(), - method + method, + locale ); MapMappingOptions options = new MapMappingOptions( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 22f9ccdc2f..746aca5a32 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -18,16 +18,16 @@ import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.gem.MappingGem; import org.mapstruct.ap.internal.gem.MappingsGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -118,6 +118,8 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, String conditionExpression = getConditionExpression( mapping, method, messager ); String dateFormat = mapping.dateFormat().getValue(); String numberFormat = mapping.numberFormat().getValue(); + String locale = mapping.locale().getValue(); + String defaultValue = mapping.defaultValue().getValue(); Set dependsOn = mapping.dependsOn().hasValue() ? @@ -129,7 +131,8 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, numberFormat, mapping.mirror(), mapping.dateFormat().getAnnotationValue(), - method + method, + locale ); SelectionParameters selectionParams = new SelectionParameters( mapping.qualifiedBy().get(), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl index ce0f605f11..6753a73d6f 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl @@ -5,9 +5,10 @@ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 --> -private DecimalFormat ${name}( String numberFormat ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private DecimalFormat ${name}( <#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - DecimalFormat df = new DecimalFormat( numberFormat ); + DecimalFormat df = new DecimalFormat( numberFormat<#if parameters.size() > 1>, DecimalFormatSymbols.getInstance( locale ) ); df.setParseBigDecimal( true ); return df; -} \ No newline at end of file +} diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index ce2f895e0f..401a6b2240 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -70,7 +70,7 @@ public void testInvalidDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isEqualTo( Diagnostic.Kind.ERROR ); } @@ -84,7 +84,7 @@ public void testNullDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( null, null, null, null, null ) + new FormattingParameters( null, null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -97,7 +97,7 @@ public void testUnsupportedType() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 9ccf8e7e1f..bc90228206 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -54,6 +54,21 @@ public void shouldApplyDateFormatForConversions() { assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); } + @ProcessorTest + @EnabledOnJre( JRE.JAVA_8 ) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionsWithCustomLocale() { + Source source = new Source(); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); + } + @ProcessorTest @EnabledForJreRange(min = JRE.JAVA_11) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -69,6 +84,21 @@ public void shouldApplyDateFormatForConversionsJdk11() { assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); } + @ProcessorTest + @EnabledForJreRange(min = JRE.JAVA_11) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { + Source source = new Source(); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); + } + @ProcessorTest @EnabledOnJre(JRE.JAVA_8) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -86,6 +116,23 @@ public void shouldApplyDateFormatForConversionInReverseMapping() { ); } + @ProcessorTest + @EnabledOnJre(JRE.JAVA_8) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionInReverseMappingWithCustomLocale() { + Target target = new Target(); + target.setDate( "juillet 06, 2013" ); + target.setAnotherDate( "14.02.13 8:30" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); + } + @ProcessorTest @EnabledForJreRange(min = JRE.JAVA_11) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -103,6 +150,23 @@ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { ); } + @ProcessorTest + @EnabledForJreRange(min = JRE.JAVA_11) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { + Target target = new Target(); + target.setDate( "juillet 06, 2013" ); + target.setAnotherDate( "14.02.13, 8:30" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForIterableMethod() { List dates = Arrays.asList( @@ -117,6 +181,20 @@ public void shouldApplyStringConversionForIterableMethod() { assertThat( stringDates ).containsExactly( "06.07.2013", "14.02.2013", "11.04.2013" ); } + @ProcessorTest + public void shouldApplyStringConversionForIterableMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + List stringDates = SourceTargetMapper.INSTANCE.stringListToDateListWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).containsExactly( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + } + @ProcessorTest public void shouldApplyStringConversionForArrayMethod() { List dates = Arrays.asList( @@ -131,6 +209,20 @@ public void shouldApplyStringConversionForArrayMethod() { assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); } + @ProcessorTest + public void shouldApplyStringConversionForArrayMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + String[] stringDates = SourceTargetMapper.INSTANCE.stringListToDateArrayWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).isEqualTo( new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" } ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseIterableMethod() { List stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" ); @@ -145,6 +237,20 @@ public void shouldApplyStringConversionForReverseIterableMethod() { ); } + @ProcessorTest + public void shouldApplyStringConversionForReverseIterableMethodWithCustomLocale() { + List stringDates = Arrays.asList( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + + List dates = SourceTargetMapper.INSTANCE.dateListToStringListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseArrayMethod() { String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; @@ -159,6 +265,20 @@ public void shouldApplyStringConversionForReverseArrayMethod() { ); } + @ProcessorTest + public void shouldApplyStringConversionForReverseArrayMethodWithCustomLocale() { + String[] stringDates = new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" }; + + List dates = SourceTargetMapper.INSTANCE.stringArrayToDateListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseArrayArrayMethod() { Date[] dates = new Date[]{ diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java index 2ef6a7bd96..1971a8c066 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java @@ -22,21 +22,39 @@ public interface SourceTargetMapper { @Mapping(target = "date", dateFormat = "dd.MM.yyyy") Target sourceToTarget(Source source); - @InheritInverseConfiguration + @Mapping(target = "date", dateFormat = "MMMM dd, yyyy", locale = "fr") + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration(name = "sourceToTarget") Source targetToSource(Target target); + @InheritInverseConfiguration(name = "sourceToTargetWithCustomLocale") + Source targetToSourceWithCustomLocale(Target target); + @IterableMapping(dateFormat = "dd.MM.yyyy") List stringListToDateList(List dates); + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + List stringListToDateListWithCustomLocale(List dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] stringListToDateArray(List dates); - @InheritInverseConfiguration + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + String[] stringListToDateArrayWithCustomLocale(List dates); + + @InheritInverseConfiguration(name = "stringListToDateList") List dateListToStringList(List strings); - @InheritInverseConfiguration + @InheritInverseConfiguration(name = "stringListToDateListWithCustomLocale") + List dateListToStringListWithCustomLocale(List strings); + + @InheritInverseConfiguration(name = "stringListToDateArray") List stringArrayToDateList(String[] dates); + @InheritInverseConfiguration(name = "stringListToDateArrayWithCustomLocale") + List stringArrayToDateListWithCustomLocale(String[] dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] dateArrayToStringArray(Date[] dates); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java index 6dfc1b3ca1..39b4e98319 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java @@ -7,14 +7,15 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -27,6 +28,10 @@ @DefaultLocale("en") public class NumberFormatConversionTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + @ProcessorTest public void shouldApplyStringConversions() { Source source = new Source(); @@ -68,6 +73,47 @@ public void shouldApplyStringConversions() { assertThat( target.getBigInteger1() ).isEqualTo( "1.23456789E12" ); } + @ProcessorTest + public void shouldApplyStringConversionsWithCustomLocale() { + Source source = new Source(); + source.setI( 1 ); + source.setIi( 2 ); + source.setD( 3.0 ); + source.setDd( 4.0 ); + source.setF( 3.0f ); + source.setFf( 4.0f ); + source.setL( 5L ); + source.setLl( 6L ); + source.setB( (byte) 7 ); + source.setBb( (byte) 8 ); + + source.setComplex1( 345346.456756 ); + source.setComplex2( 5007034.3 ); + + source.setBigDecimal1( new BigDecimal( "987E-20" ) ); + source.setBigInteger1( new BigInteger( "1234567890000" ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getI() ).isEqualTo( "1.00" ); + assertThat( target.getIi() ).isEqualTo( "2.00" ); + assertThat( target.getD() ).isEqualTo( "3.00" ); + assertThat( target.getDd() ).isEqualTo( "4.00" ); + assertThat( target.getF() ).isEqualTo( "3.00" ); + assertThat( target.getFf() ).isEqualTo( "4.00" ); + assertThat( target.getL() ).isEqualTo( "5.00" ); + assertThat( target.getLl() ).isEqualTo( "6.00" ); + assertThat( target.getB() ).isEqualTo( "7.00" ); + assertThat( target.getBb() ).isEqualTo( "8.00" ); + + assertThat( target.getComplex1() ).isEqualTo( "345.35E3" ); + assertThat( target.getComplex2() ).isEqualTo( "$5007034.30" ); + + assertThat( target.getBigDecimal1() ).isEqualTo( "9,87E-18" ); + assertThat( target.getBigInteger1() ).isEqualTo( "1,23456789E12" ); + } + @ProcessorTest public void shouldApplyReverseStringConversions() { Target target = new Target(); @@ -109,17 +155,73 @@ public void shouldApplyReverseStringConversions() { assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); } + @ProcessorTest + public void shouldApplyReverseStringConversionsWithCustomLocale() { + Target target = new Target(); + target.setI( "1.00" ); + target.setIi( "2.00" ); + target.setD( "3.00" ); + target.setDd( "4.00" ); + target.setF( "3.00" ); + target.setFf( "4.00" ); + target.setL( "5.00" ); + target.setLl( "6.00" ); + target.setB( "7.00" ); + target.setBb( "8.00" ); + + target.setComplex1( "345.35E3" ); + target.setComplex2( "$5007034.30" ); + + target.setBigDecimal1( "9,87E-18" ); + target.setBigInteger1( "1,23456789E12" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getI() ).isEqualTo( 1 ); + assertThat( source.getIi() ).isEqualTo( Integer.valueOf( 2 ) ); + assertThat( source.getD() ).isEqualTo( 3.0 ); + assertThat( source.getDd() ).isEqualTo( Double.valueOf( 4.0 ) ); + assertThat( source.getF() ).isEqualTo( 3.0f ); + assertThat( source.getFf() ).isEqualTo( Float.valueOf( 4.0f ) ); + assertThat( source.getL() ).isEqualTo( 5L ); + assertThat( source.getLl() ).isEqualTo( Long.valueOf( 6L ) ); + assertThat( source.getB() ).isEqualTo( (byte) 7 ); + assertThat( source.getBb() ).isEqualTo( (byte) 8 ); + + assertThat( source.getComplex1() ).isEqualTo( 345350.0 ); + assertThat( source.getComplex2() ).isEqualTo( 5007034.3 ); + + assertThat( source.getBigDecimal1() ).isEqualTo( new BigDecimal( "987E-20" ) ); + assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); + } + @ProcessorTest public void shouldApplyStringConversionsToIterables() { - List target = SourceTargetMapper.INSTANCE.sourceToTarget( Arrays.asList( 2f ) ); + List target = SourceTargetMapper.INSTANCE.sourceToTarget( List.of( 2f ) ); assertThat( target ).hasSize( 1 ); - assertThat( target ).isEqualTo( Arrays.asList( "2.00" ) ); + assertThat( target ).isEqualTo( List.of( "2.00" ) ); List source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).hasSize( 1 ); - assertThat( source ).isEqualTo( Arrays.asList( 2.00f ) ); + assertThat( source ).isEqualTo( List.of( 2.00f ) ); + } + + @ProcessorTest + public void shouldApplyStringConversionsToIterablesWithCustomLocale() { + + List target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( + List.of( new BigDecimal("987E-20") ) + ); + + assertThat( target ).hasSize( 1 ); + assertThat( target ).isEqualTo( List.of( "9,87E-18" ) ); + + List source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source ).hasSize( 1 ); + assertThat( source ).isEqualTo( List.of( new BigDecimal("987E-20") ) ); } @ProcessorTest @@ -137,4 +239,20 @@ public void shouldApplyStringConversionsToMaps() { assertThat( source2 ).contains( entry( 1.00f, 2f ) ); } + + @ProcessorTest + public void shouldApplyStringConversionsToMapsWithCustomLocale() { + + Map source1 = new HashMap<>(); + source1.put( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ); + + Map target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source1 ); + assertThat( target ).hasSize( 1 ); + assertThat( target ).contains( entry( "9,87E-18", "9,7E-9" ) ); + + Map source2 = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source2 ).hasSize( 1 ); + assertThat( source2 ).contains( entry( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ) ); + + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java index 537452fca2..b82a7a6199 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.conversion.numbers; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import org.mapstruct.InheritInverseConfiguration; @@ -41,21 +42,55 @@ public interface SourceTargetMapper { } ) Target sourceToTarget(Source source); - @InheritInverseConfiguration + @Mappings( { + @Mapping( target = "i", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ii", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "d", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "dd", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "f", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ff", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "l", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ll", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "b", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "bb", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "complex1", numberFormat = "##0.##E0", locale = "ru" ), + @Mapping( target = "complex2", numberFormat = "$#.00", locale = "ru" ), + @Mapping( target = "bigDecimal1", numberFormat = "#0.#E0", locale = "ru" ), + @Mapping( target = "bigInteger1", numberFormat = "0.#############E0", locale = "ru" ) + + } ) + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) Source targetToSource(Target target); + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Source targetToSourceWithCustomLocale(Target target); + @IterableMapping( numberFormat = NUMBER_FORMAT ) List sourceToTarget(List source); - @InheritInverseConfiguration + @InheritInverseConfiguration( name = "sourceToTarget" ) List targetToSource(List source); + @IterableMapping( numberFormat = "#0.#E0", locale = "fr" ) + List sourceToTargetWithCustomLocale(List source); + + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + List targetToSourceWithCustomLocale(List source); + @MapMapping( keyNumberFormat = NUMBER_FORMAT, valueNumberFormat = "##" ) Map sourceToTarget(Map source); - @InheritInverseConfiguration + @MapMapping( keyNumberFormat = "#0.#E0", valueNumberFormat = "0.#############E0", locale = "fr" ) + Map sourceToTargetWithCustomLocale(Map source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) Map targetToSource(Map source); + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Map targetToSourceWithCustomLocale(Map source); + } diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..ccf8042e6b --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -0,0 +1,525 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.numbers; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-09-14T11:37:30+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00" ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00" ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00" ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00" ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00" ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00" ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00" ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00" ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00" ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00" ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0" ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00" ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormat( "#0.#E0" ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormat( "0.#############E0" ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Target sourceToTargetWithCustomLocale(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Source targetToSource(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00" ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00" ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00" ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00" ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00" ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00" ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00" ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00" ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00" ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00" ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0" ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00" ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormat( "#0.#E0" ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormat( "0.#############E0" ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public Source targetToSourceWithCustomLocale(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public List sourceToTarget(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( Float float1 : source ) { + list.add( new DecimalFormat( "##.00" ).format( float1 ) ); + } + + return list; + } + + @Override + public List targetToSource(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( new DecimalFormat( "##.00" ).parse( string ).floatValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public List sourceToTargetWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( BigDecimal bigDecimal : source ) { + list.add( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( bigDecimal ) ); + } + + return list; + } + + @Override + public List targetToSourceWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( string ) ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public Map sourceToTarget(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = new DecimalFormat( "##.00" ).format( entry.getKey() ); + String value = new DecimalFormat( "##" ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map sourceToTargetWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( entry.getKey() ); + String value = createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSource(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + Float key; + try { + key = new DecimalFormat( "##.00" ).parse( entry.getKey() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + Float value; + try { + value = new DecimalFormat( "##" ).parse( entry.getValue() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSourceWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + BigDecimal key; + try { + key = (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getKey() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + BigDecimal value; + try { + value = (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + private DecimalFormat createDecimalFormatWithLocale( String numberFormat, Locale locale ) { + + DecimalFormat df = new DecimalFormat( numberFormat, DecimalFormatSymbols.getInstance( locale ) ); + df.setParseBigDecimal( true ); + return df; + } + + private DecimalFormat createDecimalFormat( String numberFormat ) { + + DecimalFormat df = new DecimalFormat( numberFormat ); + df.setParseBigDecimal( true ); + return df; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..0b536b8327 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -0,0 +1,525 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.numbers; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-09-14T11:36:20+0300", + comments = "version: , compiler: javac, environment: Java 17.0.10 (Private Build)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00" ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00" ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00" ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00" ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00" ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00" ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00" ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00" ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00" ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00" ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0" ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00" ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormat( "#0.#E0" ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormat( "0.#############E0" ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Target sourceToTargetWithCustomLocale(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Source targetToSource(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00" ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00" ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00" ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00" ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00" ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00" ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00" ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00" ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00" ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00" ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0" ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00" ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormat( "#0.#E0" ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormat( "0.#############E0" ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public Source targetToSourceWithCustomLocale(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public List sourceToTarget(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( Float float1 : source ) { + list.add( new DecimalFormat( "##.00" ).format( float1 ) ); + } + + return list; + } + + @Override + public List targetToSource(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( new DecimalFormat( "##.00" ).parse( string ).floatValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public List sourceToTargetWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( BigDecimal bigDecimal : source ) { + list.add( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( bigDecimal ) ); + } + + return list; + } + + @Override + public List targetToSourceWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( string ) ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public Map sourceToTarget(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = new DecimalFormat( "##.00" ).format( entry.getKey() ); + String value = new DecimalFormat( "##" ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map sourceToTargetWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( entry.getKey() ); + String value = createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSource(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + Float key; + try { + key = new DecimalFormat( "##.00" ).parse( entry.getKey() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + Float value; + try { + value = new DecimalFormat( "##" ).parse( entry.getValue() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSourceWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + BigDecimal key; + try { + key = (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getKey() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + BigDecimal value; + try { + value = (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + private DecimalFormat createDecimalFormatWithLocale( String numberFormat, Locale locale ) { + + DecimalFormat df = new DecimalFormat( numberFormat, DecimalFormatSymbols.getInstance( locale ) ); + df.setParseBigDecimal( true ); + return df; + } + + private DecimalFormat createDecimalFormat( String numberFormat ) { + + DecimalFormat df = new DecimalFormat( numberFormat ); + df.setParseBigDecimal( true ); + return df; + } +} From f3d2b2e65b5709b7575076a220b938ece7931d21 Mon Sep 17 00:00:00 2001 From: dudxor4587 Date: Fri, 22 Nov 2024 07:14:16 +0900 Subject: [PATCH 086/214] Delete unnecessary conditions and modify return statement (#3772) --- .../mapstruct/ap/internal/conversion/Conversions.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 6acb69492a..5090c903f3 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -268,10 +268,10 @@ private void registerNativeTypeConversion(Class sourceType, Class targetTy if ( sourceType.isPrimitive() && targetType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToPrimitiveConversion( sourceType ) ); } - else if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) { + else if ( sourceType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToWrapperConversion( sourceType, targetType ) ); } - else if ( !sourceType.isPrimitive() && targetType.isPrimitive() ) { + else if ( targetType.isPrimitive() ) { register( sourceType, targetType, inverse( new PrimitiveToWrapperConversion( targetType, sourceType ) ) ); } else { @@ -390,11 +390,7 @@ public boolean equals(Object obj) { return false; } - if ( !Objects.equals( targetType, other.targetType ) ) { - return false; - } - - return true; + return Objects.equals( targetType, other.targetType ); } } } From 084cf3abc148255404b0e2b79c878e68c8d606d1 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Fri, 29 Nov 2024 15:33:41 +0700 Subject: [PATCH 087/214] Fix javadoc typos (#3780) --- .../java/org/mapstruct/NullValuePropertyMappingStrategy.java | 2 +- .../ap/internal/model/NestedTargetPropertyMappingHolder.java | 2 +- .../java/org/mapstruct/ap/internal/model/source/Method.java | 2 +- .../main/java/org/mapstruct/ap/internal/util/NativeTypes.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 9e06e723b6..556d4253b1 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -10,7 +10,7 @@ * {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping} * and {@link Mapping}. * Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will - * overide {@link Mapper} + * override {@link Mapper} * * The enum only applies to update methods: methods that update a pre-existing target (annotated with * {@code @}{@link MappingTarget}). diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index ea05b84cae..2b407e240c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -64,7 +64,7 @@ public List getProcessedSourceParameters() { } /** - * @return all the targets that were hanled + * @return all the targets that were handled */ public Set getHandledTargets() { return handledTargets; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index ad2882080a..bea4d56101 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -83,7 +83,7 @@ public interface Method { Parameter getMappingTargetParameter(); /** - * Returns whether the meethod is designated as bean factory for + * Returns whether the method is designated as bean factory for * mapping target {@link org.mapstruct.ObjectFactory } * * @return true if it is a target bean factory. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 729cb180ef..0a4ca0cde0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -174,7 +174,7 @@ void removeAndValidateFloatingPointLiteralSuffix() { boolean endsWithDSuffix = PTRN_DOUBLE.matcher( val ).find(); // error handling if ( isFloat && endsWithDSuffix ) { - throw new NumberFormatException( "Assiging double to a float" ); + throw new NumberFormatException( "Assigning double to a float" ); } // remove suffix if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) { From f98a742f9805dad05653d8fcbae05c085725e4da Mon Sep 17 00:00:00 2001 From: jinhyogyeom <148544516+jinhyogyeom@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:22:51 +0900 Subject: [PATCH 088/214] Standardize Class Names to PascalCase in tests (#3773) --- .../referenced/AbstractSourceTargetMapperPrivate.java | 2 +- .../referenced/AbstractSourceTargetMapperProtected.java | 2 +- .../accessibility/referenced/ReferencedAccessibilityTest.java | 4 ++-- ...perPrivateBase.java => SourceTargetMapperPrivateBase.java} | 2 +- ...rotectedBase.java => SourceTargetMapperProtectedBase.java} | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/{SourceTargetmapperPrivateBase.java => SourceTargetMapperPrivateBase.java} (91%) rename processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/{SourceTargetmapperProtectedBase.java => SourceTargetMapperProtectedBase.java} (90%) diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java index 507e63a957..2a16c3980f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java @@ -14,7 +14,7 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetmapperPrivateBase { +public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetMapperPrivateBase { public static final AbstractSourceTargetMapperPrivate INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperPrivate.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java index ef5df10285..ccdba61514 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java @@ -14,7 +14,7 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperProtected extends SourceTargetmapperProtectedBase { +public abstract class AbstractSourceTargetMapperProtected extends SourceTargetMapperProtectedBase { public static final AbstractSourceTargetMapperProtected INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperProtected.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java index 3cd62a8b4f..dc3d8ea10f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java @@ -72,12 +72,12 @@ public void shouldNotBeAbleToAccessDefaultMethodInReferencedInOtherPackage() { @ProcessorTest @IssueKey( "206" ) - @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetmapperProtectedBase.class } ) + @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetMapperProtectedBase.class } ) public void shouldBeAbleToAccessProtectedMethodInBase() { } @ProcessorTest @IssueKey("206") - @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetmapperPrivateBase.class }) + @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetMapperPrivateBase.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java rename to processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java index 94f9b2668f..06dbb0cec7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java @@ -8,7 +8,7 @@ /** * @author Sjaak Derksen */ -public class SourceTargetmapperPrivateBase { +public class SourceTargetMapperPrivateBase { @SuppressWarnings("unused") private ReferencedTarget sourceToTarget(ReferencedSource source) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java rename to processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java index 263d74bee6..fe5a50fa37 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java @@ -9,7 +9,7 @@ * * @author Sjaak Derksen */ -public class SourceTargetmapperProtectedBase { +public class SourceTargetMapperProtectedBase { protected ReferencedTarget sourceToTarget( ReferencedSource source ) { ReferencedTarget target = new ReferencedTarget(); From 4812d2b030b199f6761fc8da031236d55bf3538b Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 7 Dec 2024 14:57:13 +0100 Subject: [PATCH 089/214] Align README with v1.6.3 release (#3784) --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 6932f4d748..adb48b2fd4 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.2-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.2) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.3-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.3) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.6.2 + 1.6.3 ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.6.2' + implementation 'org.mapstruct:mapstruct:1.6.3' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' // if you are using mapstruct in test code } ... ``` From 8f962919117c30c95165e5349314c0adea455d8f Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sat, 18 Jan 2025 19:04:42 +0700 Subject: [PATCH 090/214] Fix documentation typo and code polish (#3787) --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- .../src/main/asciidoc/chapter-6-mapping-collections.asciidoc | 4 ++-- .../java/org/mapstruct/ap/spi/util/IntrospectorUtils.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ed64580eb4..ea574603ea 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -302,7 +302,7 @@ If you don't want explicitly name all properties from nested source bean, you ca The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them. The same goes for `Customer.account`. -When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict. +When there are conflicts, these can be resolved by explicitly defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict. This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`). diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 17025da4de..79d4544f9b 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -192,7 +192,7 @@ The option `DEFAULT` should not be used explicitly. It is used to distinguish be [TIP] ==== -When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with intialized collections instead of Mapstruct creating the target entity by its constructor. +When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with initialized collections instead of Mapstruct creating the target entity by its constructor. ==== [[implementation-types-for-collection-mappings]] @@ -224,4 +224,4 @@ When an iterable or map mapping method declares an interface type as return type |`ConcurrentMap`|`ConcurrentHashMap` |`ConcurrentNavigableMap`|`ConcurrentSkipListMap` -|=== +|=== \ No newline at end of file diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java index fccc22b38b..95dcd7a92d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -32,7 +32,7 @@ private IntrospectorUtils() { * @return The decapitalized version of the string. */ public static String decapitalize(String name) { - if ( name == null || name.length() == 0 ) { + if ( name == null || name.isEmpty() ) { return name; } if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && From e0a7d3d0e62695591eef532f63874729c56f64b7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 17 Jan 2025 18:01:47 +0100 Subject: [PATCH 091/214] Use latest Maven Wrapper --- .mvn/wrapper/MavenWrapperDownloader.java | 117 ------ .mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .mvn/wrapper/maven-wrapper.properties | 21 +- mvnw | 451 ++++++++++------------- mvnw.cmd | 281 +++++++------- 5 files changed, 343 insertions(+), 527 deletions(-) delete mode 100644 .mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 .mvn/wrapper/maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index b901097f2d..0000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index abd303b673..d58dfb70ba 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,19 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/mvnw b/mvnw index 41c0f0c23d..19529ddf8c 100755 --- a/mvnw +++ b/mvnw @@ -19,292 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac -fi +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 fi fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" +} - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done + printf %x\\n $h +} - saveddir=`pwd` +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - M2_HOME=`dirname "$PRG"`/.. +die() { + printf %s\\n "$1" >&2 + exit 1 +} - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" fi -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; fi -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 86115719e5..249bdf3822 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -18,165 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From 57d4f88a48f6dc95f02ecbd9d19b6c396be2444f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 20 Jan 2025 07:56:19 +0100 Subject: [PATCH 092/214] Java EA GitHub Actions improvements (#3803) * Use Java 17 for building MapStruct (still Java 8 compatible) * Upgrade to Spring 6 for tests * Adjust excludes and min tests version for some integration tests --- .github/workflows/java-ea.yml | 6 +-- .github/workflows/main.yml | 43 ++++++++----------- .github/workflows/release.yml | 2 +- ...eatureCompilationExclusionCliEnhancer.java | 3 ++ .../itest/tests/MavenIntegrationTest.java | 8 ++++ .../ProcessorInvocationInterceptor.java | 10 ++++- .../test/resources/fullFeatureTest/pom.xml | 10 +++++ .../superTypeGenerationTest/generator/pom.xml | 1 - .../superTypeGenerationTest/usage/pom.xml | 1 - .../generator/pom.xml | 1 - .../targetTypeGenerationTest/usage/pom.xml | 1 - parent/pom.xml | 6 +-- processor/pom.xml | 2 +- .../_1395/{ => spring}/Issue1395Mapper.java | 2 +- .../_1395/{ => spring}/Issue1395Test.java | 2 +- .../_1395/{ => spring}/NotUsedService.java | 2 +- .../test/bugs/_1395/{ => spring}/Source.java | 2 +- .../test/bugs/_1395/{ => spring}/Target.java | 2 +- .../_2807/{ => spring}/Issue2807Test.java | 8 ++-- .../{ => spring}/SpringLifeCycleMapper.java | 8 ++-- .../_2807/{ => spring}/after/AfterMethod.java | 2 +- .../{ => spring}/before/BeforeMethod.java | 2 +- .../beforewithtarget/BeforeWithTarget.java | 2 +- .../test/bugs/_880/{ => spring}/Config.java | 2 +- .../DefaultsToProcessorOptionsMapper.java | 2 +- .../bugs/_880/{ => spring}/Issue880Test.java | 2 +- .../test/bugs/_880/{ => spring}/Poodle.java | 2 +- .../UsesConfigFromAnnotationMapper.java | 2 +- .../decorator/jsr330/Jsr330DecoratorTest.java | 3 ++ ...30DefaultCompileOptionFieldMapperTest.java | 3 ++ ...330CompileOptionConstructorMapperTest.java | 3 ++ .../Jsr330ConstructorMapperTest.java | 3 ++ .../jsr330/field/Jsr330FieldMapperTest.java | 3 ++ .../jsr330/setter/Jsr330SetterMapperTest.java | 3 ++ .../org/mapstruct/ap/testutil/WithSpring.java | 3 ++ readme.md | 2 +- 36 files changed, 96 insertions(+), 63 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Mapper.java (91%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Test.java (92%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/NotUsedService.java (81%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Source.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Target.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/Issue2807Test.java (69%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/SpringLifeCycleMapper.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/after/AfterMethod.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/before/BeforeMethod.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/beforewithtarget/BeforeWithTarget.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Config.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/DefaultsToProcessorOptionsMapper.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Issue880Test.java (97%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Poodle.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/UsesConfigFromAnnotationMapper.java (90%) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index d9b018bfb8..fce2dc06b2 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -7,11 +7,7 @@ env: jobs: test_jdk_ea: - strategy: - fail-fast: false - matrix: - java: [19-ea] - name: 'Linux JDK ${{ matrix.java }}' + name: 'Linux JDK EA' runs-on: ubuntu-latest steps: - name: 'Checkout' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b27dfc075a..9cc098161d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,45 +24,38 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true - linux: - name: 'Linux JDK 11' - runs-on: ubuntu-latest - steps: - - name: 'Checkout' - uses: actions/checkout@v3 - - name: 'Set up JDK 11' - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 11 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} - name: 'Generate coverage report' + if: matrix.java == 17 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' + if: matrix.java == 17 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + if: matrix.java == 17 && 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 - linux-jdk-8: - name: 'Linux JDK 8' + integration_test_jdk: + strategy: + fail-fast: false + matrix: + java: [ 8, 11 ] + name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v3 - - name: 'Set up JDK 11 for building everything' + - name: 'Set up JDK 17 for building everything' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - - name: 'Set up JDK 8 for running integration tests' + - name: 'Set up JDK ${{ matrix.java }} for running integration tests' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 8 + java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest windows: @@ -70,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -82,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f18a12b813..682fa36876 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: 11 + java-version: 17 distribution: 'zulu' cache: maven 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 15d9e42598..77a5e2af06 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -30,6 +30,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces switch ( currentJreVersion ) { case JAVA_8: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); @@ -42,6 +43,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); break; + case JAVA_11: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); default: } 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 0bef2994f6..ab43cc4cba 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.itest.tests; +import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.parallel.Execution; @@ -80,12 +81,15 @@ void jakartaJaxbTest() { } @ProcessorTest(baseDir = "jsr330Test") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void jsr330Test() { } @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) + @DisabledOnJre(JRE.OTHER) void lombokBuilderTest() { } @@ -94,6 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) + @DisabledOnJre(JRE.OTHER) void lombokModuleTest() { } @@ -150,6 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 + @DisabledOnJre(JRE.OTHER) void kotlinDataTest() { } @@ -163,6 +169,8 @@ void defaultPackageTest() { } @ProcessorTest(baseDir = "springTest") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void springTest() { } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 89009aaffe..85ce64952f 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -134,14 +134,20 @@ private void addAdditionalCliArguments(Verifier verifier) } private void configureProcessor(Verifier verifier) { - String compilerId = processorTestContext.getProcessor().getCompilerId(); + ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); + String compilerId = processor.getCompilerId(); if ( compilerId != null ) { - String profile = processorTestContext.getProcessor().getProfile(); + String profile = processor.getProfile(); if ( profile == null ) { profile = "generate-via-compiler-plugin"; } verifier.addCliOption( "-P" + profile ); verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); + } + } } else { verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 1a31b28221..849f7150fc 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -27,6 +27,11 @@ x x x + x + x + x + x + x @@ -49,6 +54,11 @@ ${additionalExclude4} ${additionalExclude5} ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 1b84638ef2..5ab2d0d18f 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index ee0d556b9f..d1e1dd7dff 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index bf0d704851..67df383a18 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index da72f667a4..bd06b79a49 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..5509e783d2 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 5.3.31 + 6.2.2 1.6.0 8.36.1 5.10.1 @@ -47,7 +47,7 @@ jdt_apt 1.8 3.21.7 @@ -400,7 +400,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 org.apache.maven.plugins diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..13deafb413 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 11 + 17 diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java index ebc9ae27e0..4e48020c8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java similarity index 92% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java index 042d0288cd..3417fbd707 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java similarity index 81% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java index 765764ef2b..ba47cf179f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java index 1b676c2b62..8f2a52d462 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java index 2c7b22fd4d..1ba393dbe0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java similarity index 69% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java index 2cb96ce37e..102a9d26eb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java @@ -3,11 +3,11 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java index daabe7f52a..408a2ca3e0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java @@ -3,15 +3,15 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.factory.Mappers; /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java index f7c7457348..05770c6603 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.after; +package org.mapstruct.ap.test.bugs._2807.spring.after; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java index 5252bee150..9fb9e7e883 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.before; +package org.mapstruct.ap.test.bugs._2807.spring.before; import org.mapstruct.BeforeMapping; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java index 69e62bdea1..a3ee8b57ea 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.beforewithtarget; +package org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java index 32f58384b9..7aef1e9e1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.MapperConfig; import org.mapstruct.ReportingPolicy; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java index c54da3f7cb..5f67b41552 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java similarity index 97% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java index 9d8540b409..d2f371abed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import javax.tools.Diagnostic.Kind; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java index e822db1cfe..71166f6d4c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; /** * @author Andreas Gudian diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java index d9a1efe33e..bb9a7def9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index a3bad68b92..be1a67cf09 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.decorator.Address; import org.mapstruct.ap.test.decorator.AddressDto; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330DecoratorTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java index 6b92d9815a..192059c8a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330DefaultCompileOptionFieldMapper.class) @WithJavaxInject @Configuration +@DisabledOnJre(JRE.OTHER) public class Jsr330DefaultCompileOptionFieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java index fe9cf295f4..02b634da8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330CompileOptionConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330CompileOptionConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index a5cfce5c2e..d51843f00c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330ConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index 84e67f63a8..d713f9cc88 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330FieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java index 558206e51c..a6f18ed594 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -42,6 +44,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330SetterMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330SetterMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java index 40db5afd81..04e387d429 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java @@ -10,6 +10,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; /** * Meta annotation that adds the needed Spring Dependencies @@ -23,6 +25,7 @@ "spring-beans", "spring-context" }) +@DisabledOnJre(JRE.OTHER) public @interface WithSpring { } diff --git a/readme.md b/readme.md index adb48b2fd4..907c8411cd 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run ./mvnw clean install From c08ba4ca7e2832ad398a61b6a5a181ebaecd4e0d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 22 Jan 2025 08:51:49 +0100 Subject: [PATCH 093/214] Update setup-java and checkout actions to v4 (#3804) --- .github/workflows/java-ea.yml | 2 +- .github/workflows/main.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fce2dc06b2..fc3a4a0fb1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' uses: oracle-actions/setup-java@v1 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cc098161d..a760175c0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -43,16 +43,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK 17 for building everything' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 - 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@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -62,9 +62,9 @@ jobs: name: 'Windows' runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -76,7 +76,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 From 39551242d7cfbab7e6bbadfed8c771b483cc1a13 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:41:35 +0100 Subject: [PATCH 094/214] #3786: Improve error message when mapping non-iterable to array --- .../processor/MethodRetrievalProcessor.java | 5 +++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3786/ErroneousByteArrayMapper.java | 13 +++++++ .../ap/test/bugs/_3786/Issue3786Test.java | 36 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 2c8ad5dcb9..c8c13e6bf9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -555,6 +555,11 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List Date: Sat, 10 May 2025 14:12:20 +0200 Subject: [PATCH 095/214] #3815: chore(docs): Improved wording about @Condition usage --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e2bd133d4..b5c116ffd3 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -308,7 +308,7 @@ Conditional mapping can also be used to check if a source parameter should be ma A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)` -e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like: +e.g. if you only want to map a String property when it is not `null` and not empty then you can do something like: .Mapper using custom condition check method ==== From 668eeb5de1b1dc79cbf90e68cdf38fbba03c98a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:25:00 +0000 Subject: [PATCH 096/214] Bump com.google.protobuf:protobuf-java from 3.21.7 to 3.25.5 in /parent Bumps [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.7 to 3.25.5. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.7...v3.25.5) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 5509e783d2..5735d4760c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -50,7 +50,7 @@ The processor module needs at least Java 17. --> 1.8 - 3.21.7 + 3.25.5 2.3.2 From 2c84d04463a3ec2edd6c05e726a579b4ba936847 Mon Sep 17 00:00:00 2001 From: Cause Chung Date: Sun, 11 May 2025 17:33:35 +0200 Subject: [PATCH 097/214] #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 a760175c0e..166466348d 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 682fa36876..61f347af3c 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 79d4544f9b..4510c82cc0 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 77a5e2af06..0cc1a87818 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 Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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 Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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 849f7150fc..b5ddae9a79 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 5735d4760c..f6ad60a22a 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 13deafb413..2341c4ddb0 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 ceb190a332..c2646f63db 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.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 TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes 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 0000000000..68d556c542 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util; + +/** + * Helper holding Java collections full qualified class names for conversion registration, + * to achieve Java compatibility. + * + * @author Cause Chung + */ +public final class JavaCollectionConstants { + public static final String SEQUENCED_MAP_FQN = "java.util.SequencedMap"; + public static final String SEQUENCED_SET_FQN = "java.util.SequencedSet"; + + private JavaCollectionConstants() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java new file mode 100644 index 0000000000..4551d5b0b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java @@ -0,0 +1,74 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.ap.test.collection.defaultimplementation.Source; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.Target; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceFoo.class, + TargetFoo.class, + SequencedCollectionsMapper.class +}) +@IssueKey("3420") +class SequencedCollectionsDefaultImplementationTest { + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedMap() { + SequencedMap target = + SequencedCollectionsMapper.INSTANCE.sourceFooMapToTargetFooSequencedMap( createSourceFooMap() ); + + assertResultMap( target ); + } + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedSet() { + SequencedSet target = + SequencedCollectionsMapper.INSTANCE.sourceFoosToTargetFooSequencedSet( createSourceFooList() ); + + assertResultList( target ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private void assertResultMap(Map result) { + assertThat( result ).isNotNull(); + assertThat( result ).hasSize( 2 ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + } + + private Map createSourceFooMap() { + Map map = new HashMap<>(); + map.put( 1L, new SourceFoo( "Bob" ) ); + map.put( 2L, new SourceFoo( "Alice" ) ); + + return map; + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java new file mode 100644 index 0000000000..bbffb56b0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Collection; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SequencedCollectionsMapper { + + SequencedCollectionsMapper INSTANCE = Mappers.getMapper( SequencedCollectionsMapper.class ); + + SequencedSet sourceFoosToTargetFooSequencedSet(Collection foos); + + SequencedMap sourceFooMapToTargetFooSequencedMap(Map foos); +} diff --git a/readme.md b/readme.md index 907c8411cd..16a70f3f0b 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 From 602e29083fe980be0256fd01d37906ceab1ff6ca Mon Sep 17 00:00:00 2001 From: zral <73640367+zyberzebra@users.noreply.github.com> Date: Sun, 11 May 2025 19:59:59 +0200 Subject: [PATCH 098/214] #1140 Add warning when target has no properties --- .../ap/internal/model/BeanMappingMethod.java | 78 ++++++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../test/annotatewith/AnnotateWithTest.java | 5 +- .../ErroneousMapperWithClassOnMethod.java | 10 +-- .../ap/test/bugs/_1111/Issue1111Mapper.java | 25 +++++- .../ap/test/bugs/_1111/Issue1111Test.java | 2 +- .../ap/test/bugs/_1130/Issue1130Mapper.java | 19 +++++ ...roneousIssue1242MapperMultipleSources.java | 3 +- .../ap/test/bugs/_1242/Issue1242Mapper.java | 3 +- .../ap/test/bugs/_1242/Issue1242Test.java | 2 +- .../mapstruct/ap/test/bugs/_1242/TargetB.java | 9 +++ .../ap/test/bugs/_1345/Issue1345Mapper.java | 18 +++++ .../ap/test/bugs/_1460/Issue1460Mapper.java | 21 ++++- .../_1460/java8/Issue1460JavaTimeMapper.java | 11 ++- .../mapstruct/ap/test/bugs/_1590/Book.java | 10 +++ .../ap/test/bugs/_1590/Issue1590Test.java | 2 +- .../org/mapstruct/ap/test/bugs/_1650/C.java | 10 +++ .../mapstruct/ap/test/bugs/_1650/CPrime.java | 10 +++ .../ap/test/bugs/_1650/Issue1650Test.java | 2 +- .../ap/test/bugs/_1821/Issue1821Mapper.java | 20 +++++ .../ap/test/bugs/_1821/Issue1821Test.java | 2 +- .../ap/test/bugs/_611/Issue611Test.java | 4 +- .../ap/test/bugs/_611/SomeClass.java | 40 ++++++++++ .../ap/test/bugs/_611/SomeOtherClass.java | 20 +++++ .../simple/BuilderInfoTargetTest.java | 16 +++- .../simple/SimpleBuilderMapper.java | 3 - .../simple/SimpleImmutableUpdateMapper.java | 19 +++++ .../immutabletarget/ImmutableProductTest.java | 10 +++ .../ap/test/collection/wildcard/Idea.java | 9 +++ .../ap/test/collection/wildcard/Plan.java | 9 +++ .../visibility/ConstructorVisibilityTest.java | 12 +++ .../AbstractDestinationClassNameMapper.java | 2 +- .../AbstractDestinationPackageNameMapper.java | 2 +- .../DestinationClassNameMapper.java | 2 +- .../DestinationClassNameMapperDecorated.java | 2 +- .../DestinationClassNameMapperDecorator.java | 4 +- .../DestinationClassNameMapperWithConfig.java | 2 +- ...tionClassNameMapperWithConfigOverride.java | 2 +- .../destination/DestinationClassNameTest.java | 1 + .../DestinationClassNameWithJsr330Mapper.java | 2 +- .../DestinationPackageNameMapper.java | 2 +- ...DestinationPackageNameMapperDecorated.java | 2 +- ...DestinationPackageNameMapperDecorator.java | 4 +- ...estinationPackageNameMapperWithConfig.java | 2 +- ...onPackageNameMapperWithConfigOverride.java | 2 +- ...estinationPackageNameMapperWithSuffix.java | 2 +- .../DestinationPackageNameTest.java | 1 + .../mapstruct/ap/test/destination/Target.java | 19 +++++ .../ap/test/emptytarget/EmptyTarget.java | 9 +++ .../test/emptytarget/EmptyTargetMapper.java | 18 +++++ .../ap/test/emptytarget/EmptyTargetTest.java | 39 ++++++++++ .../mapstruct/ap/test/emptytarget/Source.java | 36 +++++++++ .../mapstruct/ap/test/emptytarget/Target.java | 34 ++++++++ .../test/emptytarget/TargetWithNoSetters.java | 15 ++++ .../ap/test/imports/SourceTargetMapper.java | 10 ++- .../ap/test/java8stream/wildcard/Idea.java | 9 +++ .../ap/test/java8stream/wildcard/Plan.java | 9 +++ .../ErroneousSourceTargetMapper.java | 16 +++- .../MissingIgnoredSourceTest.java | 2 +- .../objectfactory/ObjectFactoryMapper.java | 13 +++- .../fixture/AbstractParentSource.java | 9 +++ .../fixture/AbstractParentTarget.java | 9 +++ .../fixture/ImplementedParentSource.java | 9 +++ .../fixture/ImplementedParentTarget.java | 9 +++ .../ap/test/versioninfo/SimpleMapper.java | 3 +- .../SuppressTimestampViaMapper.java | 3 +- .../SuppressTimestampViaMapperConfig.java | 3 +- .../ap/test/versioninfo/VersionInfoTest.java | 3 +- .../fixture/SubclassAbstractMapperImpl.java | 13 ++++ .../SubclassImplementedMapperImpl.java | 8 ++ .../fixture/SubclassInterfaceMapperImpl.java | 5 ++ 71 files changed, 657 insertions(+), 85 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/destination/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 4ff2e8079f..08c8ceda59 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -235,9 +235,10 @@ else if ( !method.isUpdateMethod() ) { this.unprocessedTargetProperties = new LinkedHashMap<>( accessors ); + boolean constructorAccessorHadError = false; if ( !method.isUpdateMethod() && !hasFactoryMethod ) { ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap ); - if ( constructorAccessor != null ) { + if ( constructorAccessor != null && !constructorAccessor.hasError ) { this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors; @@ -250,8 +251,10 @@ else if ( !method.isUpdateMethod() ) { else { this.unprocessedConstructorProperties = new LinkedHashMap<>(); } + constructorAccessorHadError = constructorAccessor != null && constructorAccessor.hasError; this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() ); + this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties ); } else { @@ -322,7 +325,7 @@ else if ( !method.isUpdateMethod() ) { // report errors on unmapped properties if ( shouldHandledDefinedMappings ) { - reportErrorForUnmappedTargetPropertiesIfRequired(); + reportErrorForUnmappedTargetPropertiesIfRequired( resultTypeToMap, constructorAccessorHadError ); reportErrorForUnmappedSourcePropertiesIfRequired(); } reportErrorForMissingIgnoredSourceProperties(); @@ -964,7 +967,7 @@ private ConstructorAccessor getConstructorAccessor(Type type) { ) .collect( Collectors.joining( ", " ) ) ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { return getConstructorAccessor( type, accessibleConstructors.get( 0 ) ); @@ -1023,7 +1026,7 @@ else if ( constructorProperties.size() != constructorParameters.size() ) { GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS, type ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { Map constructorAccessors = new LinkedHashMap<>(); @@ -1731,36 +1734,45 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { return method.getOptions().getMapper().unmappedTargetPolicy(); } - private void reportErrorForUnmappedTargetPropertiesIfRequired() { + private void reportErrorForUnmappedTargetPropertiesIfRequired(Type resultType, + boolean constructorAccessorHadError) { // fetch settings from element to implement ReportingPolicyGem unmappedTargetPolicy = getUnmappedTargetPolicy(); - if ( method instanceof ForgedMethod && targetProperties.isEmpty() ) { - //TODO until we solve 1140 we report this error when the target properties are empty - ForgedMethod forgedMethod = (ForgedMethod) method; - if ( forgedMethod.getHistory() == null ) { - Type sourceType = this.method.getParameters().get( 0 ).getType(); - Type targetType = this.method.getReturnType(); - ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, - sourceType.describe(), - targetType.describe(), - targetType.describe(), - sourceType.describe() - ); + if ( targetProperties.isEmpty() ) { + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( forgedMethod.getHistory() == null ) { + Type sourceType = this.method.getParameters().get( 0 ).getType(); + Type targetType = this.method.getReturnType(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, + sourceType.describe(), + targetType.describe(), + targetType.describe(), + sourceType.describe() + ); + } + else { + ForgedMethodHistory history = forgedMethod.getHistory(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, + history.createSourcePropertyErrorMessage(), + history.getTargetType().describe(), + history.createTargetPropertyName(), + history.getTargetType().describe(), + history.getSourceType().describe() + ); + } } - else { - ForgedMethodHistory history = forgedMethod.getHistory(); + else if ( !constructorAccessorHadError ) { ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, - history.createSourcePropertyErrorMessage(), - history.getTargetType().describe(), - history.createTargetPropertyName(), - history.getTargetType().describe(), - history.getSourceType().describe() + method.getExecutable(), + Message.PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES, + resultType.describe() ); } } @@ -1780,7 +1792,8 @@ else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.require reportErrorForUnmappedProperties( unprocessedTargetProperties, unmappedPropertiesMsg, - unmappedForgedPropertiesMsg ); + unmappedForgedPropertiesMsg + ); } } @@ -1907,12 +1920,19 @@ private void reportErrorForUnusedSourceParameters() { } private static class ConstructorAccessor { + private final boolean hasError; private final List parameterBindings; private final Map constructorAccessors; private ConstructorAccessor( List parameterBindings, Map constructorAccessors) { + this( false, parameterBindings, constructorAccessors ); + } + + private ConstructorAccessor(boolean hasError, List parameterBindings, + Map constructorAccessors) { + this.hasError = hasError; this.parameterBindings = parameterBindings; this.constructorAccessors = constructorAccessors; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 221aed2859..883b3e3792 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -92,6 +92,7 @@ public enum Message { PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ), + PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES( "No target property found for target \"%s\".", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index 3687fe2bc0..eaa8832df7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -176,12 +177,12 @@ public void erroneousMapperWithAnnotationOnlyOnClass() { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousMapperWithClassOnMethod.class, - line = 17, + line = 18, message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods." ) } ) - @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class }) + @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class, WithProperties.class }) public void erroneousMapperWithClassOnMethod() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java index d3d53c910a..c34bce304f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java @@ -7,6 +7,7 @@ import org.mapstruct.AnnotateWith; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Ben Zegveld @@ -15,13 +16,6 @@ public interface ErroneousMapperWithClassOnMethod { @AnnotateWith( value = CustomClassOnlyAnnotation.class ) - Target toString(Source value); + WithProperties toString(String string); - class Source { - - } - - class Target { - - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java index a5a624e684..7cdfa82316 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java @@ -24,8 +24,29 @@ public interface Issue1111Mapper { List> listList(List> in); - class Source { } + class Source { + private final String value; - class Target { } + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java index 5619b64175..f5efb21e3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java @@ -27,7 +27,7 @@ public class Issue1111Test { @ProcessorTest public void shouldCompile() { - List> source = Arrays.asList( Arrays.asList( new Source() ) ); + List> source = Arrays.asList( Arrays.asList( new Source( "test" ) ) ); List> target = Issue1111Mapper.INSTANCE.listList( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java index 042d5a2f8b..350d95eebc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java @@ -30,6 +30,16 @@ public void setB(BEntity b) { } static class BEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } static class ADto { @@ -46,6 +56,7 @@ public void setB(BDto b) { class BDto { private final String passedViaConstructor; + private String id; BDto(String passedViaConstructor) { this.passedViaConstructor = passedViaConstructor; @@ -54,6 +65,14 @@ class BDto { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } abstract void mergeA(AEntity source, @MappingTarget ADto target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java index 7259d343b8..7a8ad05542 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; /** * Results in an ambiguous factory method error, as there are two methods with matching source types available. * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class ErroneousIssue1242MapperMultipleSources { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java index 0491b5ffe0..90ea60a2ee 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; /** * Test mapper for properly resolving the best fitting factory method * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class Issue1242Mapper { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java index f27051670c..036a63f312 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java @@ -54,7 +54,7 @@ public void factoryMethodWithSourceParamIsChosen() { diagnostics = { @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, + line = 21, message = "Ambiguous factory methods found for creating TargetB: " + "TargetB anotherTargetBCreator(SourceB source), " + "TargetB TargetFactories.createTargetB(SourceB source, @TargetType Class clazz), " + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java index ab4f639697..6a28adc828 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java @@ -9,6 +9,7 @@ * @author Andreas Gudian */ class TargetB { + protected String value; private final String passedViaConstructor; TargetB(String passedViaConstructor) { @@ -18,4 +19,12 @@ class TargetB { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java index 7699a8f7e9..b2d89c96e6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java @@ -27,8 +27,17 @@ public interface Issue1345Mapper { class A { + private String value; private String readOnlyProperty; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getReadOnlyProperty() { return readOnlyProperty; } @@ -36,8 +45,17 @@ public String getReadOnlyProperty() { class B { + private String value; private String property; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getProperty() { return property; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java index 90ed83d28a..1d0d4fccbb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java @@ -18,13 +18,13 @@ public abstract class Issue1460Mapper { public abstract Target map(Source source); - public abstract String forceUsageOfIssue1460Enum(Issue1460Enum source); + public abstract Value forceUsageOfIssue1460Enum(Issue1460Enum source); - public abstract String forceUsageOfLocale(Locale source); + public abstract Value forceUsageOfLocale(Locale source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract Value forceUsageOfLocalDate(LocalDate source); - public abstract String forceUsageOfDateTime(DateTime source); + public abstract Value forceUsageOfDateTime(DateTime source); public static class Issue1460Enum { } @@ -37,4 +37,17 @@ public static class LocalDate { public static class DateTime { } + + public static class Value { + + private final T source; + + public Value(T source) { + this.source = source; + } + + public T getSource() { + return source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java index 216cda21fd..b9bedfe451 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java @@ -18,8 +18,17 @@ public abstract class Issue1460JavaTimeMapper { public abstract Target map(Source source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract LocalTarget forceUsageOfLocalDate(LocalDate source); public static class LocalDate { } + + public static class LocalTarget { + + private final LocalDate source; + + public LocalTarget(LocalDate source) { + this.source = source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java index 53355ee603..bfe79c1772 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1590; public class Book { + + private final String name; + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java index d77add25de..0a9113797c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java @@ -28,7 +28,7 @@ public class Issue1590Test { @ProcessorTest public void shouldTestMappingLocalDates() { BookShelf source = new BookShelf(); - source.setBooks( Arrays.asList( new Book() ) ); + source.setBooks( Arrays.asList( new Book("Test") ) ); BookShelf target = BookShelfMapper.INSTANCE.map( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java index 705f2e2d69..dbbd74ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class C { + + private final int value; + + public C(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java index 24e4376f88..db504189f8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class CPrime { + + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java index 2d1437eb77..33237e726c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java @@ -27,7 +27,7 @@ public void shouldCompile() { A a = new A(); a.setB( new B() ); - a.getB().setC( new C() ); + a.getB().setC( new C( 10 ) ); APrime aPrime = new APrime(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java index 08eccae32e..406fa7b71b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java @@ -22,11 +22,31 @@ public interface Issue1821Mapper { ExtendedTarget mapExtended(Source source); class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class ExtendedTarget extends Target { } class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java index 4481ed43e2..865ef8f6c1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java @@ -15,7 +15,7 @@ public class Issue1821Test { @ProcessorTest public void shouldNotGiveNullPtr() { - Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source() ); + Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source( "test" ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java index 79a6e045ec..b77da90ad9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java @@ -43,8 +43,8 @@ public void mapperNestedInsideNestedClassIsFound() { */ @ProcessorTest public void rightMapperIsFound() { - SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source(); - SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source(); + SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source( "test" ); + SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source( "test2" ); SomeClass.InnerMapper.Target target1 = SomeClass.InnerMapper.INSTANCE.toTarget( source1 ); SomeOtherClass.InnerMapper.Target target2 = SomeOtherClass.InnerMapper.INSTANCE.toTarget( source2 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java index 0d41128711..e0fb59d4d7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } @@ -33,9 +53,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java index e50187a82a..2ff2d0d831 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java index 4f3d8b85f4..5d0ff919ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java @@ -9,6 +9,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +83,17 @@ public void testUpdateMutableWithBuilder() { } @ProcessorTest + @WithClasses( { + SimpleImmutableUpdateMapper.class + } ) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleImmutableUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 18, + message = "No target property found for target \"SimpleImmutableTarget\"."), + }) + public void updatingTargetWithNoSettersShouldNotFail() { SimpleMutableSource source = new SimpleMutableSource(); @@ -90,7 +104,7 @@ public void updatingTargetWithNoSettersShouldNotFail() { .build(); assertThat( target.getAge() ).isEqualTo( 20 ); - SimpleBuilderMapper.INSTANCE.toImmutable( source, target ); + SimpleImmutableUpdateMapper.INSTANCE.toImmutable( source, target ); assertThat( target.getAge() ).isEqualTo( 20 ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java index 9f7187c634..6185daee97 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java @@ -21,9 +21,6 @@ public interface SimpleBuilderMapper { @Mapping(target = "builder.name", source = "source.fullName") void updateImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); - // This method is fine as if the mapping target has setters it would use them, otherwise it won't - void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); - @Mapping(target = "name", source = "fullName") SimpleImmutableTarget toImmutable(SimpleMutableSource source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java new file mode 100644 index 0000000000..c6eccc96c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.mappingTarget.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleImmutableUpdateMapper { + + SimpleImmutableUpdateMapper INSTANCE = Mappers.getMapper( SimpleImmutableUpdateMapper.class ); + + // This method is fine as if the mapping target has setters it would use them, otherwise it won't + void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java index de22436296..8097dfdfe9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -11,6 +11,9 @@ 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; @@ -41,6 +44,13 @@ public void shouldHandleImmutableTarget() { CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = CupboardNoSetterMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "No target property found for target \"CupboardEntityOnlyGetter\"."), + }) public void shouldIgnoreImmutableTarget() { CupboardDto in = new CupboardDto(); in.setContent( Arrays.asList( "flour", "peas" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java index fc9b89b571..4e21b223db 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java index 29fcfc2f3a..00db02170c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java index b7a254296d..5eebaf5d05 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java @@ -10,6 +10,9 @@ 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; @@ -44,6 +47,15 @@ public void shouldUseSinglePublicConstructorAlways() { @WithClasses({ SimpleWithPublicParameterlessConstructorMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleWithPublicParameterlessConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "No target property found for target " + + "\"SimpleWithPublicParameterlessConstructorMapper.Person\"."), + }) + public void shouldUsePublicParameterConstructorIfPresent() { PersonDto source = new PersonDto(); source.setName( "Bob" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java index 9498b832f7..8dd010cae3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationClassNameMapper { public static final AbstractDestinationClassNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationClassNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java index b59d5cd21b..e26a6bbe71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationPackageNameMapper { public static final AbstractDestinationPackageNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationPackageNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java index a331096a63..99bd8915a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapper { DestinationClassNameMapper INSTANCE = Mappers.getMapper( DestinationClassNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java index ef44767379..454e5e15bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationClassNameMapperDecorated { DestinationClassNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationClassNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java index 216786da24..3ba1433e21 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationClassNameMapperDecorator(DestinationClassNameMapperDecorate } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java index d76392d11d..d13675f0ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapperWithConfig { DestinationClassNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java index b218fff158..a3f845462b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationClassNameMapperWithConfigOverride { DestinationClassNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java index 30611a8d60..1f6e255b95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java @@ -16,6 +16,7 @@ /** * @author Christophe Labouisse on 27/05/2015. */ +@WithClasses( Target.class ) public class DestinationClassNameTest { @ProcessorTest @WithClasses({ DestinationClassNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java index 92c72a877d..3358f68fd4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java @@ -13,5 +13,5 @@ */ @Mapper(implementationName = "Jsr330Impl", componentModel = MappingConstants.ComponentModel.JSR330) public interface DestinationClassNameWithJsr330Mapper { - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java index 8c4740e323..c049036a13 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapper { DestinationPackageNameMapper INSTANCE = Mappers.getMapper( DestinationPackageNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java index 1dd371b48a..98f6236027 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationPackageNameMapperDecorated { DestinationPackageNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationPackageNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java index 4f6dd3242b..667bd99349 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationPackageNameMapperDecorator(DestinationPackageNameMapperDeco } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java index fbe69ce43e..6a5a870520 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithConfig { DestinationPackageNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java index b53dcaae42..79699006b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationPackageNameMapperWithConfigOverride { DestinationPackageNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java index 2a221e5f5b..068f30331d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithSuffix { DestinationPackageNameMapperWithSuffix INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithSuffix.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java index 63c7ece6d5..5e02c88d93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java @@ -15,6 +15,7 @@ * @author Christophe Labouisse on 27/05/2015. */ @IssueKey( "556" ) +@WithClasses( Target.class ) public class DestinationPackageNameTest { @ProcessorTest @WithClasses({ DestinationPackageNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java new file mode 100644 index 0000000000..5b3bb4ba34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.destination; + +public class Target { + + private final Integer source; + + public Target(Integer source) { + this.source = source; + } + + public Integer getSource() { + return source; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java new file mode 100644 index 0000000000..f1b3a0f073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java @@ -0,0 +1,9 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +public class EmptyTarget { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java new file mode 100644 index 0000000000..8c82f81b7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyTargetMapper { + + TargetWithNoSetters mapToTargetWithSetters(Source source); + + EmptyTarget mapToEmptyTarget(Source source); + + Target mapToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java new file mode 100644 index 0000000000..66fa65b796 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +@IssueKey("1140") +@WithClasses({ + EmptyTarget.class, + EmptyTargetMapper.class, + Source.class, + Target.class, + TargetWithNoSetters.class, +}) +class EmptyTargetTest { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 13, + message = "No target property found for target \"TargetWithNoSetters\"."), + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 15, + message = "No target property found for target \"EmptyTarget\".") + }) + void shouldProvideWarnings() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java new file mode 100644 index 0000000000..643cdccf3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +public class Source { + private String label; + private double weight; + private Object content; + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java new file mode 100644 index 0000000000..488a1fa8a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String label; + private final double weight; + private final Object content; + + public Target(String label, double weight, Object content) { + this.label = label; + this.weight = weight; + this.content = content; + } + + public String getLabel() { + return label; + } + + public double getWeight() { + return weight; + } + + public Object getContent() { + return content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java new file mode 100644 index 0000000000..edeb80f0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +public class TargetWithNoSetters { + private int flightNumber; + private String airplaneName; + + public String getAirplaneName() { + return airplaneName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java index 1380ca400a..845544ba9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java @@ -25,7 +25,7 @@ public interface SourceTargetMapper { ParseException sourceToTarget(Named source); //custom types - Map listToMap(List list); + Value listToMap(List list); java.util.List namedsToExceptions(java.util.List source); @@ -36,4 +36,12 @@ public interface SourceTargetMapper { java.util.Map stringsToDates(java.util.Map stringDates); Target sourceToTarget(Source target); + + class Value { + private T value; + + public Value(List list) { + + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java index bb8434fd63..063a5e5615 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java index 8c55ebc273..7aa366ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java index 659867ca40..1196d773cb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java @@ -8,14 +8,24 @@ import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.factory.Mappers; @Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE) public interface ErroneousSourceTargetMapper { - ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); @BeanMapping(ignoreUnmappedSourceProperties = "bar") - Object sourceToTarget(Object source); + Target map(Target source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java index 77f80c5537..5794126604 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java @@ -22,7 +22,7 @@ public class MissingIgnoredSourceTest { diagnostics = { @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, - line = 20, + line = 18, message = "Ignored unknown source property: \"bar\".") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java index 2ea183bfc9..5ea0a54cb2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java @@ -7,10 +7,11 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; import org.mapstruct.TargetType; import org.mapstruct.factory.Mappers; -@Mapper +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface ObjectFactoryMapper { ObjectFactoryMapper INSTANCE = Mappers.getMapper( ObjectFactoryMapper.class ); @@ -44,6 +45,16 @@ public boolean isA() { } abstract class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class TargetA extends Target { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java index 7f9f19a2ab..295b4e2871 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java @@ -7,4 +7,13 @@ public abstract class AbstractParentSource { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java index 3215aee644..a95f58b260 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java @@ -7,4 +7,13 @@ public abstract class AbstractParentTarget { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java index 94dde481b0..a047ee2573 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java @@ -7,4 +7,13 @@ public class ImplementedParentSource extends AbstractParentSource { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java index 7f3d254e21..a187a87091 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java @@ -7,4 +7,13 @@ public class ImplementedParentTarget extends AbstractParentTarget { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java index 3bcea83364..f40525538c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Andreas Gudian @@ -13,5 +14,5 @@ */ @Mapper public interface SimpleMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java index 02a7b467b1..c3f9d39d26 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java @@ -6,11 +6,12 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(suppressTimestampInGenerated = true) public interface SuppressTimestampViaMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java index a471cad028..54dc6b2fb7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(config = SuppressTimestampViaMapperConfig.Config.class) public interface SuppressTimestampViaMapperConfig { - Object toObject(Object object); + WithProperties toObject(WithProperties object); @MapperConfig(suppressTimestampInGenerated = true) interface Config { diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java index f3192b85ad..60781c399f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -18,7 +19,7 @@ * */ @IssueKey( "424" ) -@WithClasses( SimpleMapper.class ) +@WithClasses( { SimpleMapper.class, WithProperties.class } ) public class VersionInfoTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java index a66b646f1e..b5dd2893c8 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -58,6 +58,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -74,6 +76,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } @@ -88,6 +93,9 @@ protected SubSourceSeparate subTargetSeparateToSubSourceSeparate(SubTargetSepara SubSourceSeparate subSourceSeparate = new SubSourceSeparate( separateValue ); + subSourceSeparate.setParentValue( subTargetSeparate.getParentValue() ); + subSourceSeparate.setImplementedParentValue( subTargetSeparate.getImplementedParentValue() ); + return subSourceSeparate; } @@ -102,6 +110,9 @@ protected SubSourceOverride subTargetOtherToSubSourceOverride(SubTargetOther sub SubSourceOverride subSourceOverride = new SubSourceOverride( finalValue ); + subSourceOverride.setParentValue( subTargetOther.getParentValue() ); + subSourceOverride.setImplementedParentValue( subTargetOther.getImplementedParentValue() ); + return subSourceOverride; } @@ -112,6 +123,8 @@ protected SubSource subTargetToSubSource(SubTarget subTarget) { SubSource subSource = new SubSource(); + subSource.setParentValue( subTarget.getParentValue() ); + subSource.setImplementedParentValue( subTarget.getImplementedParentValue() ); subSource.setValue( subTarget.getValue() ); return subSource; diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java index 444b012078..695ef03577 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java @@ -29,6 +29,9 @@ else if (item instanceof SubSourceOther) { else { ImplementedParentTarget implementedParentTarget = new ImplementedParentTarget(); + implementedParentTarget.setParentValue( item.getParentValue() ); + implementedParentTarget.setImplementedParentValue( item.getImplementedParentValue() ); + return implementedParentTarget; } } @@ -40,6 +43,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -56,6 +61,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java index 8366d53ea9..782831d6e3 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java @@ -38,6 +38,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -54,6 +56,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } From 2fb5776350abf59f09cf26c727eb16ebfffadbc3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 11 May 2025 20:07:53 +0200 Subject: [PATCH 099/214] Add release notes for next version --- NEXT_RELEASE_CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..20cbc8c3f8 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,26 @@ ### Features +* Support for Java 21 Sequenced Collections (#3240) + + ### Enhancements +* Add support for locale parameter for numberFormat and dateFormat (#3628) +* Behaviour change: Warning when the target has no target properties (#1140) + + + ### Bugs +* Improve error message when mapping non-iterable to array (#3786) + ### Documentation ### Build +### Behaviour Change + +#### Warning when the target has no target properties + +With this change, if the target bean does not have any target properties, a warning will be shown. +This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. From fce73aee6ab655f3896dee440fa890a1971a281f Mon Sep 17 00:00:00 2001 From: roelmang Date: Sun, 11 May 2025 21:58:47 +0200 Subject: [PATCH 100/214] #3729 Support for using inner class Builder without using static factory method --- NEXT_RELEASE_CHANGELOG.md | 3 + .../chapter-3-defining-a-mapper.asciidoc | 7 +- .../ap/spi/DefaultBuilderProvider.java | 98 +++++++++++++--- .../ErroneousSimpleBuilderMapper.java | 5 +- .../{ => innerclass}/SimpleBuilderMapper.java | 5 +- ...lderThroughInnerClassConstructorTest.java} | 15 +-- ...ImmutablePersonWithInnerClassBuilder.java} | 14 +-- .../ErroneousSimpleBuilderMapper.java | 23 ++++ .../SimpleBuilderMapper.java | 23 ++++ ...BuilderThroughStaticFactoryMethodTest.java | 61 ++++++++++ ...ePersonWithStaticFactoryMethodBuilder.java | 105 ++++++++++++++++++ 11 files changed, 321 insertions(+), 38 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/ErroneousSimpleBuilderMapper.java (72%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/SimpleBuilderMapper.java (74%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutableBuilderTest.java => innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java} (78%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutablePerson.java => innerclass/SimpleImmutablePersonWithInnerClassBuilder.java} (86%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 20cbc8c3f8..2d0555cb24 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -6,6 +6,9 @@ ### Enhancements * Add support for locale parameter for numberFormat and dateFormat (#3628) +* Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, +it will be treated as a potential builder. +Builders through static methods on the type have a precedence. * Behaviour change: Warning when the target has no target properties (#1140) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ea574603ea..81980a35ac 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -421,8 +421,11 @@ If a Builder exists for a certain type, then that builder will be used for the m The default implementation of the `BuilderProvider` assumes the following: -* The type has a parameterless public static builder creation method that returns a builder. -So for example `Person` has a public static method that returns `PersonBuilder`. +* The type has either +** A parameterless public static builder creation method that returns a builder. +e.g. `Person` has a public static method that returns `PersonBuilder`. +** A public static inner class with the name having the suffix "Builder", and a public no-args constructor +e.g. `Person` has an inner class `PersonBuilder` with a public no-args constructor. * The builder type has a parameterless public method (build method) that returns the type being built. In our example `PersonBuilder` has a method returning `Person`. * In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 92cd1ed80d..afcee7d247 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -10,6 +10,8 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -184,32 +186,96 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkPare return null; } - List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); - List builderInfo = new ArrayList<>(); - for ( ExecutableElement method : methods ) { - if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { - TypeElement builderElement = getTypeElement( method.getReturnType() ); - Collection buildMethods = findBuildMethods( builderElement, typeElement ); - if ( !buildMethods.isEmpty() ) { - builderInfo.add( new BuilderInfo.Builder() - .builderCreationMethod( method ) - .buildMethod( buildMethods ) - .build() - ); + // Builder infos which are determined by a static method on the type itself + List methodBuilderInfos = new ArrayList<>(); + // Builder infos which are determined by an inner builder class in the type itself + List innerClassBuilderInfos = new ArrayList<>(); + + for ( Element enclosedElement : typeElement.getEnclosedElements() ) { + if ( ElementKind.METHOD == enclosedElement.getKind() ) { + ExecutableElement method = (ExecutableElement) enclosedElement; + BuilderInfo builderInfo = determineMethodBuilderInfo( method, typeElement ); + if ( builderInfo != null ) { + methodBuilderInfos.add( builderInfo ); } } + else if ( ElementKind.CLASS == enclosedElement.getKind() ) { + if ( !methodBuilderInfos.isEmpty() ) { + // Small optimization to not check the inner classes + // if we already have at least one builder through a method + continue; + } + TypeElement classElement = (TypeElement) enclosedElement; + BuilderInfo builderInfo = determineInnerClassBuilderInfo( classElement, typeElement ); + if ( builderInfo != null ) { + innerClassBuilderInfos.add( builderInfo ); + } + } + } - if ( builderInfo.size() == 1 ) { - return builderInfo.get( 0 ); + if ( methodBuilderInfos.size() == 1 ) { + return methodBuilderInfos.get( 0 ); + } + else if ( methodBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), methodBuilderInfos ); } - else if ( builderInfo.size() > 1 ) { - throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); + else if ( innerClassBuilderInfos.size() == 1 ) { + return innerClassBuilderInfos.get( 0 ); + } + else if ( innerClassBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), innerClassBuilderInfos ); } if ( checkParent ) { return findBuilderInfo( typeElement.getSuperclass() ); } + + return null; + } + + private BuilderInfo determineMethodBuilderInfo(ExecutableElement method, + TypeElement typeElement) { + if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { + TypeElement builderElement = getTypeElement( method.getReturnType() ); + Collection buildMethods = findBuildMethods( builderElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( method ) + .buildMethod( buildMethods ) + .build(); + } + } + + return null; + } + + private BuilderInfo determineInnerClassBuilderInfo(TypeElement innerClassElement, + TypeElement typeElement) { + if ( innerClassElement.getModifiers().contains( Modifier.PUBLIC ) + && innerClassElement.getModifiers().contains( Modifier.STATIC ) + && innerClassElement.getSimpleName().toString().endsWith( "Builder" ) ) { + for ( Element element : innerClassElement.getEnclosedElements() ) { + if ( ElementKind.CONSTRUCTOR == element.getKind() ) { + ExecutableElement constructor = (ExecutableElement) element; + if ( constructor.getParameters().isEmpty() ) { + // We have a no-arg constructor + // Now check if we have build methods + Collection buildMethods = findBuildMethods( innerClassElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( constructor ) + .buildMethod( buildMethods ) + .build(); + } + // If we don't have any build methods + // then we can stop since we are only interested in the no-arg constructor + return null; + } + } + } + } + return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java similarity index 72% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java index 305ae77e80..18e82ed19c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) public interface ErroneousSimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface ErroneousSimpleBuilderMapper { @Mapping(target = "job", ignore = true ), @Mapping(target = "city", ignore = true ) }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java index 0b0e961b30..e2c673401a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) public interface SimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface SimpleBuilderMapper { @Mapping(target = "job", constant = "programmer"), @Mapping(target = "city", expression = "java(\"Bengalore\")") }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java similarity index 78% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java index 7f77621644..0a2c5287bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java @@ -3,11 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.Arrays; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -20,16 +21,16 @@ @WithClasses({ SimpleMutablePerson.class, - SimpleImmutablePerson.class + SimpleImmutablePersonWithInnerClassBuilder.class }) -public class SimpleImmutableBuilderTest { +public class SimpleImmutableBuilderThroughInnerClassConstructorTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); @ProcessorTest @WithClasses({ SimpleBuilderMapper.class }) - public void testSimpleImmutableBuilderHappyPath() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorHappyPath() { SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); SimpleMutablePerson source = new SimpleMutablePerson(); source.setAge( 3 ); @@ -37,7 +38,7 @@ public void testSimpleImmutableBuilderHappyPath() { source.setChildren( Arrays.asList( "Alice", "Tom" ) ); source.setAddress( "Plaza 1" ); - SimpleImmutablePerson targetObject = mapper.toImmutable( source ); + SimpleImmutablePersonWithInnerClassBuilder targetObject = mapper.toImmutable( source ); assertThat( targetObject.getAge() ).isEqualTo( 3 ); assertThat( targetObject.getName() ).isEqualTo( "Bob" ); @@ -53,8 +54,8 @@ public void testSimpleImmutableBuilderHappyPath() { diagnostics = @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousSimpleBuilderMapper.class, - line = 21, + line = 22, message = "Unmapped target property: \"name\".")) - public void testSimpleImmutableBuilderMissingPropertyFailsToCompile() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorMissingPropertyFailsToCompile() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java index 2fe2c8ded2..a9bda42d33 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java @@ -3,12 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.ArrayList; import java.util.List; -public class SimpleImmutablePerson { +public class SimpleImmutablePersonWithInnerClassBuilder { private final String name; private final int age; private final String job; @@ -16,7 +16,7 @@ public class SimpleImmutablePerson { private final String address; private final List children; - SimpleImmutablePerson(Builder builder) { + SimpleImmutablePersonWithInnerClassBuilder(Builder builder) { this.name = builder.name; this.age = builder.age; this.job = builder.job; @@ -25,10 +25,6 @@ public class SimpleImmutablePerson { this.children = new ArrayList<>(builder.children); } - public static Builder builder() { - return new Builder(); - } - public int getAge() { return age; } @@ -66,8 +62,8 @@ public Builder age(int age) { return this; } - public SimpleImmutablePerson build() { - return new SimpleImmutablePerson( this ); + public SimpleImmutablePersonWithInnerClassBuilder build() { + return new SimpleImmutablePersonWithInnerClassBuilder( this ); } public Builder name(String name) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java new file mode 100644 index 0000000000..6fc51ace3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ErroneousSimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "address", ignore = true ), + @Mapping(target = "job", ignore = true ), + @Mapping(target = "city", ignore = true ) + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java new file mode 100644 index 0000000000..4bd200bc7c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "name", source = "fullName"), + @Mapping(target = "job", constant = "programmer"), + @Mapping(target = "city", expression = "java(\"Bengalore\")") + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java new file mode 100644 index 0000000000..90fac6061e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SimpleMutablePerson.class, + SimpleImmutablePersonWithStaticFactoryMethodBuilder.class +}) +public class SimpleImmutableBuilderThroughStaticFactoryMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleBuilderMapper.class }) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodHappyPath() { + SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + source.setAddress( "Plaza 1" ); + + SimpleImmutablePersonWithStaticFactoryMethodBuilder targetObject = mapper.toImmutable( source ); + + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + assertThat( targetObject.getJob() ).isEqualTo( "programmer" ); + assertThat( targetObject.getCity() ).isEqualTo( "Bengalore" ); + assertThat( targetObject.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( targetObject.getChildren() ).contains( "Alice", "Tom" ); + } + + @ProcessorTest + @WithClasses({ ErroneousSimpleBuilderMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSimpleBuilderMapper.class, + line = 22, + message = "Unmapped target property: \"name\".")) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodMissingPropertyFailsToCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java new file mode 100644 index 0000000000..c3dc590210 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java @@ -0,0 +1,105 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleImmutablePersonWithStaticFactoryMethodBuilder { + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + SimpleImmutablePersonWithStaticFactoryMethodBuilder(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.job = builder.job; + this.city = builder.city; + this.address = builder.address; + this.children = new ArrayList<>( builder.children ); + } + + public static Builder builder() { + return new Builder(); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private String name; + private int age; + private String job; + private String city; + private String address; + private List children = new ArrayList<>(); + + private Builder() { + } + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutablePersonWithStaticFactoryMethodBuilder build() { + return new SimpleImmutablePersonWithStaticFactoryMethodBuilder( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder city(String city) { + this.city = city; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public List getChildren() { + throw new UnsupportedOperationException( "This is just a marker method" ); + } + + public Builder addChild(String child) { + this.children.add( child ); + return this; + } + } +} From 6e6fd01a2eb08177d5cc360a97e1721db511239d Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 18 May 2025 00:40:51 +0800 Subject: [PATCH 101/214] #3821: Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` --------- Signed-off-by: TangYang --- .../main/java/org/mapstruct/BeanMapping.java | 12 +++ core/src/main/java/org/mapstruct/Mapper.java | 12 +++ .../main/java/org/mapstruct/MapperConfig.java | 12 +++ .../ap/internal/model/BeanMappingMethod.java | 17 ++++- .../model/source/BeanMappingOptions.java | 9 +++ .../internal/model/source/DefaultOptions.java | 4 + .../model/source/DelegatingOptions.java | 4 + .../model/source/MapperConfigOptions.java | 6 ++ .../internal/model/source/MapperOptions.java | 7 ++ .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../CustomExceptionSubclassMapper.java | 25 ++++++ .../CustomSubclassMappingException.java | 12 +++ .../CustomSubclassMappingExceptionTest.java | 76 +++++++++++++++++++ .../MapperConfigSubclassMapper.java | 29 +++++++ .../MapperSubclassMapper.java | 24 ++++++ 15 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index e94ff98f2d..309458f861 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -132,6 +132,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

    + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No * warning will be issued on missing source or target properties. diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 8a6f48dad9..398dc1870a 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -281,6 +281,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

    + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Determines whether to use field or constructor injection. This is only used on annotated based component models * such as CDI, Spring and JSR 330. diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 915f3dd120..8631562a56 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -249,6 +249,18 @@ MappingInheritanceStrategy mappingInheritanceStrategy() */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

    + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Determines whether to use field or constructor injection. This is only used on annotated based component models * such as CDI, Spring and JSR 330. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 08c8ceda59..34c1ea3ccb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -100,6 +100,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final String finalizedResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final Type subclassExhaustiveException; private final MappingReferences mappingReferences; @@ -378,6 +379,11 @@ else if ( !method.isUpdateMethod() ) { } + TypeMirror subclassExhaustiveException = method.getOptions() + .getBeanMapping() + .getSubclassExhaustiveException(); + Type subclassExhaustiveExceptionType = ctx.getTypeFactory().getType( subclassExhaustiveException ); + List subclasses = new ArrayList<>(); for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { subclasses.add( createSubclassMapping( subclassMappingOptions ) ); @@ -451,7 +457,8 @@ else if ( !sourceParameter.getType().isPrimitive() ) { finalizeMethod, mappingReferences, subclasses, - presenceChecksByParameter + presenceChecksByParameter, + subclassExhaustiveExceptionType ); } @@ -1954,7 +1961,8 @@ private BeanMappingMethod(Method method, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, - Map presenceChecksByParameter) { + Map presenceChecksByParameter, + Type subclassExhaustiveException) { super( method, annotations, @@ -1969,6 +1977,7 @@ private BeanMappingMethod(Method method, this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; + this.subclassExhaustiveException = subclassExhaustiveException; if ( this.finalizerMethod != null ) { this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); @@ -2017,6 +2026,10 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { this.subclassMappings = subclassMappings; } + public Type getSubclassExhaustiveException() { + return subclassExhaustiveException; + } + public List getConstantMappings() { return constantMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index ac27dfff00..73a4d7c12d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -11,6 +11,7 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.BuilderGem; @@ -182,6 +183,14 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { .orElse( next().getSubclassExhaustiveStrategy() ); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveException ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .orElse( next().getSubclassExhaustiveException() ); + } + @Override public ReportingPolicyGem unmappedTargetPolicy() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index c754d3c395..e1f04fd941 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -131,6 +131,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); } + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().getDefaultValue(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy(); if ( nullValueIterableMappingStrategy != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 50c1d84541..34478969fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -106,6 +106,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return next.getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return next.getSubclassExhaustiveException(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { return next.getNullValueIterableMappingStrategy(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index e3ca6162a5..d606658878 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -141,6 +141,12 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return mapperConfig.subclassExhaustiveException().hasValue() ? + mapperConfig.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index ed1af34f79..9c2203efd5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -170,6 +170,13 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().hasValue() ? + mapper.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapper.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 3036e4a2c8..da590bb506 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -42,7 +42,7 @@ else { <#if isAbstractReturnType()> - throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); + throw new <@includeModel object=subclassExhaustiveException />("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); <#else> <#if !existingInstanceMapping> <#if hasConstructorMappings()> diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java new file mode 100644 index 0000000000..52738ef6f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.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.subclassmapping.abstractsuperclass; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CustomExceptionSubclassMapper { + CustomExceptionSubclassMapper INSTANCE = Mappers.getMapper( CustomExceptionSubclassMapper.class ); + + @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java new file mode 100644 index 0000000000..abb675bbb4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.abstractsuperclass; + +public class CustomSubclassMappingException extends RuntimeException { + public CustomSubclassMappingException(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java new file mode 100644 index 0000000000..472c007269 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.abstractsuperclass; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@IssueKey("3821") +@WithClasses({ + Bike.class, + BikeDto.class, + Car.class, + CarDto.class, + Motorcycle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + AbstractVehicle.class, + VehicleDto.class, + CustomSubclassMappingException.class, + CustomExceptionSubclassMapper.class +}) +public class CustomSubclassMappingExceptionTest { + + private static final String EXPECTED_ERROR_MESSAGE = "Not all subclasses are supported for this mapping. " + + "Missing for class org.mapstruct.ap.test.subclassmapping.abstractsuperclass.Motorcycle"; + + @ProcessorTest + void customExceptionIsThrownForUnknownSubclass() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + void customExceptionIsThrownForSingleVehicle() { + AbstractVehicle vehicle = new Motorcycle(); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.map( vehicle ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperConfigSubclassMapper.class }) + void customExceptionIsThrownForMapperConfig() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperConfigSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperSubclassMapper.class }) + void customExceptionIsThrownForMapper() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java new file mode 100644 index 0000000000..b3c1a66bb6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = MapperConfigSubclassMapper.Config.class) +public interface MapperConfigSubclassMapper { + + MapperConfigSubclassMapper INSTANCE = Mappers.getMapper( MapperConfigSubclassMapper.class ); + + @MapperConfig(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + interface Config { + } + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java new file mode 100644 index 0000000000..34ed3fde22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) +public interface MapperSubclassMapper { + + MapperSubclassMapper INSTANCE = Mappers.getMapper( MapperSubclassMapper.class ); + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} From 05f27e96e2108344d69a2963b04ee973c83b0eb0 Mon Sep 17 00:00:00 2001 From: Dennis Melzer Date: Sun, 25 May 2025 14:58:23 +0200 Subject: [PATCH 102/214] #3852 Initialize Optionals with empty instead of null --- .../ap/internal/model/common/Type.java | 17 +++++ .../nullvalue/OptionalDefaultMapper.java | 20 ++++++ .../nullvalue/OptionalDefaultMapperTest.java | 53 ++++++++++++++++ .../nullvalue/SimpleConstructorMapper.java | 29 +++++++++ .../ap/test/optional/nullvalue/Source.java | 61 ++++++++++++++++++ .../ap/test/optional/nullvalue/Target.java | 63 +++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6520834066..d0f65ce97a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -17,6 +17,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -367,6 +371,15 @@ public boolean isArrayType() { return componentType != null; } + private boolean isType(Class type) { + return type.getName().equals( getFullyQualifiedName() ); + } + + private boolean isOptionalType() { + return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || + isType( OptionalLong.class ); + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } @@ -1166,6 +1179,10 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { * FTL. */ public String getNull() { + if ( isOptionalType() ) { + return createReferenceName() + ".empty()"; + } + if ( !isPrimitive() || isArrayType() ) { return "null"; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java new file mode 100644 index 0000000000..7397c2ccd1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.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.nullvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper +public interface OptionalDefaultMapper { + + OptionalDefaultMapper INSTANCE = Mappers.getMapper( OptionalDefaultMapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java new file mode 100644 index 0000000000..78482831b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java @@ -0,0 +1,53 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dennis Melzer + */ +@WithClasses({ + OptionalDefaultMapper.class, + Source.class, + Target.class +}) +@IssueKey("3852") +public class OptionalDefaultMapperTest { + + @ProcessorTest + public void shouldOptionalNotNull() { + Source source = new Source( null, null, null, null, null, null, null ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).isEmpty(); + assertThat( target.getSomeInteger() ).isEmpty(); + assertThat( target.getSomeDouble() ).isEmpty(); + assertThat( target.getSomeBoolean() ).isEmpty(); + assertThat( target.getSomeIntValue() ).isEmpty(); + assertThat( target.getSomeDoubleValue() ).isEmpty(); + assertThat( target.getSomeLongValue() ).isEmpty(); + } + + @ProcessorTest + public void shouldMapOptional() { + Source source = new Source( "someString", 10, 11D, Boolean.TRUE, 10, 100D, 200L ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).contains( "someString" ); + assertThat( target.getSomeInteger() ).contains( 10 ); + assertThat( target.getSomeDouble() ).contains( 11D ); + assertThat( target.getSomeBoolean() ).contains( Boolean.TRUE ); + assertThat( target.getSomeIntValue() ).hasValue( 10 ); + assertThat( target.getSomeDoubleValue() ).hasValue( 100 ); + assertThat( target.getSomeLongValue() ).hasValue( 200 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java new file mode 100644 index 0000000000..158a01d3bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.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.nullvalue; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedproperties.simple._target.TargetObject; +import org.mapstruct.ap.test.nestedproperties.simple.source.SourceRoot; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + injectionStrategy = InjectionStrategy.CONSTRUCTOR, unmappedTargetPolicy = ReportingPolicy.IGNORE +) +public interface SimpleConstructorMapper { + + SimpleConstructorMapper MAPPER = Mappers.getMapper( SimpleConstructorMapper.class ); + + TargetObject toTargetObject(SourceRoot sourceRoot); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java new file mode 100644 index 0000000000..9746d4a152 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +public class Source { + + private final String someString; + private final Integer someInteger; + private final Double someDouble; + private final Boolean someBoolean; + private final Integer someIntValue; + private final Double someDoubleValue; + private final Long someLongValue; + + public Source(String someString, Integer someInteger, Double someDouble, Boolean someBoolean, Integer someIntValue, + Double someDoubleValue, Long someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return Optional.ofNullable( someString ); + } + + public Optional getSomeInteger() { + return Optional.ofNullable( someInteger ); + } + + public Optional getSomeDouble() { + return Optional.ofNullable( someDouble ); + } + + public Optional getSomeBoolean() { + return Optional.ofNullable( someBoolean ); + } + + public OptionalDouble getSomeDoubleValue() { + return someDouble != null ? OptionalDouble.of( someDoubleValue ) : OptionalDouble.empty(); + } + + public OptionalInt getSomeIntValue() { + return someIntValue != null ? OptionalInt.of( someIntValue ) : OptionalInt.empty(); + } + + public OptionalLong getSomeLongValue() { + return someLongValue != null ? OptionalLong.of( someLongValue ) : OptionalLong.empty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java new file mode 100644 index 0000000000..5fd5d1730b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional someString; + private final Optional someInteger; + private final Optional someDouble; + private final Optional someBoolean; + private final OptionalInt someIntValue; + private final OptionalDouble someDoubleValue; + private final OptionalLong someLongValue; + + public Target(Optional someString, Optional someInteger, Optional someDouble, + Optional someBoolean, OptionalInt someIntValue, OptionalDouble someDoubleValue, + OptionalLong someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return someString; + } + + public Optional getSomeInteger() { + return someInteger; + } + + public Optional getSomeDouble() { + return someDouble; + } + + public Optional getSomeBoolean() { + return someBoolean; + } + + public OptionalLong getSomeLongValue() { + return someLongValue; + } + + public OptionalDouble getSomeDoubleValue() { + return someDoubleValue; + } + + public OptionalInt getSomeIntValue() { + return someIntValue; + } +} From 42c87d1da99252e21699cde7d584538b5c961ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eamil=20Can?= Date: Sun, 25 May 2025 15:22:47 +0200 Subject: [PATCH 103/214] #3848: Mark String to number as lossy conversion --- .../ap/internal/util/NativeTypes.java | 1 + .../conversion/lossy/CutleryInventoryDto.java | 9 +++++++++ .../lossy/CutleryInventoryEntity.java | 9 +++++++++ .../lossy/ErroneousKitchenDrawerMapper6.java | 20 +++++++++++++++++++ .../conversion/lossy/LossyConversionTest.java | 15 ++++++++++++++ .../lossy/OversizedKitchenDrawerDto.java | 8 ++++++++ .../lossy/RegularKitchenDrawerEntity.java | 8 ++++++++ 7 files changed, 70 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 0a4ca0cde0..ff01ae0ef3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -474,6 +474,7 @@ private NativeTypes() { tmp3.put( Double.class.getName(), 6 ); tmp3.put( BigInteger.class.getName(), 50 ); tmp3.put( BigDecimal.class.getName(), 51 ); + tmp3.put( String.class.getName(), 51 ); NARROWING_LUT = Collections.unmodifiableMap( tmp3 ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java index 5a96d6deb2..3bc144511e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java @@ -10,6 +10,7 @@ public class CutleryInventoryDto { private short numberOfKnifes; private int numberOfForks; private byte numberOfSpoons; + private int drawerId; private float approximateKnifeLength; @@ -44,4 +45,12 @@ public float getApproximateKnifeLength() { public void setApproximateKnifeLength(float approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java index 9575ed99dc..755dbc3637 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java @@ -10,6 +10,7 @@ public class CutleryInventoryEntity { private int numberOfKnifes; private Long numberOfForks; private short numberOfSpoons; + private String drawerId; private double approximateKnifeLength; @@ -44,4 +45,12 @@ public double getApproximateKnifeLength() { public void setApproximateKnifeLength(double approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java new file mode 100644 index 0000000000..c33f1bd662 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index d27f04374a..629f727273 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -40,12 +40,14 @@ public void testNoErrorCase() { dto.setNumberOfKnifes( (short) 7 ); dto.setNumberOfSpoons( (byte) 3 ); dto.setApproximateKnifeLength( 3.7f ); + dto.setDrawerId( 1 ); CutleryInventoryEntity entity = CutleryInventoryMapper.INSTANCE.map( dto ); assertThat( entity.getNumberOfForks() ).isEqualTo( 5L ); assertThat( entity.getNumberOfKnifes() ).isEqualTo( 7 ); assertThat( entity.getNumberOfSpoons() ).isEqualTo( (short) 3 ); assertThat( entity.getApproximateKnifeLength() ).isCloseTo( 3.7d, withinPercentage( 0.0001d ) ); + assertThat( entity.getDrawerId() ).isEqualTo( "1" ); } @ProcessorTest @@ -74,6 +76,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"String drawerId\". It has a possibly lossy conversion from " + + "String to int.") + }) + public void testConversionFromStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java index 110bb2f07c..c42b24cc57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java @@ -21,6 +21,7 @@ public class OversizedKitchenDrawerDto { private Double depth; private BigDecimal length; private double height; + private String drawerId; public long getNumberOfForks() { return numberOfForks; @@ -70,4 +71,11 @@ public void setHeight(double height) { this.height = height; } + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java index d49a89052d..e3ae10bc1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java @@ -17,6 +17,7 @@ public class RegularKitchenDrawerEntity { private float depth; private Float length; private VerySpecialNumber height; + private int drawerId; public int getNumberOfForks() { return numberOfForks; @@ -66,4 +67,11 @@ public void setHeight(VerySpecialNumber height) { this.height = height; } + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } From 3a5c70224d60aaa834986c5ff88fcb568df9fc85 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 21:40:43 +0800 Subject: [PATCH 104/214] #3809 Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings Signed-off-by: TangYang --- .../model/assignment/UpdateWrapper.ftl | 15 +++- .../ap/test/bugs/_3809/Issue3809Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3809/Issue3809Test.java | 20 ++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 9cdd07c230..ea8eed2438 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -10,7 +10,7 @@ <@lib.handleExceptions> <#if includeSourceNullCheck> <@lib.sourceLocalVarAssignment/> - if ( <#if sourcePresenceCheckerReference?? ><@includeModel object=sourcePresenceCheckerReference /><#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <@handleSourceReferenceNullCheck/> ) { <@assignToExistingTarget/> <@lib.handleAssignment/>; } @@ -32,3 +32,16 @@ ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; } + +<#macro handleSourceReferenceNullCheck> + <@compress single_line=true> + <#if sourcePresenceCheckerReference?? > + <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName + targetType=ext.targetType/> + <#else> + <#if sourceLocalVarName??> ${sourceLocalVarName} <#else> ${sourceReference} != null + + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java new file mode 100644 index 0000000000..10ff9e2628 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3809; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetPropertyName; + +@Mapper +public interface Issue3809Mapper { + void updateMappingFails(Source source, @MappingTarget Target target); + + @Condition + default boolean canMap(Object source, @TargetPropertyName String propertyName) { + return true; + } + + class Source { + private NestedSource param; + + public NestedSource getParam() { + return param; + } + } + + class NestedSource { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } + + class Target { + + private NestedTarget param; + + public NestedTarget getParam() { + return param; + } + + public void setParam(NestedTarget param) { + this.param = param; + } + + } + + class NestedTarget { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java new file mode 100644 index 0000000000..23d97b877c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3809; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses(Issue3809Mapper.class) +@IssueKey("3809") +public class Issue3809Test { + + @ProcessorTest + public void shouldCompileNoError() { + + } +} From 0badba70038875cb2fdf6d9ff328b6a9ba31d245 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 22:35:38 +0800 Subject: [PATCH 105/214] #3849: Resolve duplicate invocation of overloaded lifecycle methods with inheritance Add compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector` to disable the deduplication if needed. Signed-off-by: TangYang --- .../org/mapstruct/ap/MappingProcessor.java | 8 +- .../model/LifecycleMethodResolver.java | 2 +- .../model/ObjectFactoryMethodResolver.java | 7 +- .../model/PresenceCheckMethodResolver.java | 3 +- .../LifecycleOverloadDeduplicateSelector.java | 129 +++++++++++++++ .../source/selector/MethodSelectors.java | 16 +- .../mapstruct/ap/internal/option/Options.java | 9 +- .../creation/MappingResolverImpl.java | 2 +- .../mapstruct/ap/test/bugs/_3849/Child.java | 12 ++ .../ap/test/bugs/_3849/ChildDto.java | 16 ++ .../bugs/_3849/DeduplicateBySourceMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateByTargetMapper.java | 69 ++++++++ .../DeduplicateForCompileArgsMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateGenericMapper.java | 51 ++++++ .../ap/test/bugs/_3849/Issue3849Test.java | 150 ++++++++++++++++++ .../mapstruct/ap/test/bugs/_3849/Parent.java | 21 +++ .../ap/test/bugs/_3849/ParentDto.java | 22 +++ 17 files changed, 642 insertions(+), 13 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 407166f6fa..b34b133262 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -95,6 +95,7 @@ MappingProcessor.VERBOSE, MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, + MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, }) public class MappingProcessor extends AbstractProcessor { @@ -115,6 +116,8 @@ public class MappingProcessor extends AbstractProcessor { protected static final String VERBOSE = "mapstruct.verbose"; protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; + protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = + "mapstruct.disableLifecycleOverloadDeduplicateSelector"; private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -174,6 +177,8 @@ private Options createOptions() { String nullValueIterableMappingStrategy = processingEnv.getOptions() .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); + String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() + .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); return new Options( Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), @@ -189,7 +194,8 @@ private Options createOptions() { NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null + NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, + Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index b713fa5f0c..cfe4f9f8b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -133,7 +133,7 @@ private static List collectLifecycleCallbackMe MappingBuilderContext ctx, Set existingVariableNames) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 4cf653b46b..89f295981f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -5,12 +5,9 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -26,6 +23,8 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.util.Message; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * * @author Sjaak Derksen @@ -126,7 +125,7 @@ public static List> getMatchingFactoryMethods( Meth MappingBuilderContext ctx) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), null ); return selectors.getMatchingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 5906db8219..96826f8f66 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -119,7 +119,8 @@ private static List> findMatchingMethods( MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), - ctx.getMessager() + ctx.getMessager(), + null ); return selectors.getMatchingMethods( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java new file mode 100644 index 0000000000..7ce23af186 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java @@ -0,0 +1,129 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selector for deduplicating overloaded lifecycle callback methods + * whose parameter signatures differ only by type hierarchy. + *

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

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

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

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

    + * The key consists of: + *

      + *
    • The type that defines the method
    • + *
    • The method name
    • + *
    • parameter bindings
    • + *
    + * This ensures that methods are grouped together only if all these aspects match, + * except for differences in type hierarchy, which are handled separately. + */ + private static List buildSignatureKey(SelectedMethod method) { + List key = new ArrayList<>(); + key.add( method.getMethod().getDefiningType() ); + key.add( method.getMethod().getName() ); + for ( ParameterBinding binding : method.getParameterBindings() ) { + key.add( binding.getType() ); + key.add( binding.getVariableName() ); + } + return key; + } + + /** + * Compare the inheritance distance of parameters between two methods to determine if candidateMethod is better. + * Compares parameters in order, returns as soon as a better one is found. + */ + private boolean isInheritanceBetter(SelectedMethod candidateMethod, + SelectedMethod currentBestMethod) { + List candidateBindings = candidateMethod.getParameterBindings(); + List bestBindings = currentBestMethod.getParameterBindings(); + List candidateParams = candidateMethod.getMethod().getParameters(); + List bestParams = currentBestMethod.getMethod().getParameters(); + int paramCount = candidateBindings.size(); + + for ( int i = 0; i < paramCount; i++ ) { + int candidateDistance = candidateBindings.get( i ) + .getType() + .distanceTo( candidateParams.get( i ).getType() ); + int bestDistance = bestBindings.get( i ).getType().distanceTo( bestParams.get( i ).getType() ); + if ( candidateDistance < bestDistance ) { + return true; + } + else if ( candidateDistance > bestDistance ) { + return false; + } + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index c14729a90f..774f25a8c3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -10,6 +10,7 @@ import java.util.List; import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.TypeUtils; @@ -24,20 +25,27 @@ public class MethodSelectors { private final List selectors; public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, - FormattingMessager messager) { - selectors = Arrays.asList( + FormattingMessager messager, Options options) { + List selectorList = new ArrayList<>( Arrays.asList( new MethodFamilySelector(), new TypeSelector( messager ), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils ), new JavaxXmlElementDeclSelector( typeUtils ), new JakartaXmlElementDeclSelector( typeUtils ), - new InheritanceSelector(), + new InheritanceSelector() + ) ); + if ( options != null && !options.isDisableLifecycleOverloadDeduplicateSelector() ) { + selectorList.add( new LifecycleOverloadDeduplicateSelector() ); + } + + selectorList.addAll( Arrays.asList( new CreateOrUpdateSelector(), new SourceRhsSelector(), new FactoryParameterSelector(), new MostSpecificResultTypeSelector() - ); + ) ); + this.selectors = selectorList; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index a544374c0e..095c472c87 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -26,6 +26,7 @@ public class Options { private final boolean verbose; private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; private final NullValueMappingStrategyGem nullValueMapMappingStrategy; + private final boolean disableLifecycleOverloadDeduplicateSelector; //CHECKSTYLE:OFF public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, @@ -36,7 +37,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers boolean disableBuilders, boolean verbose, NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy + NullValueMappingStrategyGem nullValueMapMappingStrategy, + boolean disableLifecycleOverloadDeduplicateSelector ) { //CHECKSTYLE:ON this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; @@ -50,6 +52,7 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers this.verbose = verbose; this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; + this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; } public boolean isSuppressGeneratorTimestamp() { @@ -95,4 +98,8 @@ public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return nullValueMapMappingStrategy; } + + public boolean isDisableLifecycleOverloadDeduplicateSelector() { + return disableLifecycleOverloadDeduplicateSelector; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index d84ba974db..ba903b6048 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -116,7 +116,7 @@ public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtil this.conversions = new Conversions( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null ); this.verboseLogging = verboseLogging; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java new file mode 100644 index 0000000000..393cb16970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class Child extends Parent { + public Child() { + super(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java new file mode 100644 index 0000000000..e8cdae4d17 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class ChildDto extends ParentDto { + public ChildDto(String value) { + super( value ); + } + + public void setValue(String value) { + super.setValue( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java new file mode 100644 index 0000000000..68450c7d9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateBySourceMapper { + + DeduplicateBySourceMapper INSTANCE = Mappers.getMapper( DeduplicateBySourceMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ParentDto mapChild(Child source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSourceInOtherClass" ); + } + + @BeforeMapping + void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSourceInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSource" ); + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSource" ); + } + + @AfterMapping + default void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSource" ); + } + + @AfterMapping + default void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSource" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java new file mode 100644 index 0000000000..0aed8a2957 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateByTargetMapper { + + DeduplicateByTargetMapper INSTANCE = Mappers.getMapper( DeduplicateByTargetMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java new file mode 100644 index 0000000000..276a2c4352 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateForCompileArgsMapper { + + DeduplicateForCompileArgsMapper INSTANCE = Mappers.getMapper( DeduplicateForCompileArgsMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java new file mode 100644 index 0000000000..662137f1a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateGenericMapper { + + DeduplicateGenericMapper INSTANCE = Mappers.getMapper( DeduplicateGenericMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source); + + ChildDto mapChild(Parent source); + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "beforeMappingParentGeneric" ); + } + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChild" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "afterMappingGeneric" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParent" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChild" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java new file mode 100644 index 0000000000..a58c52c921 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java @@ -0,0 +1,150 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3849; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3849") +@WithClasses({ + Parent.class, + ParentDto.class, + Child.class, + ChildDto.class +}) +public class Issue3849Test { + + @ProcessorOption(name = "mapstruct.disableLifecycleOverloadDeduplicateSelector", value = "true") + @ProcessorTest() + @WithClasses(DeduplicateForCompileArgsMapper.class) + void lifecycleMappingOverloadSelectorDisableCompileArgs() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateForCompileArgsMapper.MappingContext mappingContext = + new DeduplicateForCompileArgsMapper.MappingContext(); + ParentDto parentDto = DeduplicateForCompileArgsMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateForCompileArgsMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget", + "afterMappingChildTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest() + @WithClasses( DeduplicateByTargetMapper.class ) + void lifecycleMappingOverloadByTarget() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateByTargetMapper.MappingContext mappingContext = new DeduplicateByTargetMapper.MappingContext(); + ParentDto parentDto = DeduplicateByTargetMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateByTargetMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses( DeduplicateBySourceMapper.class ) + void lifecycleMappingOverloadBySource() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateBySourceMapper.MappingContext mappingContext = new DeduplicateBySourceMapper.MappingContext(); + ParentDto parentDto = DeduplicateBySourceMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentSourceInOtherClass", + "beforeMappingParentSource", + "afterMappingParentSourceInOtherClass", + "afterMappingParentSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateBySourceMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildSourceInOtherClass", + "beforeMappingChildSource", + "afterMappingChildSourceInOtherClass", + "afterMappingChildSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses(DeduplicateGenericMapper.class) + void lifecycleMappingOverloadForGeneric() { + Child child = new Child(); + Parent parent = new Parent(); + + ParentDto parentDto = DeduplicateGenericMapper.INSTANCE.mapParent( parent ); + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentGeneric", + "afterMappingParent" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateGenericMapper.INSTANCE.mapChild( child ); + + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChild", + "afterMappingChild" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java new file mode 100644 index 0000000000..4930677286 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class Parent { + private String value; + + public Parent() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java new file mode 100644 index 0000000000..098917f001 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class ParentDto { + private String value; + + public ParentDto(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} From 6b6600c370cb50b2fbf40f56412238974575ae85 Mon Sep 17 00:00:00 2001 From: Aleksey Ivashin Date: Sun, 25 May 2025 18:05:18 +0300 Subject: [PATCH 106/214] #1958: Add support for ignoring multiple target properties at once --- core/src/main/java/org/mapstruct/Ignored.java | 67 ++++++++++++ .../main/java/org/mapstruct/IgnoredList.java | 54 ++++++++++ core/src/main/java/org/mapstruct/Mapping.java | 3 + .../chapter-5-data-type-conversions.asciidoc | 7 ++ .../ap/internal/gem/GemGenerator.java | 4 + .../processor/MethodRetrievalProcessor.java | 61 ++++++++++- .../org/mapstruct/ap/test/ignored/Animal.java | 64 +++++++++++ .../mapstruct/ap/test/ignored/AnimalDto.java | 63 +++++++++++ .../ap/test/ignored/AnimalMapper.java | 20 ++++ .../ap/test/ignored/ErroneousMapper.java | 26 +++++ .../ap/test/ignored/IgnoredPropertyTest.java | 102 ++++++++++++++++++ .../org/mapstruct/ap/test/ignored/Zoo.java | 48 +++++++++ .../org/mapstruct/ap/test/ignored/ZooDto.java | 48 +++++++++ .../mapstruct/ap/test/ignored/ZooMapper.java | 23 ++++ 14 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/mapstruct/Ignored.java create mode 100644 core/src/main/java/org/mapstruct/IgnoredList.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java diff --git a/core/src/main/java/org/mapstruct/Ignored.java b/core/src/main/java/org/mapstruct/Ignored.java new file mode 100644 index 0000000000..47e4961f43 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Ignored.java @@ -0,0 +1,67 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures the ignored of one bean attribute. + * + *

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

    + * + *

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

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

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

    These two examples are equal. + *

    + *
    
    + * // before Java 8
    + * @Mapper
    + * public interface MyMapper {
    + *     @IgnoredList({
    + *         @Ignored(targets = { "firstProperty" } ),
    + *         @Ignored(targets = { "secondProperty" } )
    + *     })
    + *     HumanDto toHumanDto(Human human);
    + * }
    + * 
    + *
    
    + * // Java 8 and later
    + * @Mapper
    + * public interface MyMapper {
    + *     @Ignored(targets = { "firstProperty" } ),
    + *     @Ignored(targets = { "secondProperty" } )
    + *     HumanDto toHumanDto(Human human);
    + * }
    + * 
    + * + * @author Ivashin Aleksey + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface IgnoredList { + + /** + * The configuration of the bean attributes. + * + * @return The configuration of the bean attributes. + */ + Ignored[] value(); +} diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 6c95b0db2c..f15cfb75cc 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -306,6 +306,9 @@ * This can be useful when certain attributes should not be propagated from source to target or when properties in * the target object are populated using a decorator and thus would be reported as unmapped target property by * default. + *

    + * If you have multiple properties to ignore, + * you can use the {@link Ignored} annotation instead and group them all at once. * * @return {@code true} if the given property should be ignored, {@code false} otherwise */ 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 bc406cf0b0..ad49fe2960 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -280,6 +280,13 @@ This puts the configuration of the nested mapping into one place (method) where instead of re-configuring the same things on all of those upper methods. ==== +[TIP] +==== +When ignoring multiple properties instead of defining multiple `@Mapping` annotations, you can use the `@Ignored` annotation to group them together. +e.g. for the `FishTankMapperWithDocument` example above, you could write: +`@Ignored(targets = { "plant", "ornament", "material" })` +==== + [NOTE] ==== In some cases the `ReportingPolicy` that is going to be used for the generated nested method would be `IGNORE`. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 2ed9bd9a09..a8b62babe9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -26,6 +26,8 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; import org.mapstruct.Mapping; +import org.mapstruct.Ignored; +import org.mapstruct.IgnoredList; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.Named; @@ -53,6 +55,8 @@ @GemDefinition(AnnotateWiths.class) @GemDefinition(Mapper.class) @GemDefinition(Mapping.class) +@GemDefinition(Ignored.class) +@GemDefinition(IgnoredList.class) @GemDefinition(Mappings.class) @GemDefinition(IterableMapping.class) @GemDefinition(BeanMapping.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index c8c13e6bf9..6843ac5bbe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -21,6 +21,8 @@ import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.IgnoredGem; +import org.mapstruct.ap.internal.gem.IgnoredListGem; import org.mapstruct.ap.internal.gem.IterableMappingGem; import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.MappingGem; @@ -76,6 +78,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { - return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method ); + Set processedAnnotations = new RepeatableMappings( beanMapping ) + .getProcessedAnnotations( method ); + processedAnnotations.addAll( new IgnoredConditions( processedAnnotations ) + .getProcessedAnnotations( method ) ); + return processedAnnotations; } /** @@ -823,4 +831,55 @@ protected void addInstance(ConditionGem gem, Element source, Set { + + protected final Set processedAnnotations; + + protected IgnoredConditions( Set processedAnnotations ) { + super( elementUtils, IGNORED_FQN, IGNORED_LIST_FQN ); + this.processedAnnotations = processedAnnotations; + } + + @Override + protected IgnoredGem singularInstanceOn(Element element) { + return IgnoredGem.instanceOn( element ); + } + + @Override + protected IgnoredListGem multipleInstanceOn(Element element) { + return IgnoredListGem.instanceOn( element ); + } + + @Override + protected void addInstance(IgnoredGem gem, Element method, Set mappings) { + IgnoredGem ignoredGem = IgnoredGem.instanceOn( method ); + if ( ignoredGem == null ) { + ignoredGem = gem; + } + String prefix = ignoredGem.prefix().get(); + for ( String target : ignoredGem.targets().get() ) { + String realTarget = target; + if ( !prefix.isEmpty() ) { + realTarget = prefix + "." + target; + } + MappingOptions mappingOptions = MappingOptions.forIgnore( realTarget ); + if ( processedAnnotations.contains( mappingOptions ) || mappings.contains( mappingOptions ) ) { + messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, realTarget ); + } + else { + mappings.add( mappingOptions ); + } + } + } + + @Override + protected void addInstances(IgnoredListGem gem, Element method, Set mappings) { + IgnoredListGem ignoredListGem = IgnoredListGem.instanceOn( method ); + for ( IgnoredGem ignoredGem : ignoredListGem.value().get() ) { + addInstance( ignoredGem, method, mappings ); + } + } + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java new file mode 100644 index 0000000000..223f99f705 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class Animal { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColour; + //CHECKSTYLE:OFN + private String colour; + private String name; + private int size; + private Integer age; + + // private String colour; + public Animal() { + } + + public Animal(String name, int size, Integer age, String colour) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColour = colour; + this.colour = colour; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColour() { + return colour; + } + + public void setColour( String colour ) { + this.colour = colour; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java new file mode 100644 index 0000000000..1651f9b56b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class AnimalDto { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColor; + //CHECKSTYLE:ON + private String name; + private Integer size; + private Integer age; + private String color; + + public AnimalDto() { + + } + + public AnimalDto(String name, Integer size, Integer age, String color) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColor = color; + this.color = color; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java new file mode 100644 index 0000000000..2753b36070 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AnimalMapper { + + AnimalMapper INSTANCE = Mappers.getMapper( AnimalMapper.class ); + + @Ignored( targets = { "publicAge", "age", "publicColor", "color" } ) + AnimalDto animalToDto( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java new file mode 100644 index 0000000000..b113e715aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousMapper { + + ErroneousMapper INSTANCE = Mappers.getMapper( ErroneousMapper.class ); + + @Mapping(target = "name", ignore = true) + @Ignored(targets = { "name", "color", "publicColor" }) + AnimalDto ignoredAndMappingAnimalToDto( Animal animal ); + + @Mapping(target = "publicColor", source = "publicColour") + @Ignored(targets = { "publicColor", "color" }) + AnimalDto ignoredAndMappingAnimalToDtoMap( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java new file mode 100644 index 0000000000..bc6fc2f6b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java @@ -0,0 +1,102 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for ignoring properties during the mapping. + * + * @author Ivashin Aleksey + */ +@WithClasses({ Animal.class, AnimalDto.class, Zoo.class, ZooDto.class, ZooMapper.class}) +public class IgnoredPropertyTest { + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + + AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); + + assertThat( animalDto ).isNotNull(); + assertThat( animalDto.getName() ).isEqualTo( "Bruno" ); + assertThat( animalDto.getSize() ).isEqualTo( 100 ); + assertThat( animalDto.getAge() ).isNull(); + assertThat( animalDto.publicAge ).isNull(); + assertThat( animalDto.getColor() ).isNull(); + assertThat( animalDto.publicColor ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { ErroneousMapper.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Target property \"name\" must not be mapped more than once." ), + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Target property \"publicColor\" must not be mapped more than once." ) + } + ) + public void shouldFailToGenerateMappings() { + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isEqualTo( "test address" ); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute2() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto2( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isNull(); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java new file mode 100644 index 0000000000..377e03b877 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class Zoo { + + private Animal animal; + + private String name; + + private String address; + + public Zoo() { + } + + public Zoo(Animal animal, String name, String address ) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public Animal getAnimal() { + return animal; + } + + public void setAnimal(Animal animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java new file mode 100644 index 0000000000..7c062cab4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class ZooDto { + + private AnimalDto animal; + + private String name; + + private String address; + + public ZooDto() { + } + + public ZooDto(AnimalDto animal, String name, String address) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public AnimalDto getAnimal() { + return animal; + } + + public void setAnimal(AnimalDto animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java new file mode 100644 index 0000000000..f05e045b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ZooMapper { + + ZooMapper INSTANCE = Mappers.getMapper( ZooMapper.class ); + + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto( Zoo zoo ); + + @Ignored( targets = { "address" } ) + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto2( Zoo zoo ); +} From 5464c3cff805a235e908976fe5d55d0162d2a323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:10:24 +0800 Subject: [PATCH 107/214] #3711: Support generic `@Context` Signed-off-by: TangYang --- .../processor/MethodRetrievalProcessor.java | 25 +++++++++------ .../ap/test/bugs/_3711/BaseMapper.java | 12 +++++++ .../ap/test/bugs/_3711/Issue3711Test.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/JpaContext.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/ParentDto.java | 18 +++++++++++ .../ap/test/bugs/_3711/ParentEntity.java | 18 +++++++++++ .../test/bugs/_3711/SourceTargetMapper.java | 16 ++++++++++ 7 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 6843ac5bbe..d7a0329aeb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -114,7 +114,12 @@ public List process(ProcessorContext context, TypeElement mapperTy } List prototypeMethods = retrievePrototypeMethods( mapperTypeElement, mapperOptions ); - return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperOptions, prototypeMethods ); + return retrieveMethods( + typeFactory.getType( mapperTypeElement.asType() ), + mapperTypeElement, + mapperOptions, + prototypeMethods + ); } @Override @@ -166,19 +171,21 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen /** * Retrieves the mapping methods declared by the given mapper type. * - * @param usedMapper The type of interest (either the mapper to implement or a used mapper via @uses annotation) + * @param usedMapperType The type of interest (either the mapper to implement, a used mapper via @uses annotation, + * or a parameter type annotated with @Context) * @param mapperToImplement the top level type (mapper) that requires implementation * @param mapperOptions the mapper config * @param prototypeMethods prototype methods defined in mapper config type * @return All mapping methods declared by the given type */ - private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, + private List retrieveMethods(Type usedMapperType, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { List methods = new ArrayList<>(); + TypeElement usedMapper = usedMapperType.getTypeElement(); for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( usedMapper ) ) { SourceMethod method = getMethod( - usedMapper, + usedMapperType, executable, mapperToImplement, mapperOptions, @@ -195,7 +202,7 @@ private List retrieveMethods(TypeElement usedMapper, TypeElement m TypeElement usesMapperElement = asTypeElement( mapper ); if ( !mapperToImplement.equals( usesMapperElement ) ) { methods.addAll( retrieveMethods( - usesMapperElement, + typeFactory.getType( mapper ), mapperToImplement, mapperOptions, prototypeMethods ) ); @@ -218,13 +225,13 @@ private TypeElement asTypeElement(DeclaredType type) { return (TypeElement) type.asElement(); } - private SourceMethod getMethod(TypeElement usedMapper, + private SourceMethod getMethod(Type usedMapperType, ExecutableElement method, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { - - ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapper.asType(), method ); + TypeElement usedMapper = usedMapperType.getTypeElement(); + ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapperType.getTypeMirror(), method ); List parameters = typeFactory.getParameters( methodType, method ); Type returnType = typeFactory.getReturnType( methodType ); @@ -357,7 +364,7 @@ private ParameterProvidedMethods retrieveContextProvidedMethods( continue; } List contextParamMethods = retrieveMethods( - contextParam.getType().getTypeElement(), + contextParam.getType(), mapperToImplement, mapperConfig, Collections.emptyList() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java new file mode 100644 index 0000000000..e2a04fe336 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import org.mapstruct.Context; + +interface BaseMapper { + E toEntity(T s, @Context JpaContext ctx); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java new file mode 100644 index 0000000000..7b141e9e14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ParentEntity.class, + ParentDto.class, + JpaContext.class, + SourceTargetMapper.class, + BaseMapper.class, +}) +@IssueKey("3711") +class Issue3711Test { + @ProcessorTest + void shouldGenerateContextMethod() { + JpaContext jpaContext = new JpaContext<>(); + SourceTargetMapper.INSTANCE.toEntity( new ParentDto(), jpaContext ); + + assertThat( jpaContext.getInvokedMethods() ) + .containsExactly( "beforeMapping", "afterMapping" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java new file mode 100644 index 0000000000..b21bf639d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +public class JpaContext { + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + void beforeMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "beforeMapping" ); + } + + @AfterMapping + void afterMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "afterMapping" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java new file mode 100644 index 0000000000..664d6e58ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +public class ParentDto { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java new file mode 100644 index 0000000000..aaefa949fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +public class ParentEntity { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java new file mode 100644 index 0000000000..b09333fd56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper extends BaseMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + ParentEntity toDTO(ParentDto dto); +} From 8fc97f5f62743cc72280f5334dbebf9fdc39bfee Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:13:50 +0800 Subject: [PATCH 108/214] #3806: Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters Signed-off-by: TangYang --- .../model/CollectionAssignmentBuilder.java | 1 + .../GetterWrapperForCollectionsAndMaps.java | 17 ++++ .../GetterWrapperForCollectionsAndMaps.ftl | 5 +- .../ap/test/bugs/_3806/Issue3806Mapper.java | 63 ++++++++++++++ .../ap/test/bugs/_3806/Issue3806Test.java | 86 +++++++++++++++++++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 9a0a025845..76e56cd976 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -240,6 +240,7 @@ else if ( hasNoArgsConstructor() ) { result, method.getThrownTypes(), targetType, + nvpms, targetAccessorType.isFieldAssignment() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java index d29a80b420..e1c0c8cb20 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java @@ -9,9 +9,12 @@ import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; + /** * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. @@ -26,6 +29,14 @@ * @author Sjaak Derksen */ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps { + private final boolean ignoreMapNull; + + public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + this( decoratedAssignment, thrownTypesToExclude, targetType, null, fieldAssignment ); + } /** * @param decoratedAssignment source RHS @@ -36,6 +47,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, + NullValuePropertyMappingStrategyGem nvpms, boolean fieldAssignment) { super( @@ -44,6 +56,7 @@ public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, targetType, fieldAssignment ); + this.ignoreMapNull = nvpms == IGNORE; } @Override @@ -54,4 +67,8 @@ public Set getImportTypes() { } return imported; } + + public boolean isIgnoreMapNull() { + return ignoreMapNull; + } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl index 4ef55f3b40..f47a37106e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl @@ -10,10 +10,13 @@ <@lib.sourceLocalVarAssignment/> if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) { <@lib.handleExceptions> - <#if ext.existingInstanceMapping> + <#if ext.existingInstanceMapping && !ignoreMapNull> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); <@lib.handleLocalVarNullCheck needs_explicit_local_var=false> + <#if ext.existingInstanceMapping && ignoreMapNull> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java new file mode 100644 index 0000000000..89f325dfbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3806; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface Issue3806Mapper { + + Issue3806Mapper INSTANCE = Mappers.getMapper( Issue3806Mapper.class ); + + void update(@MappingTarget Target target, Target source); + + class Target { + + private final Collection authors; + private final Map booksByAuthor; + + protected Collection books; + protected Map booksByPublisher; + + public Target(Collection authors, Map booksByAuthor) { + this.authors = authors != null ? new ArrayList<>( authors ) : null; + this.booksByAuthor = booksByAuthor != null ? new HashMap<>( booksByAuthor ) : null; + } + + public Collection getAuthors() { + return authors; + } + + public Map getBooksByAuthor() { + return booksByAuthor; + } + + public Collection getBooks() { + return books; + } + + public void setBooks(Collection books) { + this.books = books; + } + + public Map getBooksByPublisher() { + return booksByPublisher; + } + + public void setBooksByPublisher(Map booksByPublisher) { + this.booksByPublisher = booksByPublisher; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java new file mode 100644 index 0000000000..1df3318c22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java @@ -0,0 +1,86 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3806; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses(Issue3806Mapper.class) +@IssueKey("3806") +class Issue3806Test { + + @ProcessorTest + void shouldNotClearGetterOnlyCollectionsInUpdateMapping() { + Map booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author1", "book1" ); + booksByAuthor.put( "author2", "book2" ); + List authors = new ArrayList<>(); + authors.add( "author1" ); + authors.add( "author2" ); + + List books = new ArrayList<>(); + books.add( "book1" ); + books.add( "book2" ); + Map booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher1", "book1" ); + booksByPublisher.put( "publisher2", "book2" ); + Issue3806Mapper.Target target = new Issue3806Mapper.Target( authors, booksByAuthor ); + target.setBooks( books ); + target.setBooksByPublisher( booksByPublisher ); + + Issue3806Mapper.Target source = new Issue3806Mapper.Target( null, null ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author1", "author2" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author1", "book1" ), + entry( "author2", "book2" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book1", "book2" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher1", "book1" ), + entry( "publisher2", "book2" ) + ); + + booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author3", "book3" ); + authors = new ArrayList<>(); + authors.add( "author3" ); + + books = new ArrayList<>(); + books.add( "book3" ); + booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher3", "book3" ); + source = new Issue3806Mapper.Target( authors, booksByAuthor ); + source.setBooks( books ); + source.setBooksByPublisher( booksByPublisher ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author3" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author3", "book3" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book3" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher3", "book3" ) + ); + } +} From bff88297e367ec856a1c5a5f68fcc9a62662ee70 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 19:29:39 +0800 Subject: [PATCH 109/214] #3807: Properly recognize the type of public generic fields Signed-off-by: TangYang --- .../ap/internal/model/BeanMappingMethod.java | 4 +- .../mapstruct/ap/internal/util/Filters.java | 23 +++++---- .../util/accessor/AbstractAccessor.java | 41 ---------------- .../util/accessor/ElementAccessor.java | 46 ++++++++++++++---- .../accessor/ExecutableElementAccessor.java | 41 ---------------- .../accessor/ParameterElementAccessor.java | 48 ------------------- .../internal/util/accessor/ReadAccessor.java | 10 ++-- .../util/accessor/RecordElementAccessor.java | 38 --------------- .../ap/test/bugs/_3807/Issue3807Mapper.java | 48 +++++++++++++++++++ .../ap/test/bugs/_3807/Issue3807Test.java | 32 +++++++++++++ 10 files changed, 140 insertions(+), 191 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 34c1ea3ccb..b5208d302b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -65,7 +65,7 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -1067,7 +1067,7 @@ private Accessor createConstructorAccessor(Element element, TypeMirror accessedT existingVariableNames ); existingVariableNames.add( safeParameterName ); - return new ParameterElementAccessor( element, accessedType, safeParameterName ); + return new ElementAccessor( element, accessedType, safeParameterName ); } private boolean hasDefaultAnnotationFromAnyPackage(Element element) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 5f7fe74bf2..6492270705 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -12,7 +12,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -23,7 +23,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -64,7 +64,7 @@ public Filters(AccessorNamingUtils accessorNaming, TypeUtils typeUtils, TypeMirr public List getterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isGetterMethod ) - .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) + .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -90,7 +90,10 @@ public Map recordAccessorsIn(Collection recordCom for ( Element recordComponent : recordComponents ) { recordAccessors.put( recordComponent.getSimpleName().toString(), - ReadAccessor.fromRecordComponent( recordComponent ) + ReadAccessor.fromRecordComponent( + recordComponent, + typeUtils.asMemberOf( (DeclaredType) typeMirror, recordComponent ) + ) ); } @@ -101,10 +104,10 @@ private TypeMirror getReturnType(ExecutableElement executableElement) { return getWithinContext( executableElement ).getReturnType(); } - public List fieldsIn(List accessors, Function creator) { + public List fieldsIn(List accessors, BiFunction creator) { return accessors.stream() .filter( Fields::isFieldAccessor ) - .map( creator ) + .map( variableElement -> creator.apply( variableElement, getWithinContext( variableElement ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -117,7 +120,7 @@ public List presenceCheckMethodsIn(List el public List setterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isSetterMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), SETTER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -129,10 +132,14 @@ private ExecutableType getWithinContext( ExecutableElement executableElement ) { return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); } + private TypeMirror getWithinContext( VariableElement variableElement ) { + return typeUtils.asMemberOf( (DeclaredType) typeMirror, variableElement ); + } + public List adderMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isAdderMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), ADDER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), ADDER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java deleted file mode 100644 index 04c4c99992..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.internal.util.accessor; - -import java.util.Set; - -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; - -/** - * This is an abstract implementation of an {@link Accessor} that provides the common implementation. - * - * @author Filip Hrisafov - */ -abstract class AbstractAccessor implements Accessor { - - protected final T element; - - AbstractAccessor(T element) { - this.element = element; - } - - @Override - public String getSimpleName() { - return element.getSimpleName().toString(); - } - - @Override - public Set getModifiers() { - return element.getModifiers(); - } - - @Override - public T getElement() { - return element; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java index 24e71cc85f..4b363815b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -5,31 +5,61 @@ */ package org.mapstruct.ap.internal.util.accessor; +import java.util.Set; import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** - * An {@link Accessor} that wraps a {@link VariableElement}. - * + * An {@link Accessor} that wraps a {@link Element}. + * Used for getter, setter, filed, constructor, record-class, etc. * @author Filip Hrisafov + * @author Tang Yang */ -public class ElementAccessor extends AbstractAccessor { +public class ElementAccessor implements Accessor { + private final Element element; + private final String name; private final AccessorType accessorType; + private final TypeMirror accessedType; + + public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) { + this( variableElement, accessedType, AccessorType.FIELD ); + } - public ElementAccessor(VariableElement variableElement) { - this( variableElement, AccessorType.FIELD ); + public ElementAccessor(Element element, TypeMirror accessedType, String name) { + this.element = element; + this.name = name; + this.accessedType = accessedType; + this.accessorType = AccessorType.PARAMETER; } - public ElementAccessor(Element element, AccessorType accessorType) { - super( element ); + public ElementAccessor(Element element, TypeMirror accessedType, AccessorType accessorType) { + this.element = element; + this.accessedType = accessedType; this.accessorType = accessorType; + this.name = null; } @Override public TypeMirror getAccessedType() { - return element.asType(); + return accessedType != null ? accessedType : element.asType(); + } + + @Override + public String getSimpleName() { + return name != null ? name : element.getSimpleName().toString(); + } + + @Override + public Set getModifiers() { + return element.getModifiers(); + } + + @Override + public Element getElement() { + return element; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java deleted file mode 100644 index af37acbce1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.internal.util.accessor; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps an {@link ExecutableElement}. - * - * @author Filip Hrisafov - */ -public class ExecutableElementAccessor extends AbstractAccessor { - - private final TypeMirror accessedType; - private final AccessorType accessorType; - - public ExecutableElementAccessor(ExecutableElement element, TypeMirror accessedType, AccessorType accessorType) { - super( element ); - this.accessedType = accessedType; - this.accessorType = accessorType; - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return accessorType; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java deleted file mode 100644 index 9991370059..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class ParameterElementAccessor extends AbstractAccessor { - - protected final String name; - protected final TypeMirror accessedType; - - public ParameterElementAccessor(Element element, TypeMirror accessedType, String name) { - super( element ); - this.name = name; - this.accessedType = accessedType; - } - - @Override - public String getSimpleName() { - return name != null ? name : super.getSimpleName(); - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.PARAMETER; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 5177bfc75b..31edf3a6da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -17,8 +17,8 @@ public interface ReadAccessor extends Accessor { String getReadValueSource(); - static ReadAccessor fromField(VariableElement variableElement) { - return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { + static ReadAccessor fromField(VariableElement variableElement, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement, accessedType ) ) { @Override public String getReadValueSource() { return getSimpleName(); @@ -26,8 +26,8 @@ public String getReadValueSource() { }; } - static ReadAccessor fromRecordComponent(Element element) { - return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { + static ReadAccessor fromRecordComponent(Element element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; @@ -36,7 +36,7 @@ public String getReadValueSource() { } static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { - return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java deleted file mode 100644 index d163f462f9..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class RecordElementAccessor extends AbstractAccessor { - - public RecordElementAccessor(Element element) { - super( element ); - } - - @Override - public TypeMirror getAccessedType() { - return element.asType(); - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.GETTER; - } - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java new file mode 100644 index 0000000000..83ee1f32bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3807; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3807Mapper { + Issue3807Mapper INSTANCE = Mappers.getMapper( Issue3807Mapper.class ); + + TargetWithoutSetter mapNoSetter(Source target); + + NormalTarget mapNormalSource(Source target); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + //CHECKSTYLE:OFF + class TargetWithoutSetter { + public T value; + } + //CHECKSTYLE:ON + + class NormalTarget { + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java new file mode 100644 index 0000000000..3b895f3e11 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3807; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses(Issue3807Mapper.class) +@IssueKey("3087") +class Issue3807Test { + + @ProcessorTest + void fieldAndSetterShouldWorkWithGeneric() { + Issue3807Mapper.Source source = new Issue3807Mapper.Source( "value" ); + Issue3807Mapper.TargetWithoutSetter targetWithoutSetter = + Issue3807Mapper.INSTANCE.mapNoSetter( source ); + + assertThat( targetWithoutSetter ).isNotNull(); + assertThat( targetWithoutSetter.value ).isEqualTo( "value" ); + + Issue3807Mapper.NormalTarget normalTarget = Issue3807Mapper.INSTANCE.mapNormalSource( source ); + + assertThat( normalTarget ).isNotNull(); + assertThat( normalTarget.getValue() ).isEqualTo( "value" ); + } +} From ce84c81de2ee809c1457dfb9078ec1ae785ad323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 23:52:05 +0800 Subject: [PATCH 110/214] #3659: Support `@AnnotatedWith` on decorators Signed-off-by: TangYang --- .../ap/internal/model/Decorator.java | 20 ++- ...nnotationBasedComponentModelProcessor.java | 6 +- .../processor/JakartaComponentProcessor.java | 3 +- .../processor/Jsr330ComponentProcessor.java | 3 +- .../processor/MapperCreationProcessor.java | 5 + .../processor/SpringComponentProcessor.java | 130 ++++++++++++------ .../ap/test/decorator/AnnotatedMapper.java | 43 ++++++ .../decorator/AnnotatedMapperDecorator.java | 18 +++ .../DecoratedWithAnnotatedWithTest.java | 32 +++++ .../ap/test/decorator/TestAnnotation.java | 20 +++ .../JakartaAnnotateWithMapper.java | 40 ++++++ ...akartaAnnotateWithWithMapperDecorator.java | 31 +++++ .../JakartaDecoratorAnnotateWithTest.java | 59 ++++++++ .../decorator/jsr330/Jsr330DecoratorTest.java | 10 +- .../Jsr330AnnotateWithMapper.java | 28 ++++ .../Jsr330AnnotateWithMapperDecorator.java | 32 +++++ .../Jsr330DecoratorAnnotateWithTest.java | 95 +++++++++++++ .../spring/annotatewith/AnnotateMapper.java | 25 ++++ .../annotatewith/AnnotateMapperDecorator.java | 30 ++++ .../annotatewith/CustomAnnotateMapper.java | 25 ++++ .../CustomAnnotateMapperDecorator.java | 28 ++++ .../spring/annotatewith/CustomComponent.java | 20 +++ .../spring/annotatewith/CustomPrimary.java | 19 +++ .../SpringDecoratorAnnotateWithTest.java | 118 ++++++++++++++++ 24 files changed, 791 insertions(+), 49 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java index b7dc0effcb..df7940da31 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java @@ -7,14 +7,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.SortedSet; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.version.VersionInformation; /** @@ -33,6 +34,7 @@ public static class Builder extends GeneratedTypeBuilder { private String implName; private String implPackage; private boolean suppressGeneratorTimestamp; + private Set customAnnotations; public Builder() { super( Builder.class ); @@ -68,6 +70,11 @@ public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { return this; } + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; + return this; + } + public Decorator build() { String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER, Mapper.getFlatName( mapperElement ) ); @@ -95,7 +102,8 @@ public Decorator build() { suppressGeneratorTimestamp, Accessibility.fromModifiers( mapperElement.getModifiers() ), extraImportedTypes, - decoratorConstructor + decoratorConstructor, + customAnnotations ); } } @@ -110,7 +118,8 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type Options options, VersionInformation versionInformation, boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImports, - DecoratorConstructor decoratorConstructor) { + DecoratorConstructor decoratorConstructor, + Set customAnnotations) { super( typeFactory, packageName, @@ -128,6 +137,11 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type this.decoratorType = decoratorType; this.mapperType = mapperType; + + // Add custom annotations + if ( customAnnotations != null ) { + customAnnotations.forEach( this::addAnnotation ); + } } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index 014ceda58e..bbd695412a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.AnnotatedConstructor; import org.mapstruct.ap.internal.model.AnnotatedSetter; import org.mapstruct.ap.internal.model.Annotation; @@ -24,7 +25,6 @@ import org.mapstruct.ap.internal.model.MapperReference; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.source.MapperOptions; /** @@ -88,7 +88,7 @@ else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) { Decorator decorator = mapper.getDecorator(); - for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { + for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) { decorator.addAnnotation( typeAnnotation ); } @@ -308,7 +308,7 @@ protected Field replacementMapperReference(Field originalReference, List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Collections.emptyList(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java index 999c0a51df..86c95d612b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -39,7 +40,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 00ba72a079..7770eff16c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -42,7 +43,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 3b38a05b59..4fbed6cbec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder; +import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.BeanMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; @@ -287,6 +288,9 @@ else if ( constructor.getParameters().size() == 1 ) { messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_CONSTRUCTOR ); } + // Get annotations from the decorator class + Set decoratorAnnotations = additionalAnnotationsBuilder.getProcessedAnnotations( decoratorElement ); + Decorator decorator = new Decorator.Builder() .elementUtils( elementUtils ) .typeFactory( typeFactory ) @@ -300,6 +304,7 @@ else if ( constructor.getParameters().size() == 1 ) { .implPackage( mapperOptions.implementationPackage() ) .extraImports( getExtraImports( element, mapperOptions ) ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( decoratorAnnotations ) .build(); return decorator; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 84a672bcc9..c31ac51f06 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -9,20 +9,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; - import static javax.lang.model.element.ElementKind.PACKAGE; /** @@ -35,6 +37,9 @@ */ public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + private static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component"; + private static final String SPRING_PRIMARY_ANNOTATION = "org.springframework.context.annotation.Primary"; + @Override protected String getComponentModelIdentifier() { return MappingConstantsGem.ComponentModelGem.SPRING; @@ -54,12 +59,37 @@ protected List getTypeAnnotations(Mapper mapper) { return typeAnnotations; } + /** + * Returns the annotations that need to be added to the generated decorator, filtering out any annotations + * that are already present or represented as meta-annotations. + * + * @param decorator the decorator to process + * @return A list of annotations that should be added to the generated decorator. + */ @Override - protected List getDecoratorAnnotations() { - return Arrays.asList( - component(), - primary() - ); + protected List getDecoratorAnnotations(Decorator decorator) { + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + desiredAnnotationNames.add( SPRING_PRIMARY_ANNOTATION ); + List decoratorAnnotations = decorator.getAnnotations(); + if ( !decoratorAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : decoratorAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return Collections.emptyList(); + } + } + } + + return desiredAnnotationNames.stream() + .map( this::createAnnotation ) + .collect( Collectors.toList() ); } @Override @@ -82,8 +112,12 @@ protected boolean requiresGenerationOfDecoratorClass() { return true; } + private Annotation createAnnotation(String canonicalName) { + return new Annotation( getTypeFactory().getType( canonicalName ) ); + } + private Annotation autowired() { - return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) ); + return createAnnotation( "org.springframework.beans.factory.annotation.Autowired" ); } private Annotation qualifierDelegate() { @@ -96,34 +130,51 @@ private Annotation qualifierDelegate() { ) ) ); } - private Annotation primary() { - return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) ); - } - private Annotation component() { - return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ); + return createAnnotation( SPRING_COMPONENT_ANNOTATION ); } private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) { - Set handledElements = new HashSet<>(); - return mapper.getAnnotations() - .stream() - .anyMatch( - annotation -> isOrIncludesComponentAnnotation( annotation, handledElements ) - ); - } + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + + List mapperAnnotations = mapper.getAnnotations(); + if ( !mapperAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : mapperAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return true; + } + } + } - private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set handledElements) { - return isOrIncludesComponentAnnotation( - annotation.getType().getTypeElement(), handledElements - ); + return false; } - private boolean isOrIncludesComponentAnnotation(Element element, Set handledElements) { - if ( "org.springframework.stereotype.Component".equals( - ( (TypeElement) element ).getQualifiedName().toString() - )) { - return true; + /** + * Removes all the annotations and meta-annotations from {@code annotations} which are on the given element. + * + * @param element the element to check + * @param annotations the annotations to check for + * @param handledElements set of already handled elements to avoid infinite recursion + */ + private void removeAnnotationsPresentOnElement(Element element, Set annotations, + Set handledElements) { + if ( annotations.isEmpty() ) { + return; + } + if ( element instanceof TypeElement && + annotations.remove( ( (TypeElement) element ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { @@ -132,17 +183,16 @@ private boolean isOrIncludesComponentAnnotation(Element element, Set ha if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) && !handledElements.contains( annotationMirrorElement ) ) { handledElements.add( annotationMirrorElement ); - boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation( - annotationMirrorElement, handledElements - ); - - if ( isOrIncludesComponentAnnotation ) { - return true; + if ( annotations.remove( ( (TypeElement) annotationMirrorElement ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } + + removeAnnotationsPresentOnElement( element, annotations, handledElements ); } } - - return false; } private PackageElement getPackageOf( Element element ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java new file mode 100644 index 0000000000..6da0d70c93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +@DecoratedWith(AnnotatedMapperDecorator.class) +public interface AnnotatedMapper { + + AnnotatedMapper INSTANCE = Mappers.getMapper( AnnotatedMapper.class ); + + Target toTarget(Source source); + + class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java new file mode 100644 index 0000000000..6bf7a1fd66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.AnnotateWith; + +@AnnotateWith(value = TestAnnotation.class, elements = @AnnotateWith.Element(strings = "decoratorValue")) +public abstract class AnnotatedMapperDecorator implements AnnotatedMapper { + + private final AnnotatedMapper delegate; + + public AnnotatedMapperDecorator(AnnotatedMapper delegate) { + this.delegate = delegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java new file mode 100644 index 0000000000..1e3d574a6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotatedWith on decorator classes. + */ +@IssueKey("3659") +@WithClasses({ + TestAnnotation.class, + AnnotatedMapper.class, + AnnotatedMapperDecorator.class +}) +public class DecoratedWithAnnotatedWithTest { + + @ProcessorTest + public void shouldApplyAnnotationFromDecorator() { + Class implementationClass = AnnotatedMapper.INSTANCE.getClass(); + + assertThat( implementationClass ).hasAnnotation( TestAnnotation.class ); + assertThat( implementationClass.getAnnotation( TestAnnotation.class ).value() ).isEqualTo( "decoratorValue" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java new file mode 100644 index 0000000000..742184d261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation for testing @AnnotatedWith on decorators. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java new file mode 100644 index 0000000000..a1a754158b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jakarta.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using Jakarta component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +@DecoratedWith(JakartaAnnotateWithWithMapperDecorator.class) +public interface JakartaAnnotateWithMapper { + + /** + * Maps a person to a person DTO. + * + * @param person the person to map + * @return the person DTO + */ + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + /** + * Maps an address to an address DTO. + * + * @param address the address to map + * @return the address DTO + */ + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java new file mode 100644 index 0000000000..89a42aaf6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jakarta.annotatewith; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link JakartaAnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class JakartaAnnotateWithWithMapperDecorator implements JakartaAnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.annotatewith.JakartaAnnotateWithMapperImpl_") + private JakartaAnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..d98e26b217 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java @@ -0,0 +1,59 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jakarta.annotatewith; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test for the application of @AnnotateWith on decorator classes with Jakarta component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + JakartaAnnotateWithMapper.class, + TestAnnotation.class, + JakartaAnnotateWithWithMapperDecorator.class +}) +@WithJakartaInject +public class JakartaDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( JakartaAnnotateWithMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@TestAnnotation" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( JakartaAnnotateWithMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index be1a67cf09..a29310b9ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -18,6 +18,7 @@ import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330DecoratorAnnotateWithTest; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -27,6 +28,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import static java.lang.System.lineSeparator; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +47,13 @@ PersonMapperDecorator.class }) @IssueKey("592") -@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) +@ComponentScan( + basePackageClasses = Jsr330DecoratorTest.class, + excludeFilters = @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = { Jsr330DecoratorAnnotateWithTest.class } + ) +) @Configuration @WithJavaxInject @DisabledOnJre(JRE.OTHER) diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java new file mode 100644 index 0000000000..7e6ab8b7bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jsr330.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using JSR-330 component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(Jsr330AnnotateWithMapperDecorator.class) +public interface Jsr330AnnotateWithMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java new file mode 100644 index 0000000000..96cee9f824 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jsr330.annotatewith; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link Jsr330AnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class Jsr330AnnotateWithMapperDecorator implements Jsr330AnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330AnnotateWithMapperImpl_") + private Jsr330AnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..19d77dd5cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java @@ -0,0 +1,95 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jsr330.annotatewith; + +import java.util.Calendar; +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with JSR-330 component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + Jsr330AnnotateWithMapper.class, + Jsr330AnnotateWithMapperDecorator.class, + TestAnnotation.class +}) +@ComponentScan(basePackageClasses = Jsr330DecoratorAnnotateWithTest.class) +@Configuration +@WithJavaxInject +@DisabledOnJre(JRE.OTHER) +public class Jsr330DecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private Jsr330AnnotateWithMapper jsr330AnnotateWithMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldContainCustomAnnotation() { + generatedSource.forMapper( Jsr330AnnotateWithMapper.class ) + .content() + .contains( "@TestAnnotation" ); + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = jsr330AnnotateWithMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java new file mode 100644 index 0000000000..0ba8e88299 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(AnnotateMapperDecorator.class) +public interface AnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java new file mode 100644 index 0000000000..cff0a4c148 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = "decoratorComponent")) +@AnnotateWith(value = Primary.class) +public abstract class AnnotateMapperDecorator implements AnnotateMapper { + + @Autowired + @Qualifier("delegate") + private AnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java new file mode 100644 index 0000000000..9e2e35676c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(CustomAnnotateMapperDecorator.class) +public interface CustomAnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java new file mode 100644 index 0000000000..3668d98b04 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@AnnotateWith(value = CustomComponent.class, elements = @AnnotateWith.Element(strings = "customComponentDecorator")) +@AnnotateWith(value = CustomPrimary.class) +public abstract class CustomAnnotateMapperDecorator implements CustomAnnotateMapper { + + @Autowired + @Qualifier("delegate") + private CustomAnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java new file mode 100644 index 0000000000..be71249f2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomComponent { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java new file mode 100644 index 0000000000..4e18bdbc7d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Primary; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Primary +public @interface CustomPrimary { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..7b3911c22b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java @@ -0,0 +1,118 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with Spring component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + AnnotateMapper.class, + AnnotateMapperDecorator.class, + CustomComponent.class, + CustomPrimary.class, + CustomAnnotateMapper.class, + CustomAnnotateMapperDecorator.class +}) +@WithSpring +@ComponentScan(basePackageClasses = SpringDecoratorAnnotateWithTest.class) +@Configuration +public class SpringDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private AnnotateMapper annotateMapper; + + @Autowired + private CustomAnnotateMapper customAnnotateMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldNotDuplicateComponentAnnotation() { + generatedSource.forMapper( AnnotateMapper.class ) + .content() + .contains( "@Component(value = \"decoratorComponent\")", "@Primary" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + } + + @ProcessorTest + public void shouldNotDuplicateCustomComponentAnnotation() { + generatedSource.forMapper( CustomAnnotateMapper.class ) + .content() + .contains( "@CustomComponent(value = \"customComponentDecorator\")", "@CustomPrimary" ) + .doesNotContain( "@Component" ); + } + + @ProcessorTest + public void shouldInvokeAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = annotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldInvokeCustomAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = customAnnotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} From 9847eaf195cb891c5a47df96db2b90a1b335b75b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 May 2025 18:14:13 +0200 Subject: [PATCH 111/214] #3876: Move Windows and Mac OS builds outside of the main workflow --- .github/workflows/macos.yml | 20 ++++++++++++++++++++ .github/workflows/main.yml | 24 ------------------------ .github/workflows/windows.yml | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000000..833bb6ba39 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,20 @@ +name: Mac OS CI + +on: push + +env: + MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + mac: + name: 'Mac OS' + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: 'Set up JDK 21' + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + - name: 'Test' + run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 166466348d..b6a2ef371e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,27 +58,3 @@ jobs: java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest - windows: - name: 'Windows' - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: 'Set up JDK 21' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 21 - - name: 'Test' - run: ./mvnw %MAVEN_ARGS% install - mac: - name: 'Mac OS' - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - name: 'Set up JDK 21' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 21 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..bda38f8783 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,20 @@ +name: Windows CI + +on: push + +env: + MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + windows: + name: 'Windows' + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: 'Set up JDK 21' + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + - name: 'Test' + run: ./mvnw %MAVEN_ARGS% install From 46ce011e4bd9b9fbcc0f0d337d5ad631f5d214b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Jun 2025 07:52:18 +0200 Subject: [PATCH 112/214] Refactor options and add an enum (#3877) --- .../org/mapstruct/ap/MappingProcessor.java | 101 ++++++------------ .../internal/model/source/DefaultOptions.java | 20 ++-- .../ap/internal/option/MappingOption.java | 43 ++++++++ .../mapstruct/ap/internal/option/Options.java | 89 +++++++-------- 4 files changed, 128 insertions(+), 125 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index b34b133262..a66762d739 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -13,17 +13,16 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -35,9 +34,8 @@ import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; -import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.option.MappingOption; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; @@ -84,19 +82,6 @@ * @author Gunnar Morling */ @SupportedAnnotationTypes("org.mapstruct.Mapper") -@SupportedOptions({ - MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, - MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, - MappingProcessor.UNMAPPED_TARGET_POLICY, - MappingProcessor.UNMAPPED_SOURCE_POLICY, - MappingProcessor.DEFAULT_COMPONENT_MODEL, - MappingProcessor.DEFAULT_INJECTION_STRATEGY, - MappingProcessor.DISABLE_BUILDERS, - MappingProcessor.VERBOSE, - MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, - MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, - MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, -}) public class MappingProcessor extends AbstractProcessor { /** @@ -104,20 +89,32 @@ public class MappingProcessor extends AbstractProcessor { */ private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false; - protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; - protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = - "mapstruct.suppressGeneratorVersionInfoComment"; - protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; - protected static final String UNMAPPED_SOURCE_POLICY = "mapstruct.unmappedSourcePolicy"; - protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; - protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; - protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; - protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; - protected static final String VERBOSE = "mapstruct.verbose"; - protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; - protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; - protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = - "mapstruct.disableLifecycleOverloadDeduplicateSelector"; + // CHECKSTYLE:OFF + // Deprecated options, kept for backwards compatibility. + // They will be removed in a future release. + @Deprecated + protected static final String SUPPRESS_GENERATOR_TIMESTAMP = MappingOption.SUPPRESS_GENERATOR_TIMESTAMP.getOptionName(); + @Deprecated + protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT.getOptionName(); + @Deprecated + protected static final String UNMAPPED_TARGET_POLICY = MappingOption.UNMAPPED_TARGET_POLICY.getOptionName(); + @Deprecated + protected static final String UNMAPPED_SOURCE_POLICY = MappingOption.UNMAPPED_SOURCE_POLICY.getOptionName(); + @Deprecated + protected static final String DEFAULT_COMPONENT_MODEL = MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName(); + @Deprecated + protected static final String DEFAULT_INJECTION_STRATEGY = MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName(); + @Deprecated + protected static final String ALWAYS_GENERATE_SERVICE_FILE = MappingOption.ALWAYS_GENERATE_SERVICE_FILE.getOptionName(); + @Deprecated + protected static final String DISABLE_BUILDERS = MappingOption.DISABLE_BUILDERS.getOptionName(); + @Deprecated + protected static final String VERBOSE = MappingOption.VERBOSE.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY.getOptionName(); + // CHECKSTYLE:ON private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -156,7 +153,7 @@ public MappingProcessor() { public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); - options = createOptions(); + options = new Options( processingEnv.getOptions() ); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), @@ -171,34 +168,6 @@ public synchronized void init(ProcessingEnvironment processingEnv) { } } - private Options createOptions() { - String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); - String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); - String nullValueIterableMappingStrategy = processingEnv.getOptions() - .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); - String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); - String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() - .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); - - return new Options( - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), - unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, - unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, - processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), - Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ), - nullValueIterableMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : - null, - nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, - Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) - ); - } - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -259,13 +228,11 @@ else if ( !deferredMappers.isEmpty() ) { @Override public Set getSupportedOptions() { - Set supportedOptions = super.getSupportedOptions(); - if ( additionalSupportedOptions.isEmpty() ) { - return supportedOptions; - } - Set allSupportedOptions = new HashSet<>( supportedOptions ); - allSupportedOptions.addAll( additionalSupportedOptions ); - return allSupportedOptions; + return Stream.concat( + Stream.of( MappingOption.values() ).map( MappingOption::getOptionName ), + additionalSupportedOptions.stream() + ) + .collect( Collectors.toSet() ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index e1f04fd941..8d76e65c75 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -56,16 +56,18 @@ public Set imports() { @Override public ReportingPolicyGem unmappedTargetPolicy() { - if ( options.getUnmappedTargetPolicy() != null ) { - return options.getUnmappedTargetPolicy(); + ReportingPolicyGem unmappedTargetPolicy = options.getUnmappedTargetPolicy(); + if ( unmappedTargetPolicy != null ) { + return unmappedTargetPolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedTargetPolicy().getDefaultValue() ); } @Override public ReportingPolicyGem unmappedSourcePolicy() { - if ( options.getUnmappedSourcePolicy() != null ) { - return options.getUnmappedSourcePolicy(); + ReportingPolicyGem unmappedSourcePolicy = options.getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy != null ) { + return unmappedSourcePolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().getDefaultValue() ); } @@ -77,8 +79,9 @@ public ReportingPolicyGem typeConversionPolicy() { @Override public String componentModel() { - if ( options.getDefaultComponentModel() != null ) { - return options.getDefaultComponentModel(); + String defaultComponentModel = options.getDefaultComponentModel(); + if ( defaultComponentModel != null ) { + return defaultComponentModel; } return mapper.componentModel().getDefaultValue(); } @@ -97,8 +100,9 @@ public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { @Override public InjectionStrategyGem getInjectionStrategy() { - if ( options.getDefaultInjectionStrategy() != null ) { - return InjectionStrategyGem.valueOf( options.getDefaultInjectionStrategy().toUpperCase() ); + String defaultInjectionStrategy = options.getDefaultInjectionStrategy(); + if ( defaultInjectionStrategy != null ) { + return InjectionStrategyGem.valueOf( defaultInjectionStrategy.toUpperCase() ); } return InjectionStrategyGem.valueOf( mapper.injectionStrategy().getDefaultValue() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java new file mode 100644 index 0000000000..d35b2c0dc6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.option; + +/** + * @author Filip Hrisafov + */ +public enum MappingOption { + + // CHECKSTYLE:OFF + SUPPRESS_GENERATOR_TIMESTAMP( "mapstruct.suppressGeneratorTimestamp" ), + SUPPRESS_GENERATOR_VERSION_INFO_COMMENT( "mapstruct.suppressGeneratorVersionInfoComment" ), + UNMAPPED_TARGET_POLICY("mapstruct.unmappedTargetPolicy"), + UNMAPPED_SOURCE_POLICY("mapstruct.unmappedSourcePolicy"), + DEFAULT_COMPONENT_MODEL("mapstruct.defaultComponentModel"), + DEFAULT_INJECTION_STRATEGY("mapstruct.defaultInjectionStrategy"), + ALWAYS_GENERATE_SERVICE_FILE("mapstruct.alwaysGenerateServicesFile"), + DISABLE_BUILDERS("mapstruct.disableBuilders"), + VERBOSE("mapstruct.verbose"), + NULL_VALUE_ITERABLE_MAPPING_STRATEGY("mapstruct.nullValueIterableMappingStrategy"), + NULL_VALUE_MAP_MAPPING_STRATEGY("mapstruct.nullValueMapMappingStrategy"), + DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR("mapstruct.disableLifecycleOverloadDeduplicateSelector"), + ; + // CHECKSTYLE:ON + + private final String optionName; + + MappingOption(String optionName) { + this.optionName = optionName; + } + + /** + * Returns the name of the option, which can be used in the compiler arguments. + * + * @return the name of the option + */ + public String getOptionName() { + return optionName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 095c472c87..38474baf9c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -5,6 +5,9 @@ */ package org.mapstruct.ap.internal.option; +import java.util.Locale; +import java.util.Map; + import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; @@ -13,93 +16,79 @@ * * @author Andreas Gudian * @author Gunnar Morling + * @author Filip Hrisafov */ public class Options { - private final boolean suppressGeneratorTimestamp; - private final boolean suppressGeneratorVersionComment; - private final ReportingPolicyGem unmappedTargetPolicy; - private final ReportingPolicyGem unmappedSourcePolicy; - private final boolean alwaysGenerateSpi; - private final String defaultComponentModel; - private final String defaultInjectionStrategy; - private final boolean disableBuilders; - private final boolean verbose; - private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; - private final NullValueMappingStrategyGem nullValueMapMappingStrategy; - private final boolean disableLifecycleOverloadDeduplicateSelector; - - //CHECKSTYLE:OFF - public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, - ReportingPolicyGem unmappedTargetPolicy, - ReportingPolicyGem unmappedSourcePolicy, - String defaultComponentModel, String defaultInjectionStrategy, - boolean alwaysGenerateSpi, - boolean disableBuilders, - boolean verbose, - NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy, - boolean disableLifecycleOverloadDeduplicateSelector - ) { - //CHECKSTYLE:ON - this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; - this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; - this.unmappedTargetPolicy = unmappedTargetPolicy; - this.unmappedSourcePolicy = unmappedSourcePolicy; - this.defaultComponentModel = defaultComponentModel; - this.defaultInjectionStrategy = defaultInjectionStrategy; - this.alwaysGenerateSpi = alwaysGenerateSpi; - this.disableBuilders = disableBuilders; - this.verbose = verbose; - this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; - this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; - this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; + + private final Map options; + + public Options(Map options) { + this.options = options; } public boolean isSuppressGeneratorTimestamp() { - return suppressGeneratorTimestamp; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_TIMESTAMP ); } public boolean isSuppressGeneratorVersionComment() { - return suppressGeneratorVersionComment; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ); } public ReportingPolicyGem getUnmappedTargetPolicy() { - return unmappedTargetPolicy; + return parseEnum( MappingOption.UNMAPPED_TARGET_POLICY, ReportingPolicyGem.class ); } public ReportingPolicyGem getUnmappedSourcePolicy() { - return unmappedSourcePolicy; + return parseEnum( MappingOption.UNMAPPED_SOURCE_POLICY, ReportingPolicyGem.class ); } public String getDefaultComponentModel() { - return defaultComponentModel; + return options.get( MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName() ); } public String getDefaultInjectionStrategy() { - return defaultInjectionStrategy; + return options.get( MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName() ); } public boolean isAlwaysGenerateSpi() { - return alwaysGenerateSpi; + return parseBoolean( MappingOption.ALWAYS_GENERATE_SERVICE_FILE ); } public boolean isDisableBuilders() { - return disableBuilders; + return parseBoolean( MappingOption.DISABLE_BUILDERS ); } public boolean isVerbose() { - return verbose; + return parseBoolean( MappingOption.VERBOSE ); } public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { - return nullValueIterableMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { - return nullValueMapMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public boolean isDisableLifecycleOverloadDeduplicateSelector() { - return disableLifecycleOverloadDeduplicateSelector; + return parseBoolean( MappingOption.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); + } + + private boolean parseBoolean(MappingOption option) { + if ( options.isEmpty() ) { + return false; + } + return Boolean.parseBoolean( options.get( option.getOptionName() ) ); + } + + private > E parseEnum(MappingOption option, Class enumType) { + if ( options.isEmpty() ) { + return null; + } + String value = options.get( option.getOptionName() ); + if ( value == null ) { + return null; + } + return Enum.valueOf( enumType, value.toUpperCase( Locale.ROOT ) ); } } From d68819a233030b5d863e4d4645787a84e7db7d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:02:23 +0000 Subject: [PATCH 113/214] Bump org.springframework:spring-context from 6.2.2 to 6.2.7 in /parent Bumps [org.springframework:spring-context](https://github.com/spring-projects/spring-framework) from 6.2.2 to 6.2.7. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.2...v6.2.7) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-version: 6.2.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index f6ad60a22a..35fc24e96b 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 6.2.2 + 6.2.7 1.6.0 8.36.1 5.10.1 From f4d18181719b821bc319f53314b7c8e19e929e16 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 21:33:19 +0200 Subject: [PATCH 114/214] Fix issue key in Issue3807Test --- .../java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java index 3b895f3e11..db82472f98 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; @WithClasses(Issue3807Mapper.class) -@IssueKey("3087") +@IssueKey("3807") class Issue3807Test { @ProcessorTest From c90c93630e8580e330b09bcdd6f2bc99b08bdee6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 22:13:56 +0200 Subject: [PATCH 115/214] #3886: Records do not have property write accessors (apart from the record components) --- processor/pom.xml | 1 + .../ap/internal/model/common/Type.java | 4 +++ .../bugs/_3886/jdk21/Issue3886Mapper.java | 31 +++++++++++++++++++ .../test/bugs/_3886/jdk21/Issue3886Test.java | 25 +++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java diff --git a/processor/pom.xml b/processor/pom.xml index 2341c4ddb0..87c3517018 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -286,6 +286,7 @@ org.apache.maven.plugins maven-compiler-plugin + ${minimum.java.version} org.mapstruct.tools.gem diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index d0f65ce97a..24f23ecea8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -796,6 +796,10 @@ public Map getPropertyPresenceCheckers() { * @return an unmodifiable map of all write accessors indexed by property name */ public Map getPropertyWriteAccessors( CollectionMappingStrategyGem cmStrategy ) { + if ( isRecord() ) { + // Records do not have setters, so we return an empty map + return Collections.emptyMap(); + } // collect all candidate target accessors List candidates = new ArrayList<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java new file mode 100644 index 0000000000..aba305a0bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3886.jdk21; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface Issue3886Mapper { + + RangeRecord map(LocalDate validFrom); + + record RangeRecord(LocalDate validFrom) { + + public RangeRecord restrictTo(RangeRecord other) { + return null; + } + + public void setName(String name) { + // This method is here to ensure that MapStruct won't treat it as a setter + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java new file mode 100644 index 0000000000..93c068cbc7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3886.jdk21; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3886Mapper.class) +@IssueKey("3886") +class Issue3886Test { + + // The current version of the Eclipse compiler we use does not support records + @ProcessorTest(Compiler.JDK) + void shouldCompile() { + + } +} From e4bc1cdf1e39a3ff25f0a55b44bdb25837d49a69 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 15 Jun 2025 14:29:45 +0800 Subject: [PATCH 116/214] #3884 Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null Signed-off-by: TangYang --- ...anceSetterWrapperForCollectionsAndMaps.ftl | 4 + .../ap/test/bugs/_3884/DestinationType.java | 22 ++++ .../ap/test/bugs/_3884/Issue3884Mapper.java | 21 ++++ .../ap/test/bugs/_3884/Issue3884Test.java | 116 ++++++++++++++++++ .../ap/test/bugs/_3884/SourceType.java | 37 ++++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ 7 files changed, 248 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index cee722e2d3..95662c4c79 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -30,6 +30,10 @@ <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && mapNullToDefault>else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; + } + <#-- wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java new file mode 100644 index 0000000000..467b58990c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Destination type interface for testing null value property mapping strategy with Map properties. + */ +public interface DestinationType { + Map getAttributes(); + + void setAttributes(Map attributes); + + List getItems(); + + void setItems(List items); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java new file mode 100644 index 0000000000..6f6f4f924d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing null value property mapping strategy with Map properties. + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface Issue3884Mapper { + Issue3884Mapper INSTANCE = Mappers.getMapper( Issue3884Mapper.class ); + + void update(@MappingTarget DestinationType destination, SourceType source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java new file mode 100644 index 0000000000..6c789a0c29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java @@ -0,0 +1,116 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Test for issue 3884: NullValuePropertyMappingStrategy.SET_TO_DEFAULT should set target Map/Collection to default + * when source and target are all null. + */ +@IssueKey("3884") +@WithClasses({ + DestinationType.class, + SourceType.class, + Issue3884Mapper.class +}) +public class Issue3884Test { + + @ProcessorTest + public void shouldSetTargetToDefaultWhenBothSourceAndTargetAreNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldClearTargetWhenSourceIsNullAndTargetIsInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenSourceIsInitializedAndTargetIsNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenBothSourceAndTargetAreInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java new file mode 100644 index 0000000000..be5c19e01c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Source type class implementing DestinationType for testing null value property mapping strategy with Map properties. + */ +public class SourceType implements DestinationType { + private Map attributes; + private List items; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public List getItems() { + return items; + } + + @Override + public void setItems(List items) { + this.items = items; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index 76de94378b..f36e2e437d 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index f1bf0fa78f..87d2fccb26 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); From 46a9c29fcabc7ade34f39a92741449c874d7547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Thu, 31 Jul 2025 04:37:50 +0900 Subject: [PATCH 117/214] #3902: Validate unknown properties in @Ignored --- .../ap/internal/model/BeanMappingMethod.java | 60 +++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3902/ErroneousIssue3902Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3902/Issue3902Test.java | 57 +++++++++++++++ 4 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index b5208d302b..c81fc51800 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1280,40 +1280,54 @@ else if ( inheritContext.isReversed() ) { return false; } } - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); Message msg; String[] args; - if ( targetRef.getPathProperties().isEmpty() ) { - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + Element elementForMessage = mapping.getElement(); + if ( elementForMessage == null ) { + elementForMessage = method.getExecutable(); + } + + if ( mapping.isIgnored() && mapping.getElement() == null ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe(), - mostSimilarProperty + resultTypeToMap.describe() }; } else { - List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); - pathProperties.add( mostSimilarProperty ); - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; - args = new String[] { - targetPropertyName, - resultTypeToMap.describe(), - mapping.getTargetName(), - Strings.join( pathProperties, "." ) - }; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + }; + } } - ctx.getMessager() - .printMessage( - mapping.getElement(), - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args - ); + ctx.getMessager().printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args + ); return true; } else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 883b3e3792..8534758285 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,6 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java new file mode 100644 index 0000000000..07f94ce6fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing bug #3902. + * + * @author znight1020 + */ +@Mapper +public interface ErroneousIssue3902Mapper { + + ErroneousIssue3902Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3902Mapper.class ); + + @Ignored(targets = {"name", "foo", "bar"}) + ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); + + @Ignored(targets = {"name", "address", "foo"}) + ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + + class Zoo { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class ZooDto { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java new file mode 100644 index 0000000000..f1332bbdc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Verifies that using an unknown property in {@code @Ignored} yields a proper + * compile error instead of an internal processor error. + * + * @author znight1020 + */ +@WithClasses({ErroneousIssue3902Mapper.class}) +@IssueKey("3902") +public class Issue3902Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + // Test case: mapWithOneKnownAndMultipleUnknowns + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"bar\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + + // Test case: mapWithMultipleKnownAndOneUnknown + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ) + } + ) + public void shouldFailOnUnknownPropertiesInIgnored() { + } +} From 29036542b85ff93a05fc5851793438ede8713f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Mon, 4 Aug 2025 15:08:22 +0900 Subject: [PATCH 118/214] #3908: Add similar suggestion for unknown property in `@Ignored` --- .../ap/internal/model/BeanMappingMethod.java | 22 ++++++++++--------- .../mapstruct/ap/internal/util/Message.java | 2 +- .../bugs/_3902/ErroneousIssue3902Mapper.java | 3 +++ .../ap/test/bugs/_3902/Issue3902Test.java | 15 ++++++++++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index c81fc51800..7973ece427 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1284,6 +1284,9 @@ else if ( inheritContext.isReversed() ) { Message msg; String[] args; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + Element elementForMessage = mapping.getElement(); if ( elementForMessage == null ) { elementForMessage = method.getExecutable(); @@ -1293,13 +1296,11 @@ else if ( inheritContext.isReversed() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe() + resultTypeToMap.describe(), + mostSimilarProperty }; } else { - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); - if ( targetRef.getPathProperties().isEmpty() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; args = new String[] { @@ -1321,12 +1322,13 @@ else if ( inheritContext.isReversed() ) { } } - ctx.getMessager().printMessage( - elementForMessage, - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args + ctx.getMessager() + .printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args ); return true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 8534758285..60aa55eb38 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,7 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\". Did you mean \"%s\"?"), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java index 07f94ce6fe..b646f1a08e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -25,6 +25,9 @@ public interface ErroneousIssue3902Mapper { @Ignored(targets = {"name", "address", "foo"}) ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + @Ignored(targets = {"name", "addres"}) + ZooDto mapWithTypo(Zoo source); + class Zoo { private String name; private String address; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java index f1332bbdc6..44247d8c27 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -32,14 +32,14 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), @Diagnostic( type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"bar\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), // Test case: mapWithMultipleKnownAndOneUnknown @@ -48,7 +48,16 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + + // Test case: mapWithTypo + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 29, + message = "No property named \"addres\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"address\"?" ) } ) From fe43563c8184dcf0e96a9ac9bfb40a8ace436e6f Mon Sep 17 00:00:00 2001 From: Ritesh Chopade <94113981+codeswithritesh@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:47:30 +0530 Subject: [PATCH 119/214] #3837 Add warning/error for redundant ignoreUnmappedSourceProperties entries (#3906) Adds compiler warning / error when properties listed in `ignoreUnmappedSourceProperties` are actually mapped. Respects `unmappedSourcePolicy` and includes tests for redundant and valid ignore cases. --- .../ap/internal/model/BeanMappingMethod.java | 44 +++++- .../mapstruct/ap/internal/util/Message.java | 2 + .../IgnoredMappedPropertyTest.java | 136 ++++++++++++++++++ .../ap/test/ignoreunmapped/UserDto.java | 31 ++++ .../ap/test/ignoreunmapped/UserEntity.java | 40 ++++++ .../ignoreunmapped/mapper/UserMapper.java | 19 +++ ...rMapperWithIgnorePolicyInMapperConfig.java | 24 ++++ .../UserMapperWithIgnoreSourcePolicy.java | 21 +++ ...serMapperWithWarnPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithWarnSourcePolicy.java | 21 +++ ...serMapperWithWarnSourcePolicyInMapper.java | 20 +++ .../mapper/UserMapperWithoutBeanMapping.java | 17 +++ ...erMapperWithErrorPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithErrorSourcePolicy.java | 21 +++ ...erMapperWithErrorSourcePolicyInMapper.java | 20 +++ .../erroneous/UserMapperWithMultiMapping.java | 22 +++ 16 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 7973ece427..05e0424a21 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -114,6 +114,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedTargetProperties; private Map unprocessedSourceProperties; private Set missingIgnoredSourceProperties; + private Set redundantIgnoredSourceProperties; private Set targetProperties; private final List propertyMappings = new ArrayList<>(); private final Set unprocessedSourceParameters = new HashSet<>(); @@ -278,11 +279,23 @@ else if ( !method.isUpdateMethod() ) { // get bean mapping (when specified as annotation ) this.missingIgnoredSourceProperties = new HashSet<>(); - if ( beanMapping != null ) { + this.redundantIgnoredSourceProperties = new HashSet<>(); + if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { + // Get source properties explicitly mapped using @Mapping annotations + Set mappedSourceProperties = method.getOptions().getMappings().stream() + .map( MappingOptions::getSourceName ) + .filter( Objects::nonNull ) + .collect( Collectors.toSet() ); + for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) { + // Track missing ignored properties (i.e. not in source class) if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) { missingIgnoredSourceProperties.add( ignoreUnmapped ); } + // Track redundant ignored properties (actually mapped despite being ignored) + if ( mappedSourceProperties.contains( ignoreUnmapped ) ) { + redundantIgnoredSourceProperties.add( ignoreUnmapped ); + } } } @@ -331,6 +344,7 @@ else if ( !method.isUpdateMethod() ) { } reportErrorForMissingIgnoredSourceProperties(); reportErrorForUnusedSourceParameters(); + reportErrorForRedundantIgnoredSourceProperties(); // mapNullToDefault boolean mapNullToDefault = method.getOptions() @@ -1914,6 +1928,34 @@ private void reportErrorForMissingIgnoredSourceProperties() { } } + private void reportErrorForRedundantIgnoredSourceProperties() { + if ( !redundantIgnoredSourceProperties.isEmpty() ) { + ReportingPolicyGem unmappedSourcePolicy = getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy == ReportingPolicyGem.IGNORE ) { //don't show warning + return; + } + + Message message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING; + if ( unmappedSourcePolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) { + message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR; + } + + Object[] args = new Object[] { + MessageFormat.format( + "{0,choice,1#property|1 Date: Thu, 28 Aug 2025 22:31:52 +0200 Subject: [PATCH 120/214] docs: reference gh discussions --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6064004d73..14443592e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ You love MapStruct but miss a certain feature? You found a bug and want to repor * Source code: [http://github.com/mapstruct/mapstruct](http://github.com/mapstruct/mapstruct) * Issue tracker: [https://github.com/mapstruct/mapstruct/issues](https://github.com/mapstruct/mapstruct/issues) -* Discussions: Join the [mapstruct-users](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) Google group +* Discussions: [https://github.com/mapstruct/mapstruct/discussions](https://github.com/mapstruct/mapstruct/discussions) * CI build: [https://github.com/mapstruct/mapstruct/actions/](https://github.com/mapstruct/mapstruct/actions) MapStruct follows the _Fork & Pull_ development approach. To get started just fork the [MapStruct repository](http://github.com/mapstruct/mapstruct) to your GitHub account and create a new topic branch for each change. Once you are done with your change, submit a [pull request](https://help.github.com/articles/using-pull-requests) against the MapStruct repo. @@ -15,7 +15,7 @@ When doing changes, keep the following best practices in mind: * Use the code formatter for your IDE * [IntelliJ Formatter](https://github.com/mapstruct/mapstruct/blob/master/etc/mapstruct.xml) * Update the [reference documentation](mapstruct.org/documentation) on [mapstruct.org](mapstruct.org) where required -* Discuss new features you'd like to implement at the [Google group](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) before getting started +* Discuss new features you'd like to implement in the [discussions](https://github.com/mapstruct/mapstruct/discussions) before getting started * Create one pull request per feature * Provide a meaningful history, e.g. squash intermediary commits before submitting a pull request From 0f7f5434295093d1f452d95d3d72c10c7f37103e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 15:57:31 +0200 Subject: [PATCH 121/214] Update Distribution management to point to new Maven Central Portal --- parent/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 35fc24e96b..f90b066774 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -87,12 +87,12 @@ sonatype-nexus-staging Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ From 4e1720c2a899252de448c38eb10942f9fbbf5276 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 16:11:26 +0200 Subject: [PATCH 122/214] #3905: Using custom class Override should compile --- .../model/AbstractMappingMethodBuilder.java | 9 ++++++- .../model/NormalTypeMappingMethod.java | 6 ----- .../ap/internal/model/BeanMappingMethod.ftl | 1 - .../internal/model/IterableMappingMethod.ftl | 1 - .../ap/internal/model/MapMappingMethod.ftl | 1 - .../ap/internal/model/StreamMappingMethod.ftl | 1 - .../ap/test/bugs/_3905/Issue3905Mapper.java | 17 ++++++++++++ .../ap/test/bugs/_3905/Issue3905Test.java | 26 +++++++++++++++++++ .../ap/test/bugs/_3905/Override.java | 22 ++++++++++++++++ .../ap/test/bugs/_3905/OverrideDto.java | 22 ++++++++++++++++ 10 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 7329df5ab7..efb5887c95 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -130,7 +130,14 @@ public List getMethodAnnotations() { ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - return new ArrayList<>( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + List annotations = new ArrayList<>( + additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) + ); + + if ( method.overridesMethod() ) { + annotations.add( new Annotation( ctx.getTypeFactory().getType( Override.class ) ) ); + } + return annotations; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 79cd0e0566..89ee1baab2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -21,7 +21,6 @@ */ public abstract class NormalTypeMappingMethod extends MappingMethod { private final MethodReference factoryMethod; - private final boolean overridden; private final boolean mapNullToDefault; private final List annotations; @@ -33,7 +32,6 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { List afterMappingReferences) { super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); this.factoryMethod = factoryMethod; - this.overridden = method.overridesMethod(); this.mapNullToDefault = mapNullToDefault; this.annotations = annotations; } @@ -59,10 +57,6 @@ public boolean isMapNullToDefault() { return mapNullToDefault; } - public boolean isOverridden() { - return overridden; - } - public MethodReference getFactoryMethod() { return this.factoryMethod; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index da590bb506..059e2d77d4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#assign targetType = resultType /> <#if !existingInstanceMapping> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 4d08c44b38..7c3d3071ac 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 1227dbd27a..957dc712ee 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 7d0080a1d2..2269892b8a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable--> <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java new file mode 100644 index 0000000000..27e3ac9960 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3905Mapper { + + OverrideDto map(Override override); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java new file mode 100644 index 0000000000..f15271362d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3905") +@WithClasses({ + Issue3905Mapper.class, + Override.class, + OverrideDto.class +}) +class Issue3905Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java new file mode 100644 index 0000000000..dedc6b28dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class Override { + + private final String name; + + public Override(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java new file mode 100644 index 0000000000..531fab505b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class OverrideDto { + + private final String name; + + public OverrideDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From a0552131a5bf89f8192acb154b81bbd03d334ba4 Mon Sep 17 00:00:00 2001 From: hdulme Date: Wed, 31 Dec 2025 17:23:53 +0100 Subject: [PATCH 123/214] skip coverage upload to codecov on contributors repositories --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6a2ef371e..c4e6092723 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 21 + if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' From 1c8b9107f99e986e5e28cff66ad604ca89975a8c Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:01:18 +0100 Subject: [PATCH 124/214] Update codecov-action to v5 and use token (#3967) --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4e6092723..c4def2a89d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,10 @@ jobs: run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} - name: 'Publish Snapshots' if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy From 7bcab2824d0d17fe0a68d520e0c73f83a0e82588 Mon Sep 17 00:00:00 2001 From: ro-otgo <116685946+ro-otgo@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:44:21 +0100 Subject: [PATCH 125/214] Fix example 5 code comment. (#3968) Example 5: Mapping with default value should be using fullName instead of just name in order to map the property. --- core/src/main/java/org/mapstruct/Mapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index f15cfb75cc..c1923eb41a 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -118,7 +118,7 @@ * // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it * @Mapper * public interface HumanMapper { - * @Mapping(source="name", target="name", defaultValue="Somebody") + * @Mapping(source="name", target="fullName", defaultValue="Somebody") * HumanDto toHumanDto(Human human) * } * From 57790791abd0193cf0ff42d33524c69ab0eb6215 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Jan 2026 15:46:36 +0100 Subject: [PATCH 126/214] BeanMappingMethod and NestedPropertyMappingMethod simplification (#3970) * Move `returnTypeBuilder` and `userDefinedReturnType` in `BeanMappingMethod#build` instead of doing it earlier * Move source parameter presence check resolving into `PresenceCheckMethodResolver` * Simplify `NestedPropertyMappingMethod.ftl` by moving logic into java instead of freemarker --- .../internal/model/AbstractBaseBuilder.java | 4 +- .../model/AbstractMappingMethodBuilder.java | 7 +- .../ap/internal/model/BeanMappingMethod.java | 32 +++----- .../model/NestedPropertyMappingMethod.java | 79 ++++++++++++------- .../model/PresenceCheckMethodResolver.java | 4 + .../ap/internal/model/PropertyMapping.java | 2 +- .../AnyPresenceChecksPresenceCheck.java | 64 +++++++++++++++ .../processor/MapperCreationProcessor.java | 14 ---- .../model/NestedPropertyMappingMethod.ftl | 16 +--- .../AnyPresenceChecksPresenceCheck.ftl | 16 ++++ 10 files changed, 152 insertions(+), 86 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 56a1831d52..584e0260bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -9,7 +9,6 @@ import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.model.common.Assignment; -import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; @@ -73,7 +72,7 @@ private boolean isDisableSubMappingMethodsGeneration() { * * @return See above */ - Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) { + Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) { Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { @@ -87,7 +86,6 @@ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, else { forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() .forgedMethod( forgedMethod ) - .returnTypeBuilder( builderType ) .mappingContext( ctx ) .build(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index efb5887c95..3b6e3c1686 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.model; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.SourceRHS; @@ -94,12 +93,8 @@ private Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targe ForgedMethod forgedMethod = forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - return createForgedAssignment( - sourceRHS, - ctx.getTypeFactory().builderTypeFor( targetType, builder ), - forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private String getName(Type sourceType, Type targetType) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 05e0424a21..39222efc88 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -32,6 +32,7 @@ import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; +import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; @@ -52,7 +53,6 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; -import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; @@ -106,8 +106,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { public static class Builder extends AbstractMappingMethodBuilder { - private Type userDefinedReturnType; - /* returnType to construct can have a builder */ private BuilderType returnTypeBuilder; private Map unprocessedConstructorProperties; @@ -135,16 +133,6 @@ protected boolean shouldUsePropertyNamesInHistory() { return true; } - public Builder userDefinedReturnType(Type userDefinedReturnType) { - this.userDefinedReturnType = userDefinedReturnType; - return this; - } - - public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) { - this.returnTypeBuilder = returnTypeBuilder; - return this; - } - public Builder sourceMethod(SourceMethod sourceMethod) { method( sourceMethod ); return this; @@ -181,7 +169,17 @@ public BeanMappingMethod build() { // determine which return type to construct boolean cannotConstructReturnType = false; if ( !method.getReturnType().isVoid() ) { - Type returnTypeImpl = null; + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + Type returnTypeImpl; + Type userDefinedReturnType = null; + if ( selectionParameters != null && selectionParameters.getResultType() != null ) { + // This is a user-defined return type, which means we need to do some extra checks for it + userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); + } + else { + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set returnTypeImpl = returnTypeBuilder.getBuilder(); @@ -446,12 +444,6 @@ else if ( !method.isUpdateMethod() ) { if ( parameterPresenceCheck != null ) { presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); } - else if ( !sourceParameter.getType().isPrimitive() ) { - presenceChecksByParameter.put( - sourceParameter.getName(), - new NullPresenceCheck( sourceParameter.getName() ) - ); - } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index da73e1783f..25c9f8fc03 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; @@ -14,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -68,9 +71,32 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); - for ( PropertyEntry propertyEntry : propertyEntries ) { + for ( int i = 0; i < propertyEntries.size(); i++ ) { + PropertyEntry propertyEntry = propertyEntries.get( i ); + PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } + } + String safeName = Strings.getSafeVariableName( propertyEntry.getName(), existingVariableNames ); - safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName, previousPropertyName ) ); + String source = previousPropertyName + "." + propertyEntry.getReadAccessor().getReadValueSource(); + safePropertyEntries.add( new SafePropertyEntry( + propertyEntry.getType(), + safeName, + source, + presenceCheck + ) ); existingVariableNames.add( safeName ); thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); @@ -79,6 +105,18 @@ public NestedPropertyMappingMethod build() { method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); } + + private PresenceCheck getPresenceCheck(PropertyEntry propertyEntry, String previousPropertyName) { + PresenceCheckAccessor propertyPresenceChecker = propertyEntry.getPresenceChecker(); + if ( propertyPresenceChecker != null ) { + return new SuffixPresenceCheck( + previousPropertyName, + propertyPresenceChecker.getPresenceCheckSuffix(), + true + ); + } + return null; + } } private NestedPropertyMappingMethod( ForgedMethod method, List sourcePropertyEntries ) { @@ -157,44 +195,29 @@ public boolean equals( Object obj ) { public static class SafePropertyEntry { private final String safeName; - private final String readAccessorName; + private final String source; private final PresenceCheck presenceChecker; - private final String previousPropertyName; private final Type type; - public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { + public SafePropertyEntry(Type type, String safeName, String source, PresenceCheck presenceCheck) { this.safeName = safeName; - this.readAccessorName = entry.getReadAccessor().getReadValueSource(); - PresenceCheckAccessor presenceChecker = entry.getPresenceChecker(); - if ( presenceChecker != null ) { - this.presenceChecker = new SuffixPresenceCheck( - previousPropertyName, - presenceChecker.getPresenceCheckSuffix() - ); - } - else { - this.presenceChecker = null; - } - this.previousPropertyName = previousPropertyName; - this.type = entry.getType(); + this.source = source; + this.presenceChecker = presenceCheck; + this.type = type; } public String getName() { return safeName; } - public String getAccessorName() { - return readAccessorName; + public String getSource() { + return source; } public PresenceCheck getPresenceChecker() { return presenceChecker; } - public String getPreviousPropertyName() { - return previousPropertyName; - } - public Type getType() { return type; } @@ -210,7 +233,7 @@ public boolean equals(Object o) { SafePropertyEntry that = (SafePropertyEntry) o; - if ( !Objects.equals( readAccessorName, that.readAccessorName ) ) { + if ( !Objects.equals( source, that.source ) ) { return false; } @@ -218,10 +241,6 @@ public boolean equals(Object o) { return false; } - if ( !Objects.equals( previousPropertyName, that.previousPropertyName ) ) { - return false; - } - if ( !Objects.equals( type, that.type ) ) { return false; } @@ -231,7 +250,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = readAccessorName != null ? readAccessorName.hashCode() : 0; + int result = source != null ? source.hashCode() : 0; result = 31 * result + ( presenceChecker != null ? presenceChecker.hashCode() : 0 ); result = 31 * result + ( type != null ? type.hashCode() : 0 ); return result; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 96826f8f66..049e4caf37 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -86,6 +87,9 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { + if ( !sourceParameter.getType().isPrimitive() ) { + return new NullPresenceCheck( sourceParameter.getName() ); + } return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index db14ccbd8e..08e8adbd1b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -829,7 +829,7 @@ private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sour forgeMethodWithMappingReferences, forgedNamedBased ); - return createForgedAssignment( sourceRHS, targetBuilderType, forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java new file mode 100644 index 0000000000..0b5770e42f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.presence; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { + + private final Collection presenceChecks; + + public AnyPresenceChecksPresenceCheck(Collection presenceChecks) { + this.presenceChecks = presenceChecks; + } + + public Collection getPresenceChecks() { + return presenceChecks; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + for ( PresenceCheck presenceCheck : presenceChecks ) { + importTypes.addAll( presenceCheck.getImportTypes() ); + } + + return importTypes; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + AnyPresenceChecksPresenceCheck that = (AnyPresenceChecksPresenceCheck) o; + return Objects.equals( presenceChecks, that.presenceChecks ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceChecks ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 4fbed6cbec..ba24e868aa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -24,7 +24,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.gem.InheritConfigurationGem; import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem; @@ -420,15 +419,10 @@ else if ( method.isStreamMapping() ) { } else { this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - Type userDefinedReturnType = getUserDesiredReturnType( method ); - Type builderBaseType = userDefinedReturnType != null ? userDefinedReturnType : method.getReturnType(); BeanMappingMethod.Builder beanMappingBuilder = new BeanMappingMethod.Builder(); BeanMappingMethod beanMappingMethod = beanMappingBuilder .mappingContext( mappingContext ) .sourceMethod( method ) - .userDefinedReturnType( userDefinedReturnType ) - .returnTypeBuilder( typeFactory.builderTypeFor( builderBaseType, builder ) ) .build(); // We can consider that the bean mapping method can always be constructed. If there is a problem @@ -466,14 +460,6 @@ private Javadoc getJavadoc(TypeElement element) { return javadoc; } - private Type getUserDesiredReturnType(SourceMethod method) { - SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters(); - if ( selectionParameters != null && selectionParameters.getResultType() != null ) { - return typeFactory.getType( selectionParameters.getResultType() ); - } - return null; - } - private M createWithElementMappingMethod(SourceMethod method, MappingMethodOptions mappingMethodOptions, ContainerMappingMethodBuilder builder) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 8eabe23a16..04d385a9dc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -9,22 +9,14 @@ <#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list propertyEntries as entry> <#if entry.presenceChecker?? > - if ( <#if entry_index != 0>${entry.previousPropertyName} == null || !<@includeModel object=entry.presenceChecker /> ) { + if ( <@includeModel object=entry.presenceChecker /> ) { return ${returnType.null}; } - <#if !entry_has_next> - return ${entry.previousPropertyName}.${entry.accessorName}; - <#if entry_has_next> - <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName}; - <#if !entry.presenceChecker?? > - <#if !entry.type.primitive> - if ( ${entry.name} == null ) { - return ${returnType.null}; - } - - + <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.source}; + <#else> + return ${entry.source}; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl new file mode 100644 index 0000000000..3df10348ad --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck" --> +<@compress single_line=true> +<#list presenceChecks as presenceCheck> + <#if presenceCheck_index != 0> + || + + <@includeModel object=presenceCheck /> + + \ No newline at end of file From 9d75a48df5e40c1ed87d2f68d96fbb35807e3b91 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 26 Jan 2026 11:35:18 +0100 Subject: [PATCH 127/214] Block plexus.snapshots repository in GitHub actions #3972 --- .github/workflows/java-ea.yml | 3 +++ .github/workflows/macos.yml | 3 +++ .github/workflows/main.yml | 6 ++++++ .github/workflows/release.yml | 4 ++++ .github/workflows/windows.yml | 3 +++ 5 files changed, 19 insertions(+) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fc3a4a0fb1..70b81c40f1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -17,5 +17,8 @@ jobs: with: website: jdk.java.net release: EA + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 833bb6ba39..98b4e156e0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4def2a89d..ca66a22291 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,9 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' @@ -59,5 +62,8 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61f347af3c..b4dffe0586 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,10 @@ jobs: distribution: 'zulu' cache: maven + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' + - name: Set release version id: version run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bda38f8783..fc37af6a9d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw %MAVEN_ARGS% install From 99e865f9861bba3d8cc24d90b89821e8bb191164 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 10:27:59 +0100 Subject: [PATCH 128/214] Improve testing support for Kotlin as part of the regular processor testing (#3977) --- .../itest/tests/MavenIntegrationTest.java | 6 + .../test/resources/fullFeatureTest/pom.xml | 1 + .../resources/kotlinFullFeatureTest/pom.xml | 143 ++++++++++++++++++ parent/pom.xml | 13 ++ processor/pom.xml | 68 +++++++++ .../ap/test/kotlin/data/CustomerDto.kt | 11 ++ .../ap/test/kotlin/data/CustomerEntity.java | 31 ++++ .../ap/test/kotlin/data/CustomerMapper.java | 27 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 54 +++++++ .../ap/testutil/WithKotlinSources.java | 32 ++++ .../model/CompilationOutcomeDescriptor.java | 31 ++++ .../model/DiagnosticDescriptor.java | 34 +++++ .../testutil/runner/CompilationRequest.java | 12 +- .../testutil/runner/CompilingExtension.java | 102 ++++++++++++- .../runner/EclipseCompilingExtension.java | 11 +- .../runner/JdkCompilingExtension.java | 15 +- 16 files changed, 579 insertions(+), 12 deletions(-) create mode 100644 integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java 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 ab43cc4cba..70e1f948b9 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -159,6 +159,12 @@ void expressionTextBlocksTest() { void kotlinDataTest() { } + @ProcessorTest(baseDir = "kotlinFullFeatureTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC_WITH_PATHS + }) + void kotlinFullFeatureTest() { + } + @ProcessorTest(baseDir = "simpleTest") void simpleTest() { } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index b5ddae9a79..670d5cff92 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -47,6 +47,7 @@ **/*Test.java **/testutil/**/*.java **/spi/**/*.java + **/kotlin/**/*.java ${additionalExclude0} ${additionalExclude1} ${additionalExclude2} diff --git a/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml new file mode 100644 index 0000000000..ba18457f42 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + kotlinFullFeatureTest + jar + + + 2.3.0 + + + + + generate-via-compiler-plugin-with-annotation-processor-paths + + false + + + + + maven-resources-plugin + + + filter-kotlin-patterns + generate-sources + + copy-resources + + + \${project.build.directory}/kotlin-sources + + + ../../../../../processor/src/test/java + + **/kotlin/**/*.kt + + + + true + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + kotlin-compile + compile + + compile + + + + \${project.build.directory}/kotlin-sources + + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + ../../../../../processor/src/test/java + + + **/kotlin/**/*.java + + + **/erroneous/**/*.java + **/*Erroneous*.java + **/*Test.java + **/testutil/**/*.java + + + + + java-test-compile + test-compile + + testCompile + + + + + + + + + + + + org.jetbrains.kotlin + kotlin-stdlib + + + + diff --git a/parent/pom.xml b/parent/pom.xml index f90b066774..5c3a73da88 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -52,6 +52,7 @@ 1.8 3.25.5 2.3.2 + 2.3.0 @@ -151,6 +152,13 @@ ${com.puppycrawl.tools.checkstyle.version} + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + org.junit junit-bom @@ -490,6 +498,11 @@ maven-shade-plugin 3.2.0 + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + com.mycila.maven-license-plugin maven-license-plugin diff --git a/processor/pom.xml b/processor/pom.xml index 87c3517018..9f24281e33 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-compiler-embeddable + test + org.eclipse.tycho tycho-compiler-jdt @@ -282,6 +287,42 @@ + + org.jetbrains.kotlin + + kotlin-maven-plugin + + false + + + + kotlin-compile + compile + + compile + + + + src/main/kotlin + + src/main/java + + + + + kotlin-test-compile + test-compile + + test-compile + + + + src/test/java + + + + + org.apache.maven.plugins maven-compiler-plugin @@ -296,6 +337,33 @@ + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + org.apache.maven.plugins diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt new file mode 100644 index 0000000000..3fbd48b0b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class CustomerDto(var name: String?, var email: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java new file mode 100644 index 0000000000..7ddd41bda0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java new file mode 100644 index 0000000000..1830a9c583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromData(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toData(CustomerEntity entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java new file mode 100644 index 0000000000..310039363d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.junit.jupiter.api.Nested; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class KotlinDataTest { + + @Nested + @WithClasses({ + CustomerEntity.class, + CustomerMapper.class + }) + @WithKotlinSources("CustomerDto.kt") + class Standard { + + @ProcessorTest + void shouldMapData() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromData( new CustomerDto( + "Kermit", + "kermit@test.com" + ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @ProcessorTest + void shouldMapIntoData() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toData( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getEmail() ).isEqualTo( "kermit@test.com" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java new file mode 100644 index 0000000000..656dc9bc5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.intellij.lang.annotations.Language; + +/** + * Specifies the kotlin sources to compile during an annotation processor test. + * If given both on the class-level and the method-level for a given test, all the given sources will be compiled. + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface WithKotlinSources { + + /** + * The classes to be compiled for the annotated test class or method. + * + * @return the classes to be compiled for the annotated test class or method + */ + @Language(value = "file-reference", prefix = "", suffix = ".kt") + String[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java index b9f7bb8425..b5a4dc535d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java @@ -18,6 +18,8 @@ import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.compiler.CompilerResult; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; @@ -104,6 +106,25 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerR return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, Collections.emptyList() ); } + public static CompilationOutcomeDescriptor forResult(String sourceDir, ExitCode exitCode, + List messages) { + List notes = new ArrayList<>(); + List diagnosticDescriptors = new ArrayList<>( messages.size() ); + for ( MessageCollectorImpl.Message message : messages ) { + //ignore notes created by the compiler + DiagnosticDescriptor descriptor = DiagnosticDescriptor.forCompilerMessage( sourceDir, message ); + if ( descriptor.getKind() != Kind.NOTE ) { + diagnosticDescriptors.add( descriptor ); + } + else { + notes.add( descriptor.getMessage() ); + } + } + CompilationResult compilationResult = + exitCode == ExitCode.OK ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, notes ); + } + public CompilationResult getCompilationResult() { return compilationResult; } @@ -116,6 +137,16 @@ public List getNotes() { return notes; } + public CompilationOutcomeDescriptor merge(CompilationOutcomeDescriptor other) { + CompilationResult compilationResult = + this.compilationResult == CompilationResult.SUCCEEDED ? other.compilationResult : this.compilationResult; + List diagnostics = new ArrayList<>( this.diagnostics ); + diagnostics.addAll( other.diagnostics ); + List notes = new ArrayList<>( this.notes ); + notes.addAll( other.notes ); + return new CompilationOutcomeDescriptor( compilationResult, diagnostics, notes ); + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index ecfec14a16..68e2cb00fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -15,6 +15,9 @@ import javax.tools.JavaFileObject; import org.codehaus.plexus.compiler.CompilerMessage; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; /** @@ -100,6 +103,37 @@ private static Kind toJavaxKind(CompilerMessage.Kind kind) { } } + public static DiagnosticDescriptor forCompilerMessage(String sourceDir, MessageCollectorImpl.Message message) { + CompilerMessageSourceLocation location = message.getLocation(); + String sourceFileName; + Long line; + if ( location != null ) { + sourceFileName = location.getPath(); + line = (long) location.getLine(); + } + else { + sourceFileName = null; + line = null; + } + return new DiagnosticDescriptor( + sourceFileName, + toJavaxKind( message.getSeverity() ), + line, + message.getMessage(), + null + ); + } + + private static Kind toJavaxKind(CompilerMessageSeverity severity) { + return switch ( severity ) { + case ERROR, EXCEPTION -> Kind.ERROR; + case WARNING, FIXED_WARNING -> Kind.WARNING; + case STRONG_WARNING -> Kind.MANDATORY_WARNING; + case INFO, LOGGING -> Kind.NOTE; + case OUTPUT -> Kind.OTHER; + }; + } + private static String getSourceName(String sourceDir, javax.tools.Diagnostic diagnostic) { if ( diagnostic.getSource() == null ) { return null; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 9f1b78caa0..8622fbe92c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,14 +19,17 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, - List processorOptions, Collection testDependencies) { + List processorOptions, Collection testDependencies, + Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.kotlinSources = kotlinSources; } @Override @@ -58,7 +61,8 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) - && sourceClasses.equals( other.sourceClasses ); + && sourceClasses.equals( other.sourceClasses ) + && kotlinSources.equals( other.kotlinSources ); } public Set> getSourceClasses() { @@ -77,6 +81,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getKotlinSources() { + return kotlinSources; + } + public Compiler getCompiler() { return compiler; } 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 95874e4b3d..7ff78753a6 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 @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; @@ -30,9 +31,15 @@ import com.puppycrawl.tools.checkstyle.PropertiesExpander; import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; +import org.jetbrains.kotlin.config.Services; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -455,6 +462,20 @@ protected static Set getSourceFiles(Collection> classes) { return sourceFiles; } + private Collection getKotlinSources(ExtensionContext context) { + Collection kotlinSources = new HashSet<>(); + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + findAnnotation( testMethod, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + findAnnotation( testClass, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + return kotlinSources; + } + private CompilationOutcomeDescriptor compile(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); Class testClass = context.getRequiredTestClass(); @@ -464,7 +485,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getTestClasses( testMethod, testClass ), getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), - getAdditionalTestDependencies( testMethod, testClass ) + getAdditionalTestDependencies( testMethod, testClass ), + getKotlinSources( context ) ); ExtensionContext.Store rootStore = context.getRoot().getStore( NAMESPACE ); @@ -484,7 +506,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { boolean needsAdditionalCompilerClasspath = prepareServices( compilationRequest ); CompilationOutcomeDescriptor resultHolder; - resultHolder = compileWithSpecificCompiler( + resultHolder = compile( + context, compilationRequest, sourceOutputDir, classOutputDir, @@ -503,6 +526,81 @@ protected Object loadAndInstantiate(ClassLoader processorClassloader, Class c } } + protected CompilationOutcomeDescriptor compile( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = compileWithKotlin( + context, + compilationRequest, + sourceOutputDir, + classOutputDir + ); + + if ( kotlinCompilationOutcome != null && + kotlinCompilationOutcome.getCompilationResult() == CompilationResult.FAILED ) { + return kotlinCompilationOutcome; + } + + CompilationOutcomeDescriptor javaCompilationOutcome = compileWithSpecificCompiler( + compilationRequest, + sourceOutputDir, + classOutputDir, + additionalCompilerClasspath + ); + return kotlinCompilationOutcome == null ? javaCompilationOutcome : + kotlinCompilationOutcome.merge( javaCompilationOutcome ); + } + + private CompilationOutcomeDescriptor compileWithKotlin( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir + ) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = null; + if ( !compilationRequest.getKotlinSources().isEmpty() ) { + K2JVMCompiler k2JvmCompiler = new K2JVMCompiler(); + MessageCollectorImpl messageCollector = new MessageCollectorImpl(); + K2JVMCompilerArguments k2JvmArguments = new K2JVMCompilerArguments(); + k2JvmArguments.setClasspath( + String.join( + File.pathSeparator, filterBootClassPath( List.of( + "kotlin-stdlib", + "kotlin-reflect" + ) ) + ) + ); + k2JvmArguments.setNoStdlib( true ); + k2JvmArguments.setNoReflect( true ); + String packageName = context.getRequiredTestClass() + .getPackageName(); + String sourcePrefix = + SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; + k2JvmArguments.setFreeArgs( Arrays.asList( + compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.joining( File.pathSeparator ) ) + ) ); + k2JvmArguments.setVerbose( true ); + k2JvmArguments.setSuppressWarnings( false ); + k2JvmArguments.setDestination( classOutputDir ); + + ExitCode kotlinExitCode = k2JvmCompiler.exec( messageCollector, Services.EMPTY, k2JvmArguments ); + + kotlinCompilationOutcome = CompilationOutcomeDescriptor.forResult( + sourceOutputDir, + kotlinExitCode, + messageCollector.getMessages() + ); + + } + return kotlinCompilationOutcome; + } + protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( CompilationRequest compilationRequest, String sourceOutputDir, diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index c356347664..4755df0bd1 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -68,24 +68,27 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe return clHelper.compileInOtherClassloader( compilationRequest, - getTestCompilationClasspath( compilationRequest ), + getTestCompilationClasspath( compilationRequest, classOutputDir ), getSourceFiles( compilationRequest.getSourceClasses() ), SOURCE_DIR, sourceOutputDir, classOutputDir ); } - private static List getTestCompilationClasspath(CompilationRequest request) { + private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); + if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.add( classOutputDir ); + } return testCompilationPaths; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 5185bfb7ed..b6d8afd3bf 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -59,7 +59,10 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); try { - fileManager.setLocation( StandardLocation.CLASS_PATH, getCompilerClasspathFiles( compilationRequest ) ); + fileManager.setLocation( + StandardLocation.CLASS_PATH, + getCompilerClasspathFiles( compilationRequest, classOutputDir ) + ); fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); } @@ -99,20 +102,24 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe diagnostics.getDiagnostics() ); } - private static List getCompilerClasspathFiles(CompilationRequest request) { + private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } + if ( !request.getKotlinSources().isEmpty() ) { + compilerClasspathFiles.add( new File( classOutputDir ) ); + } + return compilerClasspathFiles; } From d3006640938014a1d8fa193d735cee934771309b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 12:30:13 +0100 Subject: [PATCH 129/214] Improve support for Kotlin Data Classes (#3978) * Properly handle single field data classes * Properly handle multi constructor data classes * Properly handle all defaults constructor data classes Fixes #2281, #2577, #3031 --- NEXT_RELEASE_CHANGELOG.md | 6 + distribution/pom.xml | 5 + .../main/asciidoc/chapter-2-set-up.asciidoc | 71 +++- parent/pom.xml | 5 + processor/pom.xml | 5 + .../ap/internal/model/BeanMappingMethod.java | 22 ++ .../ap/internal/model/common/Type.java | 156 ++++++++ .../internal/util/kotlin/KotlinMetadata.java | 21 + .../ap/spi/DefaultAccessorNamingStrategy.java | 68 +++- .../test/kotlin/data/AllDefaultsProperty.kt | 11 + .../data/AllDefaultsPropertyMapper.java | 39 ++ .../ap/test/kotlin/data/KotlinDataTest.java | 370 ++++++++++++++++++ .../kotlin/data/MultiConstructorProperty.kt | 13 + .../data/MultiConstructorPropertyMapper.java | 45 +++ .../data/MultiDefaultConstructorProperty.kt | 18 + ...MultiDefaultConstructorPropertyMapper.java | 45 +++ .../data/MultiSimilarConstructorProperty.kt | 50 +++ ...MultiSimilarConstructorPropertyMapper.java | 57 +++ .../ap/test/kotlin/data/SingleProperty.kt | 11 + .../kotlin/data/SinglePropertyMapper.java | 32 ++ .../org/mapstruct/ap/testutil/WithKotlin.java | 29 ++ .../testutil/WithProcessorDependencies.java | 22 ++ .../ap/testutil/WithProcessorDependency.java | 28 ++ .../testutil/runner/CompilationRequest.java | 8 + .../testutil/runner/CompilingExtension.java | 22 ++ .../runner/EclipseCompilingExtension.java | 18 +- .../runner/JdkCompilingExtension.java | 31 +- 27 files changed, 1186 insertions(+), 22 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 2d0555cb24..f279193f6a 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,12 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. + - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: + - Single field data classes + - Proper primary constructor detection + - Data classes with multiple constructors + - Data classes with all default parameters ### Enhancements diff --git a/distribution/pom.xml b/distribution/pom.xml index db654236e4..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -39,6 +39,11 @@ org.mapstruct.tools.gem gem-api + + + org.jetbrains.kotlin + kotlin-metadata-jvm + jakarta.xml.bind diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 58759ff8e0..86ed345630 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -35,10 +35,10 @@ For Maven based projects add the following to your POM file in order to use MapS org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -141,10 +141,10 @@ When invoking javac directly, these options are passed to the compiler in the fo org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -323,3 +323,62 @@ Some features include: * Code completion in `target` and `source` * Quick Fixes + +[[kotlin-setup]] +=== Kotlin Support + +MapStruct provides support for Kotlin interoperability with Java. + +When using MapStruct with Kotlin, it's recommended to add the `org.jetbrains.kotlin:kotlin-metadata-jvm` library to enable proper introspection of Kotlin metadata: + +.Maven configuration for Kotlin support +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + + + + + + +---- +==== + +.Gradle configuration for Kotlin support +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- +dependencies { + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.jetbrains.kotlin:kotlin-metadata-jvm:${kotlinVersion}" +} +---- +==== + +[NOTE] +==== +The `org.jetbrains.kotlin:kotlin-metadata-jvm` dependency is optional but highly recommended when working with Kotlin and Java. +Without it, MapStruct will still work but may not handle complex Kotlin scenarios optimally. +==== \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 5c3a73da88..8c1266ed19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -159,6 +159,11 @@ pom import + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + org.junit junit-bom diff --git a/processor/pom.xml b/processor/pom.xml index 9f24281e33..c51c2bca26 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-metadata-jvm + provided + org.jetbrains.kotlin kotlin-compiler-embeddable diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 39222efc88..619272a175 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -68,6 +68,7 @@ import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; import static org.mapstruct.ap.internal.util.Collections.first; @@ -904,6 +905,27 @@ private ConstructorAccessor getConstructorAccessor(Type type) { return new ConstructorAccessor( parameterBindings, constructorAccessors ); } + KotlinMetadata kotlinMetadata = type.getKotlinMetadata(); + if ( kotlinMetadata != null && kotlinMetadata.isDataClass() ) { + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + ExecutableElement primaryConstructor = kotlinMetadata.determinePrimaryConstructor( constructors ); + + return primaryConstructor != null ? getConstructorAccessor( type, primaryConstructor ) : null; + } + List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 24f23ecea8..313ff38125 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -43,6 +43,13 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleTypeVisitor8; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.KmClass; +import kotlin.metadata.KmConstructor; +import kotlin.metadata.jvm.JvmExtensionsKt; +import kotlin.metadata.jvm.JvmMethodSignature; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.ElementUtils; @@ -58,6 +65,7 @@ import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static java.util.Collections.emptyList; import static org.mapstruct.ap.internal.util.Collections.first; @@ -75,6 +83,7 @@ */ public class Type extends ModelElement implements Comparable { private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD; + private static final boolean KOTLIN_METADATA_JVM_PRESENT; static { Method permittedSubclassesMethod; @@ -85,6 +94,16 @@ public class Type extends ModelElement implements Comparable { permittedSubclassesMethod = null; } SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod; + + boolean kotlinMetadataJvmPresent; + try { + Class.forName( "kotlin.metadata.jvm.KotlinClassMetadata", false, ModelElement.class.getClassLoader() ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; } private final TypeUtils typeUtils; @@ -139,6 +158,8 @@ public class Type extends ModelElement implements Comparable { private Type boxedEquivalent = null; private Boolean hasAccessibleConstructor; + private KotlinMetadata kotlinMetadata; + private boolean kotlinMetadataInitialized; private final Filters filters; @@ -1370,6 +1391,23 @@ public boolean hasAccessibleConstructor() { return hasAccessibleConstructor; } + public KotlinMetadata getKotlinMetadata() { + if ( !kotlinMetadataInitialized ) { + kotlinMetadataInitialized = true; + if ( typeElement != null && KOTLIN_METADATA_JVM_PRESENT ) { + Metadata metadataAnnotation = typeElement.getAnnotation( Metadata.class ); + if ( metadataAnnotation != null ) { + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( metadataAnnotation ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + kotlinMetadata = new KotlinMetadataImpl( (KotlinClassMetadata.Class) classMetadata ); + } + } + } + } + + return kotlinMetadata; + } + /** * Returns the direct supertypes of a type. The interface types, if any, * will appear last in the list. @@ -1831,4 +1869,122 @@ public List getPermittedSubclasses() { } } + private class KotlinMetadataImpl implements KotlinMetadata { + + private final KotlinClassMetadata.Class kotlinClassMetadata; + + private KotlinMetadataImpl(KotlinClassMetadata.Class kotlinClassMetadata) { + this.kotlinClassMetadata = kotlinClassMetadata; + } + + @Override + public boolean isDataClass() { + return Attributes.isData( kotlinClassMetadata.getKmClass() ); + } + + @Override + public ExecutableElement determinePrimaryConstructor(List constructors) { + if ( constructors.size() == 1 ) { + // If we have one constructor, that this constructor is the primary one + return constructors.get( 0 ); + } + KmClass kmClass = kotlinClassMetadata.getKmClass(); + KmConstructor primaryKmConstructor = null; + for ( KmConstructor constructor : kmClass.getConstructors() ) { + if ( !Attributes.isSecondary( constructor ) ) { + primaryKmConstructor = constructor; + } + + } + + if ( primaryKmConstructor == null ) { + return null; + } + + List sameParametersSizeConstructors = new ArrayList<>(); + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getParameters().size() == primaryKmConstructor.getValueParameters().size() ) { + sameParametersSizeConstructors.add( constructor ); + } + } + + if ( sameParametersSizeConstructors.size() == 1 ) { + return sameParametersSizeConstructors.get( 0 ); + } + + JvmMethodSignature signature = JvmExtensionsKt.getSignature( primaryKmConstructor ); + if ( signature == null ) { + return null; + } + + String signatureDescriptor = signature.getDescriptor(); + for ( ExecutableElement constructor : constructors ) { + String constructorDescriptor = buildJvmConstructorDescriptor( constructor ); + if ( signatureDescriptor.equals( constructorDescriptor ) ) { + return constructor; + } + } + + return null; + } + + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { + StringBuilder signature = new StringBuilder( "(" ); + + for ( VariableElement param : constructor.getParameters() ) { + signature.append( getJvmTypeDescriptor( param.asType() ) ); + } + + signature.append( ")V" ); + return signature.toString(); + } + + private String getJvmTypeDescriptor(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8() { + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + switch ( t.getKind() ) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case CHAR: + return "C"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + default: + return ""; + } + } + + @Override + public String visitDeclared(DeclaredType t, Void p) { + TypeElement element = (TypeElement) t.asElement(); + String binaryName = elementUtils.getBinaryName( element ).toString(); + return "L" + binaryName.replace( '.', '/' ) + ";"; + } + + @Override + public String visitArray(ArrayType t, Void p) { + return "[" + getJvmTypeDescriptor( t.getComponentType() ); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return ""; + } + }, null + ); + } + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java new file mode 100644 index 0000000000..d58ab75c79 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.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.kotlin; + +import java.util.List; +import javax.lang.model.element.ExecutableElement; + +/** + * Information about a type in case it's a Kotlin type. + * + * @author Filip Hrisafov + */ +public interface KotlinMetadata { + + boolean isDataClass(); + + ExecutableElement determinePrimaryConstructor(List constructors); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 82ecb4e17b..511504c937 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -7,6 +7,7 @@ import java.util.regex.Pattern; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -17,6 +18,9 @@ import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.spi.util.IntrospectorUtils; /** @@ -28,6 +32,24 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + private static final boolean KOTLIN_METADATA_JVM_PRESENT; + + static { + boolean kotlinMetadataJvmPresent; + try { + Class.forName( + "kotlin.metadata.jvm.KotlinClassMetadata", + false, + AccessorNamingStrategy.class.getClassLoader() + ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; + } + protected Elements elementUtils; protected Types typeUtils; @@ -102,10 +124,48 @@ public boolean isSetterMethod(ExecutableElement method) { } protected boolean isFluentSetter(ExecutableElement method) { - return method.getParameters().size() == 1 && - !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && - !isAdderWithUpperCase4thCharacter( method ) && - typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); + if ( method.getParameters().size() != 1 ) { + return false; + } + Element methodOwnerElement = method.getEnclosingElement(); + if ( !canMethodOwnerBeUsedInFluentSetter( methodOwnerElement ) ) { + return false; + } + return !isAdderWithUpperCase4thCharacter( method ) && + typeUtils.isAssignable( method.getReturnType(), methodOwnerElement.asType() ); + } + + private boolean canMethodOwnerBeUsedInFluentSetter(Element methodOwnerElement) { + if ( !( methodOwnerElement instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) methodOwnerElement; + if ( JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName().toString() ).matches() ) { + return false; + } + if ( isKotlinDataClass( typeElement ) ) { + return false; + } + + return true; + } + + private boolean isKotlinDataClass(TypeElement typeElement) { + if ( !KOTLIN_METADATA_JVM_PRESENT ) { + return false; + } + + Metadata kotlinMetadata = typeElement.getAnnotation( Metadata.class ); + if ( kotlinMetadata == null ) { + return false; + } + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( kotlinMetadata ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + return Attributes.isData( ( (KotlinClassMetadata.Class) classMetadata ).getKmClass() ); + } + + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt new file mode 100644 index 0000000000..e452102f6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class AllDefaultsProperty(val firstName: String? = null, val lastName: String? = null) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java new file mode 100644 index 0000000000..1f8aab4624 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface AllDefaultsPropertyMapper { + + AllDefaultsPropertyMapper INSTANCE = Mappers.getMapper( AllDefaultsPropertyMapper.class ); + + AllDefaultsProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 310039363d..1daa19c231 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -8,7 +8,11 @@ import org.junit.jupiter.api.Nested; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.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; @@ -51,4 +55,370 @@ void shouldMapIntoData() { } } + + @Nested + @WithClasses({ + SinglePropertyMapper.class, + }) + @WithKotlinSources("SingleProperty.kt") + class SingleData { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = SinglePropertyMapper.class, + line = 19, + message = "Unmapped target property: \"copy\"." + ) + } + ) + void shouldCompileWithWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + } + + @Nested + @WithClasses({ + AllDefaultsPropertyMapper.class, + }) + @WithKotlinSources("AllDefaultsProperty.kt") + class AllDefaults { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = AllDefaultsPropertyMapper.class, + line = 19, + message = "No target property found for target \"AllDefaultsProperty\"." + ) + } + ) + void shouldCompileWithWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + } + + @Nested + @WithClasses({ + MultiConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiConstructorProperty.kt") + class MultiConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiConstructorProperty property = MultiConstructorPropertyMapper.INSTANCE + .map( new MultiConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*MultiConstructorProperty: " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\), " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\)\\. " + + "Either declare parameterless constructor or annotate the default constructor with an " + + "annotation named @Default\\." + ) + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiSimilarConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiSimilarConstructorProperty.kt") + class MultiSimilarConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + PrimaryString primaryString = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + "Kermit the Frog" + ); + assertThat( primaryString ).isNotNull(); + assertThat( primaryString.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryString.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryString.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + + PrimaryInt primaryInt = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42 + ); + assertThat( primaryInt ).isNotNull(); + assertThat( primaryInt.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryInt.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryInt.getAge() ).isEqualTo( 42 ); + + PrimaryLong primaryLong = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42L + ); + assertThat( primaryLong ).isNotNull(); + assertThat( primaryLong.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryLong.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryLong.getAge() ).isEqualTo( 42 ); + + PrimaryBoolean primaryBoolean = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + true + ); + assertThat( primaryBoolean ).isNotNull(); + assertThat( primaryBoolean.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryBoolean.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryBoolean.getActive() ).isTrue(); + + PrimaryByte primaryByte = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (byte) 4 + ); + assertThat( primaryByte ).isNotNull(); + assertThat( primaryByte.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryByte.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryByte.getB() ).isEqualTo( (byte) 4 ); + + PrimaryShort primaryShort = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (short) 4 + ); + assertThat( primaryShort ).isNotNull(); + assertThat( primaryShort.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryShort.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryShort.getAge() ).isEqualTo( (short) 4 ); + + PrimaryChar primaryChar = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 't' + ); + assertThat( primaryChar ).isNotNull(); + assertThat( primaryChar.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryChar.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryChar.getC() ).isEqualTo( 't' ); + + PrimaryFloat primaryFloat = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2f + ); + assertThat( primaryFloat ).isNotNull(); + assertThat( primaryFloat.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryFloat.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryFloat.getPrice() ).isEqualTo( 42.2f ); + + PrimaryDouble primaryDouble = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2 + ); + assertThat( primaryDouble ).isNotNull(); + assertThat( primaryDouble.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryDouble.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryDouble.getPrice() ).isEqualTo( 42.2d ); + + PrimaryArray primaryArray = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + new String[] { "Kermit", "the Frog" } + ); + assertThat( primaryArray ).isNotNull(); + assertThat( primaryArray.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryArray.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryArray.getElements() ).containsExactly( "Kermit", "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryString" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 21, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryInt" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 23, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryLong" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 25, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryBoolean" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 27, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryByte" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 29, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryShort" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 31, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryChar" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 33, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryFloat" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 35, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryDouble" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 37, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryArray" + ) + + + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiDefaultConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiDefaultConstructorProperty.kt") + class MultiDefaultConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt new file mode 100644 index 0000000000..46cb8034e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiConstructorProperty(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java new file mode 100644 index 0000000000..3186226a57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiConstructorPropertyMapper { + + MultiConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiConstructorPropertyMapper.class ); + + MultiConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt new file mode 100644 index 0000000000..712cf1151b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: String?, val displayName: String?) { + @Default + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java new file mode 100644 index 0000000000..da04c01fb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiDefaultConstructorPropertyMapper { + + MultiDefaultConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiDefaultConstructorPropertyMapper.class ); + + MultiDefaultConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt new file mode 100644 index 0000000000..e122169075 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class PrimaryString(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?, age: Int) : this(firstName, lastName, null as String?) +} + +data class PrimaryInt(var firstName: String?, var lastName: String?, var age: Int) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryLong(var firstName: String?, var lastName: String?, var age: Long) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryBoolean(var firstName: String?, var lastName: String?, var active: Boolean) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, false) +} + +data class PrimaryByte(var firstName: String?, var lastName: String?, var b: Byte) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryShort(var firstName: String?, var lastName: String?, var age: Short) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryChar(var firstName: String?, var lastName: String?, var c: Char) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 'a') +} + +data class PrimaryFloat(var firstName: String?, var lastName: String?, var price: Float) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0f) +} + +data class PrimaryDouble(var firstName: String?, var lastName: String?, var price: Double) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0) +} + +@Suppress("ArrayInDataClass") +data class PrimaryArray(var firstName: String?, var lastName: String?, var elements: Array) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, emptyArray()) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java new file mode 100644 index 0000000000..86021cd6d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiSimilarConstructorPropertyMapper { + + MultiSimilarConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiSimilarConstructorPropertyMapper.class ); + + PrimaryString map(Source source, String displayName); + + PrimaryInt map(Source source, int age); + + PrimaryLong map(Source source, long age); + + PrimaryBoolean map(Source source, boolean active); + + PrimaryByte map(Source source, byte b); + + PrimaryShort map(Source source, short age); + + PrimaryChar map(Source source, char c); + + PrimaryFloat map(Source source, float price); + + PrimaryDouble map(Source source, double price); + + PrimaryArray map(Source source, String[] elements); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt new file mode 100644 index 0000000000..1bb63c410d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class SingleProperty(val value: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java new file mode 100644 index 0000000000..d3d70f261c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SinglePropertyMapper { + + SinglePropertyMapper INSTANCE = Mappers.getMapper( SinglePropertyMapper.class ); + + SingleProperty map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java new file mode 100644 index 0000000000..f4bd2c5970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithProcessorDependency({ + "kotlin-stdlib", + "kotlin-metadata-jvm", +}) +@WithTestDependency("kotlin-stdlib") +public @interface WithKotlin { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java new file mode 100644 index 0000000000..cccb30b6b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithProcessorDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithProcessorDependencies { + + WithProcessorDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java new file mode 100644 index 0000000000..8dbf8c8acd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies the group id of the additional dependencies that should be added to the processor path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable(WithProcessorDependencies.class) +public @interface WithProcessorDependency { + /** + * @return The group ids of the additional dependencies for the processor path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 8622fbe92c..c9718c6c21 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,16 +19,19 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection processorDependencies; private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, List processorOptions, Collection testDependencies, + Collection processorDependencies, Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.processorDependencies = processorDependencies; this.kotlinSources = kotlinSources; } @@ -61,6 +64,7 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) + && processorDependencies.equals( other.processorDependencies ) && sourceClasses.equals( other.sourceClasses ) && kotlinSources.equals( other.kotlinSources ); } @@ -81,6 +85,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getProcessorDependencies() { + return processorDependencies; + } + public Collection getKotlinSources() { return kotlinSources; } 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 7ff78753a6..d8ed3b953d 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 @@ -40,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithProcessorDependency; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -152,6 +153,15 @@ protected static List filterBootClassPath(Collection whitelist) return classpath; } + protected static Collection getProcessorClasspathDependencies(CompilationRequest request, + String additionalCompilerClasspath) { + List processClassPaths = filterBootClassPath( request.getProcessorDependencies() ); + if ( additionalCompilerClasspath != null ) { + processClassPaths.add( additionalCompilerClasspath ); + } + return processClassPaths; + } + private static boolean isWhitelisted(String path, Collection whitelist) { return whitelist.stream().anyMatch( path::contains ); } @@ -392,6 +402,17 @@ private Collection getAdditionalTestDependencies(Method testMethod, Clas return testDependencies; } + private Collection getAdditionalProcessorDependencies(Method testMethod, Class testClass) { + Collection processorDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + return processorDependencies; + } + private void addServices(Map, Class> services, List withImplementations) { for ( WithServiceImplementation withImplementation : withImplementations ) { addService( services, withImplementation ); @@ -486,6 +507,7 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), getAdditionalTestDependencies( testMethod, testClass ), + getAdditionalProcessorDependencies( testMethod, testClass ), getKotlinSources( context ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index 4755df0bd1..6b13036be3 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -47,8 +47,12 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe String sourceOutputDir, String classOutputDir, String additionalCompilerClasspath) { + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader compilerClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER; } else { @@ -59,7 +63,7 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH ) .withPaths( PROCESSOR_CLASSPATH ) .withOriginOf( ClassLoaderExecutor.class ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -77,16 +81,19 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + processorDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); - if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.addAll( filterBootClassPath( processorDependencies ) ); + if ( !kotlinSources.isEmpty() ) { testCompilationPaths.add( classOutputDir ); } return testCompilationPaths; @@ -96,6 +103,7 @@ private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { return new FilteringParentClassLoader( // reload eclipse compiler classes "org.eclipse.", + "kotlin.", // reload mapstruct processor classes "org.mapstruct.ap.internal.", "org.mapstruct.ap.spi.", diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index b6d8afd3bf..115618382c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -39,7 +39,7 @@ class JdkCompilingExtension extends CompilingExtension { private static final List COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH ); private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER = - new ModifiableURLClassLoader( new FilteringParentClassLoader( "org.mapstruct." ) ) + new ModifiableURLClassLoader( newFilteringClassLoaderForJdk() ) .withPaths( PROCESSOR_CLASSPATH ); JdkCompilingExtension() { @@ -70,15 +70,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe throw new RuntimeException( e ); } + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader processorClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER; } else { processorClassloader = new ModifiableURLClassLoader( - new FilteringParentClassLoader( "org.mapstruct." ) ) + newFilteringClassLoaderForJdk() + .hidingClasses( compilationRequest.getServices().values() ) + ) .withPaths( PROCESSOR_CLASSPATH ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -104,19 +110,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + processorDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } - if ( !request.getKotlinSources().isEmpty() ) { + if ( !kotlinSources.isEmpty() ) { compilerClasspathFiles.add( new File( classOutputDir ) ); } @@ -161,4 +169,13 @@ protected List filterExpectedDiagnostics(List Date: Thu, 29 Jan 2026 15:38:08 +0100 Subject: [PATCH 130/214] Add test case with Kotlin unsigned type (#3979) --- .../ap/internal/model/BeanMappingMethod.java | 5 +- .../mapstruct/ap/test/kotlin/data/Default.kt | 10 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 50 ++++++++++++++++++- .../data/MultiDefaultConstructorProperty.kt | 3 -- .../ap/test/kotlin/data/UnsignedProperty.kt | 17 +++++++ .../kotlin/data/UnsignedPropertyMapper.java | 35 +++++++++++++ .../testutil/runner/CompilingExtension.java | 11 ++-- 7 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 619272a175..086773dbe9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -910,8 +910,11 @@ private ConstructorAccessor getConstructorAccessor(Type type) { List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); - for ( ExecutableElement constructor : constructors ) { + Iterator constructorIterator = constructors.iterator(); + while ( constructorIterator.hasNext() ) { + ExecutableElement constructor = constructorIterator.next(); if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + constructorIterator.remove(); continue; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt new file mode 100644 index 0000000000..58ebbd128d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 1daa19c231..f55cb42535 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -10,6 +10,7 @@ import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -388,7 +389,10 @@ void shouldFailToCompile() { @WithClasses({ MultiDefaultConstructorPropertyMapper.class, }) - @WithKotlinSources("MultiDefaultConstructorProperty.kt") + @WithKotlinSources({ + "MultiDefaultConstructorProperty.kt", + "Default.kt" + }) class MultiDefaultConstructor { @ProcessorTest @@ -421,4 +425,48 @@ void shouldCompileWithoutKotlin() { assertThat( property.getDisplayName() ).isNull(); } } + + @Nested + @WithClasses({ + UnsignedPropertyMapper.class, + }) + @WithKotlinSources("UnsignedProperty.kt") + class Unsigned { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + + @ProcessorTest + @WithTestDependency( "kotlin-stdlib" ) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = UnsignedPropertyMapper.class, + line = 19, + messageRegExp = "Unmapped target property: \"copy-.*\"\\." + ) + } + ) + void shouldCompileWithoutKotlinJvmMetadata() { + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt index 712cf1151b..9f9af16053 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -13,6 +13,3 @@ data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) } -@Target(AnnotationTarget.CONSTRUCTOR) -@Retention(AnnotationRetention.BINARY) -annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt new file mode 100644 index 0000000000..c52426b3af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class UnsignedProperty(val age: UInt?) { + // Java-friendly secondary constructor + constructor(age: Int?) : this(age?.toUInt()) + + @JvmName("getAge") + fun getAgeAsLong(): Long? = age?.toLong() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java new file mode 100644 index 0000000000..4f055c7b41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UnsignedPropertyMapper { + + UnsignedPropertyMapper INSTANCE = Mappers.getMapper( UnsignedPropertyMapper.class ); + + UnsignedProperty map(Source source); + + Source map(UnsignedProperty property); + + class Source { + + private final int age; + + public Source(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index d8ed3b953d..08056ca697 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 @@ -601,12 +601,11 @@ File.pathSeparator, filterBootClassPath( List.of( .getPackageName(); String sourcePrefix = SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; - k2JvmArguments.setFreeArgs( Arrays.asList( - compilationRequest.getKotlinSources() - .stream() - .map( kotlinSource -> sourcePrefix + kotlinSource ) - .collect( Collectors.joining( File.pathSeparator ) ) - ) ); + k2JvmArguments.setFreeArgs( compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.toList() ) + ); k2JvmArguments.setVerbose( true ); k2JvmArguments.setSuppressWarnings( false ); k2JvmArguments.setDestination( classOutputDir ); From 3f20144b2f0529e048eee06cdcd93015686544c8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 15:54:38 +0100 Subject: [PATCH 131/214] Add test case for Kotlin property named default (#3980) --- .../ap/test/kotlin/data/DefaultProperty.kt | 16 ++++++ .../kotlin/data/DefaultPropertyMapper.java | 22 +++++++++ .../ap/test/kotlin/data/KotlinDataTest.java | 49 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt new file mode 100644 index 0000000000..f5ff27d3d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class DefaultPropertySource(val default: Boolean, val identifier: String?) + +class DefaultPropertyTarget( + var default: Boolean, + var identifier: String? +) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java new file mode 100644 index 0000000000..ce08e26e7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface DefaultPropertyMapper { + + DefaultPropertyMapper INSTANCE = Mappers.getMapper( DefaultPropertyMapper.class ); + + DefaultPropertyTarget map(DefaultPropertySource source); + + DefaultPropertySource map(DefaultPropertyTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index f55cb42535..94d0d90d75 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -469,4 +469,53 @@ void shouldCompileWithoutKotlinJvmMetadata() { assertThat( source.getAge() ).isEqualTo( 20 ); } } + + @Nested + @WithClasses({ + DefaultPropertyMapper.class, + }) + @WithKotlinSources("DefaultProperty.kt") + class Default { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + } + } } From ec1e5f9317d399b3af420191bc5d6d025726073d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 16:07:57 +0100 Subject: [PATCH 132/214] Add tests for sealed classes in processor module --- ...eatureCompilationExclusionCliEnhancer.java | 6 ++ .../subclassmapping/jdk17/sealed/Motor.java | 42 ++++++++++ .../jdk17/sealed/MotorDto.java | 42 ++++++++++ .../jdk17/sealed/SealedSubclassMapper.java | 31 +++++++ .../jdk17/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ .../subclassmapping/jdk17/sealed/Vehicle.java | 51 ++++++++++++ .../jdk17/sealed/VehicleCollection.java | 17 ++++ .../jdk17/sealed/VehicleCollectionDto.java | 17 ++++ .../jdk17/sealed/VehicleDto.java | 51 ++++++++++++ 9 files changed, 337 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java 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 0cc1a87818..7d397c9499 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -35,6 +35,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { additionalExcludes.add( "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); @@ -43,10 +45,14 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces case JAVA_9: // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_11: additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_17: diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java new file mode 100644 index 0000000000..3498bb09e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.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.subclassmapping.jdk17.sealed; + +public abstract sealed class Motor extends Vehicle { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class Davidson extends Motor { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class Harley extends Motor { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java new file mode 100644 index 0000000000..a7ec0be4a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.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.subclassmapping.jdk17.sealed; + +public abstract sealed class MotorDto extends VehicleDto { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class DavidsonDto extends MotorDto { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class HarleyDto extends MotorDto { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..806eec78a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f9f59c45ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Motor.class, + MotorDto.class, + SealedSubclassMapper.class, + Vehicle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + VehicleDto.class +}) +public class SealedSubclassTest { + + @ProcessorTest(Compiler.JDK) + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java new file mode 100644 index 0000000000..c558ec877e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class Vehicle permits Motor, Vehicle.Bike, Vehicle.Car { + private String name; + private String vehicleManufacturingCompany; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVehicleManufacturingCompany() { + return vehicleManufacturingCompany; + } + + public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) { + this.vehicleManufacturingCompany = vehicleManufacturingCompany; + } + + public static final class Bike extends Vehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class Car extends Vehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java new file mode 100644 index 0000000000..2285789baa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java new file mode 100644 index 0000000000..6c9dc7d382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java new file mode 100644 index 0000000000..40cbb0df60 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class VehicleDto permits VehicleDto.CarDto, VehicleDto.BikeDto, MotorDto { + private String name; + private String maker; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaker() { + return maker; + } + + public void setMaker(String maker) { + this.maker = maker; + } + + public static final class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} From 49963e34dec5cebc7a3dceda03a568d96939c2f1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 17:42:00 +0100 Subject: [PATCH 133/214] Upgrade to Checkstyle 13.0.0 Fix errors which are now reported. Run only on Java 21 or later for integration tests module --- integrationtest/pom.xml | 36 ++++++++++++------- parent/pom.xml | 4 +-- .../ap/internal/model/common/Assignment.java | 1 - .../ap/internal/model/source/Method.java | 1 - .../processor/ModelElementProcessor.java | 2 +- .../bugs/_3485/ErroneousIssue3485Mapper.java | 1 + .../ap/test/bugs/_3485/Issue3485Test.java | 2 +- .../java8time/SourceTargetMapper.java | 1 + .../java/MultiLineExpressionMapper.java | 1 - .../expressions/java/SourceTargetMapper.java | 1 - .../compilation/annotation/ExpectedNote.java | 2 +- .../testutil/runner/CompilingExtension.java | 8 ++--- 12 files changed, 34 insertions(+), 26 deletions(-) diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -107,19 +107,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - @@ -138,5 +125,28 @@ + + jdk-21-or-newer + + [21 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + check-style + verify + + checkstyle + + + + + + + diff --git a/parent/pom.xml b/parent/pom.xml index 8c1266ed19..f7d26dfd22 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -36,7 +36,7 @@ 3.1.0 6.2.7 1.6.0 - 8.36.1 + 13.0.0 5.10.1 2.2.0 1.12.0 @@ -376,7 +376,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.1 + 3.6.0 build-config/checkstyle.xml true diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java index 293df9516b..7ed0140d81 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java @@ -147,7 +147,6 @@ public boolean isConverted() { */ void setSourceLoopVarName(String sourceLoopVarName); - /** * Returns whether the type of assignment * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index bea4d56101..ba32b12b46 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -97,7 +97,6 @@ public interface Method { */ Parameter getTargetTypeParameter(); - /** * Returns the {@link Accessibility} of this method. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java index d43b52953d..4a30b776b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java @@ -40,7 +40,7 @@ public interface ModelElementProcessor { * * @author Gunnar Morling */ - public interface ProcessorContext { + interface ProcessorContext { Filer getFiler(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java index 5714e04684..dc1558907c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java @@ -17,6 +17,7 @@ public interface ErroneousIssue3485Mapper { ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + class Target { private final String value; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java index 7427b63a4e..964de36ec8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -24,7 +24,7 @@ public class Issue3485Test { diagnostics = { @Diagnostic(type = ErroneousIssue3485Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 33, + line = 34, message = "Using @Mapping( target = \".\") requires a source property. Expression or " + "constant cannot be used as a source.") }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java index 37fc7d6cbe..f2351f4175 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java @@ -26,6 +26,7 @@ public interface SourceTargetMapper { String LOCAL_TIME_FORMAT = "HH:mm"; SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + @Mappings( { @Mapping( target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT ), @Mapping( target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT ), @Mapping( target = "localDate", dateFormat = LOCAL_DATE_FORMAT ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java index 50d0b4b2cd..76286bfa3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java @@ -17,7 +17,6 @@ @Mapper(imports = TimeAndFormat.class) public interface MultiLineExpressionMapper { - MultiLineExpressionMapper INSTANCE = Mappers.getMapper( MultiLineExpressionMapper.class ); @Mappings({ diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java index 0e462ab3b3..56809494a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java @@ -18,7 +18,6 @@ @Mapper( imports = TimeAndFormat.class ) public interface SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings( { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java index 1cb95f52c2..2070c028c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java @@ -29,7 +29,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) - public @interface ExpectedNotes { + @interface ExpectedNotes { /** * Regexp for the note to match. diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 08056ca697..bcfc707d68 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 @@ -25,11 +25,11 @@ import java.util.Set; import java.util.stream.Collectors; +import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; import com.puppycrawl.tools.checkstyle.DefaultLogger; import com.puppycrawl.tools.checkstyle.PropertiesExpander; -import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; @@ -225,10 +225,10 @@ private void assertCheckstyleRules() throws Exception { ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); checker.addListener( new DefaultLogger( - NullOutputStream.NULL_OUTPUT_STREAM, - AutomaticBean.OutputStreamOptions.CLOSE, + NullOutputStream.INSTANCE, + AbstractAutomaticBean.OutputStreamOptions.CLOSE, errorStream, - AutomaticBean.OutputStreamOptions.CLOSE + AbstractAutomaticBean.OutputStreamOptions.CLOSE ) ); From 60151132145a606990224376d35be6eaff9e250f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 22:33:00 +0100 Subject: [PATCH 134/214] #3404 Add support for Kotlin Sealed classes --- NEXT_RELEASE_CHANGELOG.md | 1 + .../ap/internal/model/common/Type.java | 26 ++++++ .../internal/util/kotlin/KotlinMetadata.java | 5 ++ .../mapstruct/ap/test/kotlin/sealed/Dtos.kt | 36 +++++++++ .../ap/test/kotlin/sealed/Entities.kt | 36 +++++++++ .../kotlin/sealed/SealedSubclassMapper.java | 31 +++++++ .../kotlin/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ 7 files changed, 215 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index f279193f6a..1f7026c127 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -7,6 +7,7 @@ - Proper primary constructor detection - Data classes with multiple constructors - Data classes with all default parameters + - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes ### Enhancements diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 313ff38125..8fb10090c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -47,6 +47,7 @@ import kotlin.metadata.Attributes; import kotlin.metadata.KmClass; import kotlin.metadata.KmConstructor; +import kotlin.metadata.Modality; import kotlin.metadata.jvm.JvmExtensionsKt; import kotlin.metadata.jvm.JvmMethodSignature; import kotlin.metadata.jvm.KotlinClassMetadata; @@ -1850,6 +1851,10 @@ public boolean isEnumSet() { * return true if this type is a java 17+ sealed class */ public boolean isSealed() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.isSealedClass(); + } return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals ); } @@ -1858,6 +1863,10 @@ public boolean isSealed() { */ @SuppressWarnings( "unchecked" ) public List getPermittedSubclasses() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.getPermittedSubclasses(); + } if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { return emptyList(); } @@ -1882,6 +1891,11 @@ public boolean isDataClass() { return Attributes.isData( kotlinClassMetadata.getKmClass() ); } + @Override + public boolean isSealedClass() { + return Attributes.getModality( kotlinClassMetadata.getKmClass() ) == Modality.SEALED; + } + @Override public ExecutableElement determinePrimaryConstructor(List constructors) { if ( constructors.size() == 1 ) { @@ -1928,6 +1942,18 @@ public ExecutableElement determinePrimaryConstructor(List con return null; } + @Override + public List getPermittedSubclasses() { + List sealedSubclassNames = kotlinClassMetadata.getKmClass().getSealedSubclasses(); + List permittedSubclasses = new ArrayList<>( sealedSubclassNames.size() ); + for ( String sealedSubclassName : sealedSubclassNames ) { + Type subclassType = typeFactory.getType( sealedSubclassName.replace( '/', '.' ) ); + permittedSubclasses.add( subclassType.getTypeMirror() ); + } + + return permittedSubclasses; + } + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { StringBuilder signature = new StringBuilder( "(" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java index d58ab75c79..66a6fafc02 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java @@ -7,6 +7,7 @@ import java.util.List; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; /** * Information about a type in case it's a Kotlin type. @@ -17,5 +18,9 @@ public interface KotlinMetadata { boolean isDataClass(); + boolean isSealedClass(); + ExecutableElement determinePrimaryConstructor(List constructors); + + List getPermittedSubclasses(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt new file mode 100644 index 0000000000..5a9084a685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed + +sealed class VehicleDto { + var name: String? = null + var maker: String? = null + + class BikeDto : VehicleDto() { + var numberOfGears: Int = 0 + } + + class CarDto : VehicleDto() { + var manual: Boolean = false + } + +} + +sealed class MotorDto : VehicleDto() { + var cc: Int = 0 + + class DavidsonDto : MotorDto() { + var numberOfExhausts: Int = 0 + } + + class HarleyDto : MotorDto() { + var engineDb: Int = 0 + } +} + +class VehicleCollectionDto { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt new file mode 100644 index 0000000000..b380bac231 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed + +sealed class Vehicle { + var name: String? = null + var vehicleManufacturingCompany: String? = null + + class Bike : Vehicle() { + var numberOfGears: Int = 0 + } + + class Car : Vehicle() { + var manual: Boolean = false + } + +} + +sealed class Motor : Vehicle() { + var cc: Int = 0 + + class Davidson : Motor() { + var numberOfExhausts: Int = 0 + } + + class Harley : Motor() { + var engineDb: Int = 0 + } +} + +class VehicleCollection { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..bde7dc24cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f7162ec3a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SealedSubclassMapper.class, +}) +@WithKotlinSources({ + "Dtos.kt", + "Entities.kt" +}) +@WithKotlin +public class SealedSubclassTest { + + @ProcessorTest + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} From 9923f78d91a8dae76f210c5de2ac0013baa078b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 30 Jan 2026 12:25:56 +0100 Subject: [PATCH 135/214] #674 Add support for `Optional` (#3971) MapStruct now fully supports Optional as both source and target types: * `Optional` to `Optional` - Both source and target wrapped in `Optional` * `Optional` to Non-`Optional` - Unwrapping `Optional` values * Non-`Optional` to `Optional` - Wrapping values in `Optional` * Always assume that optionals are not `null`, i.e. we can always call `isPresent()` / `isEmpty()` on an `Optional` * Add support for conversions e.g. `Optional` -> `String`, `Optional` -> `String` etc. --------- Co-authored-by: Ken Wang --- NEXT_RELEASE_CHANGELOG.md | 5 + copyright.txt | 3 +- .../mapstruct/NullValueMappingStrategy.java | 6 +- .../NullValuePropertyMappingStrategy.java | 5 +- ...apter-10-advanced-mapping-options.asciidoc | 17 +- .../chapter-3-defining-a-mapper.asciidoc | 415 +++++++++++ .../chapter-5-data-type-conversions.asciidoc | 8 + .../ap/internal/conversion/Conversions.java | 35 + .../OptionalWrapperConversionProvider.java | 97 +++ .../conversion/TypeToOptionalConversion.java | 44 ++ .../ap/internal/model/BeanMappingMethod.java | 160 +++- .../model/FromOptionalTypeConversion.java | 113 +++ .../model/LifecycleMethodResolver.java | 33 +- .../internal/model/MappingBuilderContext.java | 11 + .../ap/internal/model/MappingMethod.java | 6 +- .../model/NestedPropertyMappingMethod.java | 63 +- .../model/PresenceCheckMethodResolver.java | 6 +- .../ap/internal/model/PropertyMapping.java | 16 +- .../model/ToOptionalTypeConversion.java | 119 +++ .../model/assignment/OptionalGetWrapper.java | 35 + .../model/beanmapping/SourceReference.java | 10 + .../model/common/ConversionContext.java | 7 + .../common/DefaultConversionContext.java | 5 + .../ap/internal/model/common/Parameter.java | 26 +- .../ap/internal/model/common/Type.java | 23 +- .../model/presence/OptionalPresenceCheck.java | 77 ++ .../ap/internal/model/source/Method.java | 9 - .../source/selector/SelectionContext.java | 21 +- .../processor/DefaultVersionInformation.java | 7 + .../processor/MapperCreationProcessor.java | 1 + .../creation/MappingResolverImpl.java | 13 +- .../internal/version/VersionInformation.java | 2 + .../ap/internal/model/BeanMappingMethod.ftl | 81 +- .../model/FromOptionalTypeConversion.ftl | 16 + .../model/ToOptionalTypeConversion.ftl | 21 + .../model/assignment/OptionalGetWrapper.ftl | 15 + .../model/assignment/UpdateWrapper.ftl | 4 +- .../ap/internal/model/macro/CommonMacros.ftl | 28 +- .../model/presence/OptionalPresenceCheck.ftl | 19 + .../OptionalEnumToIntegerConversionTest.java | 73 ++ .../_enum/OptionalEnumToIntegerMapper.java | 19 + .../_enum/OptionalEnumToIntegerSource.java | 31 + .../bignumbers/BigDecimalOptionalMapper.java | 19 + .../bignumbers/BigDecimalOptionalSource.java | 139 ++++ .../bignumbers/BigIntegerOptionalMapper.java | 19 + .../bignumbers/BigIntegerOptionalSource.java | 131 ++++ .../bignumbers/BigNumbersConversionTest.java | 150 ++++ .../Java8OptionalTimeConversionTest.java | 466 ++++++++++++ .../conversion/java8time/OptionalSource.java | 160 ++++ .../java8time/OptionalSourceTargetMapper.java | 70 ++ ...ErroneousKitchenDrawerOptionalMapper1.java | 23 + ...ErroneousKitchenDrawerOptionalMapper3.java | 23 + ...ErroneousKitchenDrawerOptionalMapper4.java | 23 + ...ErroneousKitchenDrawerOptionalMapper5.java | 23 + ...ErroneousKitchenDrawerOptionalMapper6.java | 20 + .../lossy/KitchenDrawerOptionalMapper2.java | 23 + .../lossy/KitchenDrawerOptionalMapper6.java | 23 + .../conversion/lossy/LossyConversionTest.java | 94 +++ .../OversizedKitchenDrawerOptionalDto.java | 84 +++ .../lossy/VerySpecialNumberMapper.java | 6 + .../ByteWrapperOptionalSource.java | 121 +++ .../nativetypes/DoubleOptionalSource.java | 121 +++ .../DoubleWrapperOptionalSource.java | 121 +++ .../FloatWrapperOptionalSource.java | 121 +++ .../nativetypes/IntOptionalSource.java | 121 +++ .../nativetypes/IntWrapperOptionalSource.java | 121 +++ .../nativetypes/LongOptionalSource.java | 121 +++ .../LongWrapperOptionalSource.java | 121 +++ .../NumberOptionalConversionTest.java | 357 +++++++++ .../OptionalNumberConversionMapper.java | 52 ++ .../ShortWrapperOptionalSource.java | 121 +++ .../OptionalBeforeAfterMapper.java | 76 ++ .../beforeafter/OptionalBeforeAfterTest.java | 25 + .../ap/test/optional/beforeafter/Source.java | 87 +++ .../ap/test/optional/beforeafter/Target.java | 86 +++ .../optional/builder/OptionalBuilderTest.java | 31 + .../builder/SimpleOptionalBuilderMapper.java | 67 ++ .../optional/custom/CustomOptionalMapper.java | 63 ++ .../optional/custom/CustomOptionalTest.java | 60 ++ .../OptionalDifferentTypesMapper.java | 18 + .../OptionalDifferentTypesTest.java | 204 +++++ .../test/optional/differenttypes/Source.java | 89 +++ .../test/optional/differenttypes/Target.java | 84 +++ .../optional/lifecycle/MappingContext.java | 108 +++ .../lifecycle/OptionalLifecycleTest.java | 165 +++++ .../lifecycle/OptionalToOptionalMapper.java | 27 + .../OptionalToOptionalWithBuilderMapper.java | 24 + .../lifecycle/OptionalToTypeMapper.java | 27 + .../OptionalToTypeMultiSourceMapper.java | 27 + .../ap/test/optional/lifecycle/Source.java | 21 + .../ap/test/optional/lifecycle/Target.java | 39 + .../ap/test/optional/nested/Artist.java | 81 ++ .../optional/nested/OptionalNestedMapper.java | 23 + ...ptionalNestedPresenceCheckFirstMapper.java | 52 ++ .../OptionalNestedPresenceCheckMapper.java | 44 ++ .../optional/nested/OptionalNestedTest.java | 148 ++++ .../ap/test/optional/nested/Source.java | 76 ++ .../ap/test/optional/nested/Target.java | 48 ++ .../test/optional/nested/TargetAggregate.java | 49 ++ .../OptionalNullCheckAlwaysMapper.java | 19 + .../OptionalNullCheckAlwaysTest.java | 84 +++ .../test/optional/nullcheckalways/Source.java | 40 + .../test/optional/nullcheckalways/Target.java | 40 + .../nullvalue/OptionalDefaultMapperTest.java | 2 - .../nullvalue/SimpleConstructorMapper.java | 29 - .../sametype/OptionalSameTypeMapper.java | 18 + .../sametype/OptionalSameTypeTest.java | 186 +++++ .../ap/test/optional/sametype/Source.java | 74 ++ .../ap/test/optional/sametype/Target.java | 70 ++ .../optional/simple/OptionalSimpleTest.java | 110 +++ .../simple/OptionalToOptionalMapper.java | 23 + ...ionalToOptionalNullValueDefaultMapper.java | 25 + .../optional/simple/OptionalToTypeMapper.java | 23 + .../OptionalToTypeNullValueDefaultMapper.java | 24 + .../OptionalToTypeWithMappingMapper.java | 67 ++ .../ap/test/optional/simple/Source.java | 21 + .../ap/test/optional/simple/Target.java | 22 + .../optional/simple/TypeToOptionalMapper.java | 23 + .../optional/update/OptionalUpdateMapper.java | 21 + ...alUpdateNullValuePropertyIgnoreMapper.java | 23 + ...pdateNullValuePropertyToDefaultMapper.java | 23 + .../optional/update/OptionalUpdateTest.java | 117 +++ .../ap/test/optional/update/Source.java | 37 + .../ap/test/optional/update/Target.java | 37 + .../testutil/assertions/JavaFileAssert.java | 2 +- .../OptionalSourceTargetMapperImpl.java | 701 ++++++++++++++++++ .../OptionalBeforeAfterMapperImpl.java | 113 +++ .../OptionalDifferentTypesMapperImpl.java | 89 +++ .../nested/OptionalNestedMapperImpl.java | 71 ++ ...nalNestedPresenceCheckFirstMapperImpl.java | 117 +++ ...OptionalNestedPresenceCheckMapperImpl.java | 100 +++ .../OptionalNullCheckAlwaysMapperImpl.java | 38 + .../sametype/OptionalSameTypeMapperImpl.java | 55 ++ 133 files changed, 8860 insertions(+), 132 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/TargetAggregate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Target.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalSimpleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeWithMappingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/TypeToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyIgnoreMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyToDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Target.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapperImpl.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 1f7026c127..59fed4fed3 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,11 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: + - `Optional` to `Optional` - Both source and target wrapped in `Optional` + - `Optional` to Non-`Optional` - Unwrapping `Optional` values + - Non-`Optional` to `Optional` - Wrapping values in `Optional` + - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. * Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - Single field data classes diff --git a/copyright.txt b/copyright.txt index 71d47a796a..bd0034b076 100644 --- a/copyright.txt +++ b/copyright.txt @@ -38,6 +38,7 @@ Joshua Spoerri - https://github.com/spoerri Jude Niroshan - https://github.com/JudeNiroshan Justyna Kubica-Ledzion - https://github.com/JKLedzion Kemal Özcan - https://github.com/yekeoe +Ken Wang - https://github.com/ro0sterjam Kevin Grüneberg - https://github.com/kevcodez Lukas Lazar - https://github.com/LukeLaz Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis @@ -70,4 +71,4 @@ Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 -Xiu Hong Kooi - https://github.com/kooixh +Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java index 519a28bd81..4cece83031 100644 --- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -13,8 +13,9 @@ public enum NullValueMappingStrategy { /** - * If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no - * alternative strategy is configured globally, for given mapper or method. + * If {@code null} is passed to a mapping method, {@code null} will be returned, unless the return type is + * {@link java.util.Optional Optional}, in which case an empty optional will be returned. + * That's the default behavior if no alternative strategy is configured globally, for given mapper or method. */ RETURN_NULL, @@ -28,6 +29,7 @@ public enum NullValueMappingStrategy { * case. *

  • For iterable mapping methods an empty collection will be returned.
  • *
  • For map mapping methods an empty map will be returned.
  • + *
  • For optional mapping methods an empty optional will be returned.
  • * */ RETURN_DEFAULT; diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 556d4253b1..a58eed8a2a 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -27,7 +27,9 @@ public enum NullValuePropertyMappingStrategy { /** - * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. + * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null} + * or {@link java.util.Optional#empty() Optional.empty()} in case the target type is an + * {@link java.util.Optional Optional}. */ SET_TO_NULL, @@ -36,6 +38,7 @@ public enum NullValuePropertyMappingStrategy { *

    * This means: *

      + *
    1. For {@code Optional} MapStruct generates an {@code Optional.empty()}
    2. *
    3. For {@code List} MapStruct generates an {@code ArrayList}
    4. *
    5. For {@code Map} a {@code HashMap}
    6. *
    7. For arrays an empty array
    8. 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 b5c116ffd3..43f8fc38a0 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -204,7 +204,8 @@ The mechanism is also present on iterable mapping and map mapping. `@IterableMap [[mapping-result-for-null-arguments]] === Controlling mapping result for 'null' arguments -MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned. +MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. +By default `null` will be returned, or `Optional.empty()` if the return type is `Optional`. However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MapperConfig`, the mapping result can be altered to return empty *default* values. This means for: @@ -243,7 +244,7 @@ How the value of the `NullValueMappingStrategy` is applied is the same as in <> for detailed examples and behavior with Optional types. +==== + [[checking-source-property-for-null-arguments]] === Controlling checking result for 'null' properties in bean mapping diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 81980a35ac..96c76fc20d 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -660,6 +660,421 @@ public class PersonMapperImpl implements PersonMapper { ---- ==== +[[mapping-with-optional]] +=== Using Optional + +MapStruct provides comprehensive support for Java's `Optional` type, allowing you to use `Optional` as both source and target types in your mappings. +MapStruct handles all common `Optional` mapping scenarios, including conversions, nested properties, update mappings, and integration with builders and constructors. + +[[optional-basic-behavior]] +==== Basic Optional Behavior + +When using `Optional` as a source type, MapStruct performs presence checks using `Optional.isEmpty()` / `!Optional.isPresent()` instead of `null` checks. +When using `Optional` as a target type, MapStruct returns `Optional.empty()` instead of `null` when the source is absent. + +[[optional-mapping-scenarios]] +==== Optional Mapping Scenarios + +MapStruct supports three primary mapping scenarios with `Optional`: + +1. `Optional` to `Optional` - Both source and target are wrapped in `Optional` +2. `Optional` to Non-`Optional` - Source is `Optional`, target is a regular type +3. Non-`Optional` to `Optional` - Source is a regular type, target is `Optional` + +.Example mapper showing all Optional scenarios +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductMapper { + + Optional map(Optional product); + + ProductDto unwrap(Optional product); + + Optional wrap(Product product); +} +---- +==== + +.Generated code for `Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional product) { + if ( product.isEmpty() ) { + return Optional.empty(); + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +.Generated code for `Optional` to Non-`Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public ProductDto unwrap(Optional product) { + if ( product.isEmpty() ) { + return null; + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return productDto; +} +---- +==== + +.Generated code for Non-`Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional wrap(Product product) { + if ( product == null ) { + return Optional.empty(); + } + + ProductDto productDto = new ProductDto(); + + productDto.setName( product.getName() ); + productDto.setPrice( product.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +[[optional-properties]] +==== Mapping Optional Properties + +MapStruct handles `Optional` properties within your beans seamlessly. +When mapping `Optional` properties, MapStruct generates appropriate presence checks before accessing the values. + +.Example with `Optional` properties +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Car { + private Optional make; + private Optional numberOfSeats; + private Person driver; + + // getters omitted for brevity +} + +public class CarDto { + private String manufacturer; + private int seatCount; + private PersonDto driver; + + // getters and setters omitted for brevity +} + +@Mapper +public interface CarMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "seatCount", source = "numberOfSeats") + CarDto carToCarDto(Car car); +} +---- +==== + +.Code generated by MapStruct for `Optional` properties +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + if ( car.getMake().isPresent() ) { + carDto.setManufacturer( car.getMake().get() ); + } + if ( car.getNumberOfSeats().isPresent() ) { + carDto.setSeatCount( car.getNumberOfSeats().get() ); + } + carDto.setDriver( personToPersonDto( car.getDriver() ) ); + + return carDto; + } + + protected PersonDto personToPersonDto(Person person) { + //... + } +} +---- +==== + +[[optional-update-mappings]] +==== Update Mappings with Optional + +`Optional` works seamlessly with update mappings using `@MappingTarget`. +When the source `Optional` is present, the target object is updated with the unwrapped value. +When the source `Optional` is empty, the behavior depends on the null value property mapping strategy. + +.Update mapping with an `Optional` source +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductUpdateMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} + +public class ProductUpdateDto { + private Optional name; + private Optional price; + + // getters omitted +} + +public class Product { + private String name; + private BigDecimal price; + + // getters and setters omitted +} +---- +==== + +.Generated update method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public void updateProduct(ProductUpdateDto dto, Product product) { + if ( dto == null ) { + return; + } + + if ( dto.getName().isPresent() ) { + product.setName( dto.getName().get() ); + } + if ( dto.getPrice().isPresent() ) { + product.setPrice( dto.getPrice().get() ); + } +} +---- +==== + +[[optional-null-value-strategies]] +==== Null Value Mapping Strategies + +When working with `Optional` types, MapStruct's null value mapping strategies behave as follows: + +* For `Optional` return types: `NullValueMappingStrategy.RETURN_DEFAULT` will return `Optional.empty()` instead of `null` +* For `Optional` properties: When a source property `Optional` is empty, `NullValuePropertyMappingStrategy` determines whether to skip the property, set it to null/empty, or use a default value +* `IGNORE` strategy: When a source `Optional` is empty, the target property is not updated +* `SET_TO_NULL` strategy: When a source `Optional` is empty, the target property is set to `null` (for non-`Optional` targets) or `Optional.empty()` (for `Optional` targets) +* `SET_TO_DEFAULT` strategy: When a source `Optional` is empty, the target property is set to its default value + +.Using null value strategies with `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ProductMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} +---- +==== + +With the `IGNORE` strategy, when Optional properties are empty, they won't overwrite existing target values. + +[[optional-builders-constructors]] +==== Optional with Builders and Constructors + +MapStruct's Optional support works seamlessly with both builder patterns and constructor-based mappings. + +.Optional with builders +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface PersonMapper { + Optional map(Optional person); +} + +public class PersonDto { + private final String name; + private final Integer age; + + private PersonDto(Builder builder) { + this.name = builder.name; + this.age = builder.age; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private Integer age; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder age(Integer age) { + this.age = age; + return this; + } + + public PersonDto build() { + return new PersonDto(this); + } + } + + // getters omitted +} +---- +==== + +.Generated code using builder +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional person) { + if ( person.isEmpty() ) { + return Optional.empty(); + } + + Person personValue = person.get(); + + PersonDto.Builder builder = PersonDto.builder(); + + builder.name( personValue.getName() ); + builder.age( personValue.getAge() ); + + return Optional.of( builder.build() ); +} +---- +==== + +.Optional with constructors +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class PersonDto { + private final String name; + private final Integer age; + + public PersonDto(String name, Integer age) { + this.name = name; + this.age = age; + } + + // getters omitted +} +---- +==== + +When the target type uses constructors and has `Optional` properties, MapStruct will properly handle the `Optional` parameters. + +[[optional-complex-mappings]] +==== Complex Optional Mappings + +MapStruct can handle complex scenarios where `Optional` is combined with nested object mappings, collections, and custom mapping methods. + +.Complex nested Optional mappings +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface OrderMapper { + + OrderDto map(Order order); + + // MapStruct will automatically use this for nested mappings + CustomerDto map(Customer customer); +} + +public class Order { + private Optional customer; + private Optional
      shippingAddress; + + // getters omitted +} + +public class OrderDto { + private Optional customer; + private AddressDto shippingAddress; + + // getters and setters omitted +} +---- +==== + +When mapping nested objects that are wrapped in Optional, MapStruct will: + +1. Check if the source `Optional` is present +2. Extract the value from the `Optional` +3. Map the extracted value to the target type +4. Wrap the result in an `Optional` if the target is also Optional + +[[optional-best-practices]] +==== Best Practices and Limitations + +[NOTE] +==== +While MapStruct fully supports `Optional` for target properties (public fields or setter parameters), the Java community generally recommends using `Optional` only for return types (source properties / getters). +Consider your team's coding standards when deciding whether to use `Optional` for target properties. +==== + +[WARNING] +==== +Avoid using `null` when returning `Optional` values to prevent `NullPointerException`. +MapStruct assumes that if you're using `Optional`, the `Optional` itself is not null. +==== + [[mapping-map-to-bean]] === Mapping Map to Bean 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 ad49fe2960..9da7ff75c1 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -133,6 +133,14 @@ public interface CarMapper { * Between `java.util.Locale` and `String`. ** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information. +[NOTE] +==== +All the above conversions also work when the source or target type is wrapped in `java.util.Optional`, `java.util.OptionalDouble`, `java.util.OptionalLong` or `java.util.OptionalInt`. +For example, `Optional` can be converted to `String`, `int` can be converted to `Optional`, +or `Optional` can be converted to `Integer`. +The same conversion rules apply, with MapStruct automatically handling the wrapping and unwrapping of `Optional` values. +==== + [[mapping-object-references]] === Mapping object references diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 5090c903f3..076cd2f42e 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -333,6 +333,41 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro } public ConversionProvider getConversion(Type sourceType, Type targetType) { + if ( sourceType.isOptionalType() ) { + if ( targetType.isOptionalType() ) { + // We cannot convert optional to optional + return null; + } + Type sourceBaseType = sourceType.getOptionalBaseType(); + if ( sourceBaseType.equals( targetType ) ) { + // Optional -> Type + return TypeToOptionalConversion.OPTIONAL_TO_TYPE_CONVERSION; + } + + ConversionProvider conversionProvider = getInternalConversion( sourceBaseType, targetType ); + if ( conversionProvider != null ) { + return inverse( new OptionalWrapperConversionProvider( conversionProvider ) ); + } + + } + else if ( targetType.isOptionalType() ) { + // Type -> Optional + Type targetBaseType = targetType.getOptionalBaseType(); + if ( targetBaseType.equals( sourceType )) { + return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; + } + ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); + if ( conversionProvider != null ) { + return new OptionalWrapperConversionProvider( conversionProvider ); + } + return null; + + } + + return getInternalConversion( sourceType, targetType ); + } + + private ConversionProvider getInternalConversion(Type sourceType, Type targetType) { if ( sourceType.isEnumType() && ( targetType.equals( stringType ) || targetType.getBoxedEquivalent().equals( integerType ) ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java new file mode 100644 index 0000000000..2b3e1bd960 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.List; + +import org.mapstruct.ap.internal.model.FromOptionalTypeConversion; +import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.ToOptionalTypeConversion; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +/** + * @author Filip Hrisafov + */ +public class OptionalWrapperConversionProvider implements ConversionProvider { + + private final ConversionProvider conversionProvider; + + public OptionalWrapperConversionProvider(ConversionProvider conversionProvider) { + this.conversionProvider = conversionProvider; + } + + @Override + public Assignment to(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new ToOptionalTypeConversion( conversionContext.getTargetType(), assignment ); + } + + @Override + public Assignment from(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new FromOptionalTypeConversion( conversionContext.getSourceType(), assignment ); + } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperMethods( conversionContext ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperFields( conversionContext ); + } + + private static class OptionalConversionContext implements ConversionContext { + + private final ConversionContext delegate; + + private OptionalConversionContext(ConversionContext delegate) { + this.delegate = delegate; + } + + @Override + public Type getTargetType() { + return resolveType( delegate.getTargetType() ); + } + + @Override + public Type getSourceType() { + return resolveType( delegate.getSourceType() ); + } + + private Type resolveType(Type type) { + if ( type.isOptionalType() ) { + return type.getOptionalBaseType(); + } + return type; + } + + @Override + public String getDateFormat() { + return delegate.getDateFormat(); + } + + @Override + public String getNumberFormat() { + return delegate.getNumberFormat(); + } + + @Override + public String getLocale() { + return delegate.getLocale(); + } + + @Override + public TypeFactory getTypeFactory() { + return delegate.getTypeFactory(); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java new file mode 100644 index 0000000000..ad488d1a8f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.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.internal.conversion; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; + +/** + * @author Filip Hrisafov + */ +public class TypeToOptionalConversion extends SimpleConversion { + + static final TypeToOptionalConversion TYPE_TO_OPTIONAL_CONVERSION = new TypeToOptionalConversion(); + static final ConversionProvider OPTIONAL_TO_TYPE_CONVERSION = inverse( TYPE_TO_OPTIONAL_CONVERSION ); + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().asRawType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTargetType().asRawType() ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + StringBuilder sb = new StringBuilder(".get"); + Type optionalBaseType = conversionContext.getSourceType().getOptionalBaseType(); + if ( optionalBaseType.isPrimitive() ) { + sb.append( "As" ).append( Strings.capitalize( optionalBaseType.getName() ) ); + } + return sb.append( "()" ).toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 086773dbe9..c6fc7753f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -99,9 +100,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final BuilderType returnTypeBuilder; private final MethodReference finalizerMethod; private final String finalizedResultName; + private final String optionalResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithOptionalReturnType; private final Type subclassExhaustiveException; + private final Map sourceParametersReassignments; private final MappingReferences mappingReferences; @@ -119,6 +123,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final Map sourceParametersReassignments = new HashMap<>(); private MappingReferences mappingReferences; private List targetThisReferences; @@ -176,10 +181,18 @@ public BeanMappingMethod build() { if ( selectionParameters != null && selectionParameters.getResultType() != null ) { // This is a user-defined return type, which means we need to do some extra checks for it userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeImpl = userDefinedReturnType; returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); } else { - returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + Type methodReturnType = method.getReturnType(); + if ( methodReturnType.isOptionalType() ) { + returnTypeImpl = methodReturnType.getOptionalBaseType(); + } + else { + returnTypeImpl = methodReturnType; + } + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( returnTypeImpl, builder ); } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set @@ -195,7 +208,6 @@ public BeanMappingMethod build() { } } else if ( userDefinedReturnType != null ) { - returnTypeImpl = userDefinedReturnType; initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; @@ -205,7 +217,6 @@ else if ( userDefinedReturnType != null ) { } } else if ( !method.isUpdateMethod() ) { - returnTypeImpl = method.getReturnType(); initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) @@ -266,12 +277,25 @@ else if ( !method.isUpdateMethod() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || - sourceParameter.getType().isMapType() ) { + Type sourceParameterType = sourceParameter.getType(); + if ( sourceParameterType.isOptionalType() ) { + String sourceParameterValueName = Strings.getSafeVariableName( + sourceParameter.getName() + "Value", + existingVariableNames + ); + existingVariableNames.add( sourceParameterValueName ); + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParametersReassignments.put( + sourceParameter.getName(), + new Parameter( sourceParameterValueName, sourceParameter.getName(), sourceParameterType ) + ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() || + sourceParameterType.isMapType() ) { continue; } - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); + Map readAccessors = sourceParameterType.getPropertyReadAccessors(); unprocessedSourceProperties.putAll( readAccessors ); } @@ -362,12 +386,24 @@ else if ( !method.isUpdateMethod() ) { ctx, existingVariableNames ); + + Supplier> additionalAfterMappingParameterBindingsProvider = () -> + sourceParametersReassignments.values() + .stream() + .map( parameter -> method.getSourceParameters().size() == 1 ? + ParameterBinding.fromParameter( parameter ) : + ParameterBinding.fromTypeAndName( + parameter.getType(), + parameter.getOriginalName() + ".get()" + ) ) + .collect( Collectors.toList() ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, resultTypeToMap, selectionParameters, ctx, - existingVariableNames + existingVariableNames, + Collections::emptyList ); if ( method instanceof ForgedMethod ) { @@ -409,12 +445,15 @@ else if ( !method.isUpdateMethod() ) { if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { finalizeMethod = getFinalizerMethod(); - Type actualReturnType = method.getReturnType(); + Type finalizerReturnType = method.getReturnType(); + if ( finalizerReturnType.isOptionalType() ) { + finalizerReturnType = finalizerReturnType.getOptionalBaseType(); + } beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget( LifecycleMethodResolver.beforeMappingMethods( method, - actualReturnType, + finalizerReturnType, selectionParameters, ctx, existingVariableNames @@ -424,14 +463,33 @@ else if ( !method.isUpdateMethod() ) { afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( method, - actualReturnType, + finalizerReturnType, + selectionParameters, + ctx, + existingVariableNames, + additionalAfterMappingParameterBindingsProvider + ) ); + + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + } + + List afterMappingReferencesWithOptionalReturnType = new ArrayList<>(); + if ( method.getReturnType().isOptionalType() ) { + afterMappingReferencesWithOptionalReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + method.getReturnType(), selectionParameters, ctx, - existingVariableNames + existingVariableNames, + additionalAfterMappingParameterBindingsProvider ) ); - keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType ); - keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType ); + keepMappingReferencesUsingTarget( + afterMappingReferencesWithOptionalReturnType, + method.getReturnType() + ); + } Map presenceChecksByParameter = new LinkedHashMap<>(); @@ -461,11 +519,13 @@ else if ( !method.isUpdateMethod() ) { afterMappingMethods, beforeMappingReferencesWithFinalizedReturnType, afterMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithOptionalReturnType, finalizeMethod, mappingReferences, subclasses, presenceChecksByParameter, - subclassExhaustiveExceptionType + subclassExhaustiveExceptionType, + sourceParametersReassignments ); } @@ -609,8 +669,17 @@ private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { * builder is not assignable to the return type (so without building). */ private boolean isBuilderRequired() { - return returnTypeBuilder != null - && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() ); + if ( returnTypeBuilder == null ) { + return false; + } + if ( method.isUpdateMethod() ) { + // when @MappingTarget annotated parameter is the same type as the return type. + return !method.getResultType().isAssignableTo( method.getReturnType() ); + } + else { + // For non-update methods a builder is required when returnTypeBuilder is set + return true; + } } private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) { @@ -1510,6 +1579,7 @@ else if ( mapping.getJavaExpression() != null ) { if ( sourceRef != null ) { // sourceRef == null is not considered an error here if ( sourceRef.isValid() ) { + Parameter sourceParameter = sourceRef.getParameter(); // targetProperty == null can occur: we arrived here because we want as many errors // as possible before we stop analysing @@ -1518,7 +1588,8 @@ else if ( mapping.getJavaExpression() != null ) { .sourceMethod( method ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourcePropertyName( mapping.getSourceName() ) - .sourceReference( sourceRef ) + .sourceReference( sourceRef.withParameter( + sourceParametersReassignments.get( sourceParameter.getName() ) ) ) .selectionParameters( mapping.getSelectionParameters() ) .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) @@ -1530,7 +1601,6 @@ else if ( mapping.getJavaExpression() != null ) { .options( mapping ) .build(); handledTargets.add( targetPropertyName ); - Parameter sourceParameter = sourceRef.getParameter(); unprocessedSourceParameters.remove( sourceParameter ); // If the source parameter was directly mapped if ( sourceRef.getPropertyEntries().isEmpty() ) { @@ -1743,20 +1813,26 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri SourceReference sourceRef = null; - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { + Type sourceParameterType = sourceParameter.getType(); + Parameter sourceParameterToUse = sourceParameter; + if ( sourceParameterType.isOptionalType() ) { + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParameterToUse = sourceParametersReassignments.get( sourceParameter.getName() ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() ) { return sourceRef; } - ReadAccessor sourceReadAccessor = sourceParameter.getType() + ReadAccessor sourceReadAccessor = sourceParameterType .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = - sourceParameter.getType().getPresenceChecker( targetPropertyName ); + sourceParameterType.getPresenceChecker( targetPropertyName ); - DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); + DeclaredType declaredSourceType = (DeclaredType) sourceParameterType.getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); - sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) + sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameterToUse ) .type( returnType ) .readAccessor( sourceReadAccessor ) .presenceChecker( sourcePresenceChecker ) @@ -2033,11 +2109,14 @@ private BeanMappingMethod(Method method, List afterMappingReferences, List beforeMappingReferencesWithFinalizedReturnType, List afterMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithOptionalReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, Map presenceChecksByParameter, - Type subclassExhaustiveException) { + Type subclassExhaustiveException, + Map sourceParametersReassignments + ) { super( method, annotations, @@ -2057,14 +2136,21 @@ private BeanMappingMethod(Method method, this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); existingVariableNames.add( this.finalizedResultName ); + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "ResultOptional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } else { this.finalizedResultName = null; + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "Optional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } this.mappingReferences = mappingReferences; this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType; this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithOptionalReturnType = afterMappingReferencesWithOptionalReturnType; // initialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. @@ -2099,6 +2185,7 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { } this.returnTypeToConstruct = returnTypeToConstruct; this.subclassMappings = subclassMappings; + this.sourceParametersReassignments = sourceParametersReassignments; } public Type getSubclassExhaustiveException() { @@ -2121,6 +2208,18 @@ public String getFinalizedResultName() { return finalizedResultName; } + public Type getFinalizedReturnType() { + Type returnType = getReturnType(); + if ( returnType.isOptionalType() ) { + return returnType.getOptionalBaseType(); + } + return returnType; + } + + public String getOptionalResultName() { + return optionalResultName; + } + public List getBeforeMappingReferencesWithFinalizedReturnType() { return beforeMappingReferencesWithFinalizedReturnType; } @@ -2129,6 +2228,10 @@ public List getAfterMappingReferencesWithFinal return afterMappingReferencesWithFinalizedReturnType; } + public List getAfterMappingReferencesWithOptionalReturnType() { + return afterMappingReferencesWithOptionalReturnType; + } + public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); @@ -2188,6 +2291,10 @@ public Set getImportTypes() { types.addAll( reference.getImportTypes() ); } + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithOptionalReturnType ) { + types.addAll( reference.getImportTypes() ); + } + return types; } @@ -2215,6 +2322,10 @@ public List getSourceParametersNotNeedingPresenceCheck() { .collect( Collectors.toList() ); } + public Parameter getSourceParameterReassignment(Parameter parameter) { + return sourceParametersReassignments.get( parameter.getName() ); + } + private boolean needsPresenceCheck(Parameter parameter) { if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) { return false; @@ -2277,5 +2388,4 @@ public boolean equals(Object obj) { return true; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java new file mode 100644 index 0000000000..f8c596a368 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.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.internal.model; + +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class FromOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type optionalType; + + public FromOptionalTypeConversion(Type optionalType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.optionalType = optionalType; + } + + @Override + public Set getImportTypes() { + return conversionAssignment.getImportTypes(); + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + this.conversionAssignment.setAssignment( new OptionalGetWrapper( assignment, optionalType ) ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return conversionAssignment.toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index cfe4f9f8b7..ada9dac470 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -6,11 +6,14 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; @@ -63,13 +66,16 @@ public static List afterMappingMethods(Method Type alternativeTarget, SelectionParameters selectionParameters, MappingBuilderContext ctx, - Set existingVariableNames) { + Set existingVariableNames, + Supplier> parameterBindingsProvider) { return collectLifecycleCallbackMethods( method, alternativeTarget, selectionParameters, filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), ctx, - existingVariableNames ); + existingVariableNames, + parameterBindingsProvider + ); } /** @@ -131,13 +137,34 @@ private static List getAllAvailableMethods(Method method, List collectLifecycleCallbackMethods( Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, MappingBuilderContext ctx, Set existingVariableNames) { + return collectLifecycleCallbackMethods( + method, + targetType, + selectionParameters, + callbackMethods, + ctx, + existingVariableNames, + Collections::emptyList + ); + } + + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames, + Supplier> parameterBindingsProvider) { MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, - SelectionContext.forLifecycleMethods( method, targetType, selectionParameters, ctx.getTypeFactory() ) + SelectionContext.forLifecycleMethods( + method, + targetType, + selectionParameters, + ctx.getTypeFactory(), + parameterBindingsProvider + ) ); return toLifecycleCallbackMethodRefs( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index 95c229c6e6..1ebf99ffeb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -30,6 +30,7 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Services; import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.spi.EnumMappingStrategy; import org.mapstruct.ap.spi.EnumTransformationStrategy; import org.mapstruct.ap.spi.MappingExclusionProvider; @@ -109,6 +110,7 @@ Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory descrip private final ElementUtils elementUtils; private final TypeUtils typeUtils; private final FormattingMessager messager; + private final VersionInformation versionInformation; private final AccessorNamingUtils accessorNaming; private final EnumMappingStrategy enumMappingStrategy; private final Map enumTransformationStrategies; @@ -126,6 +128,7 @@ public MappingBuilderContext(TypeFactory typeFactory, ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, + VersionInformation versionInformation, AccessorNamingUtils accessorNaming, EnumMappingStrategy enumMappingStrategy, Map enumTransformationStrategies, @@ -138,6 +141,7 @@ public MappingBuilderContext(TypeFactory typeFactory, this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.versionInformation = versionInformation; this.accessorNaming = accessorNaming; this.enumMappingStrategy = enumMappingStrategy; this.enumTransformationStrategies = enumTransformationStrategies; @@ -190,6 +194,10 @@ public FormattingMessager getMessager() { return messager; } + public VersionInformation getVersionInformation() { + return versionInformation; + } + public AccessorNamingUtils getAccessorNaming() { return accessorNaming; } @@ -264,6 +272,9 @@ public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType * @return {@code true} if the type is not excluded from the {@link MappingExclusionProvider} */ private boolean canGenerateAutoSubMappingFor(Type type) { + if ( "java.util.Optional".equals( type.getFullyQualifiedName() ) ) { + return !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getOptionalBaseType().getTypeElement() ); + } return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java index 599ddf1d35..0d04f5b6d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java @@ -97,7 +97,11 @@ else if ( getResultType().isArrayType() ) { return name; } else { - String name = getSafeVariableName( getResultType().getName(), existingVarNames ); + Type resultType = getResultType(); + if ( resultType.isOptionalType() ) { + resultType = resultType.getOptionalBaseType(); + } + String name = getSafeVariableName( resultType.getName(), existingVarNames ); existingVarNames.add( name ); return name; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 25c9f8fc03..ed841ba5bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -17,6 +17,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -71,21 +72,58 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); + Type previousPropertyType = sourceParameter.getType(); for ( int i = 0; i < propertyEntries.size(); i++ ) { PropertyEntry propertyEntry = propertyEntries.get( i ); - PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); - - if ( i > 0 ) { - // If this is not the first property entry, - // then we might need to combine the presence check with a null check of the previous property - if ( presenceCheck != null ) { - presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( - new NullPresenceCheck( previousPropertyName, true ), - presenceCheck - ) ); + PresenceCheck presenceCheck; + + if ( previousPropertyType.isOptionalType() ) { + String optionalValueSafeName = Strings.getSafeVariableName( + previousPropertyName + "Value", + existingVariableNames + ); + existingVariableNames.add( optionalValueSafeName ); + + presenceCheck = getPresenceCheck( propertyEntry, optionalValueSafeName ); + + String optionalValueSource = previousPropertyName + ".get()"; + boolean doesNotNeedFollowUpProperty = false; + if ( i == propertyEntries.size() - 1 ) { + // If this is the last property, and we do not have a presence check, + // then we do not need to assign the optional value + // e.g., we need to generate .get().getXxx(); + doesNotNeedFollowUpProperty = presenceCheck == null; + if ( doesNotNeedFollowUpProperty ) { + optionalValueSource += "." + propertyEntry.getReadAccessor().getReadValueSource(); + } } - else { - presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + Type optionalBaseType = previousPropertyType.getOptionalBaseType(); + safePropertyEntries.add( new SafePropertyEntry( + optionalBaseType, + optionalValueSafeName, + optionalValueSource, + new OptionalPresenceCheck( previousPropertyName, ctx.getVersionInformation(), true ) + ) ); + if ( doesNotNeedFollowUpProperty ) { + break; + } + previousPropertyName = optionalValueSafeName; + + } + else { + presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } } } @@ -101,6 +139,7 @@ public NestedPropertyMappingMethod build() { thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); previousPropertyName = safeName; + previousPropertyType = propertyEntry.getType(); } method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 049e4caf37..784342b8d7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -87,7 +88,10 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { - if ( !sourceParameter.getType().isPrimitive() ) { + if ( sourceParameter.getType().isOptionalType() ) { + return new OptionalPresenceCheck( sourceParameter.getName(), ctx.getVersionInformation() ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { return new NullPresenceCheck( sourceParameter.getName() ); } return null; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 08e8adbd1b..622ba9cef0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -38,6 +38,7 @@ import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.model.source.DelegatingOptions; import org.mapstruct.ap.internal.model.source.MappingControl; @@ -290,7 +291,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get return new PropertyMapping( sourcePropertyName, targetPropertyName, - rightHandSide.getSourceParameterName(), + sourceReference.getParameter().getOriginalName(), targetWriteAccessor.getSimpleName(), targetReadAccessor, targetType, @@ -703,15 +704,26 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc String variableName = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); + Type variableType = propertyEntry.getType(); for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { - presenceChecks.add( new NullPresenceCheck( variableName ) ); + if ( variableType.isOptionalType() ) { + presenceChecks.add( new OptionalPresenceCheck( + variableName, + ctx.getVersionInformation() + ) ); + variableName = variableName + ".get()"; + } + else { + presenceChecks.add( new NullPresenceCheck( variableName ) ); + } presenceChecks.add( new SuffixPresenceCheck( variableName, entry.getPresenceChecker().getPresenceCheckSuffix() ) ); variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()"; + variableType = entry.getType(); } else { break; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java new file mode 100644 index 0000000000..5925540e10 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -0,0 +1,119 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class ToOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type targetType; + + public ToOptionalTypeConversion(Type targetType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.targetType = targetType; + } + + public Type getTargetType() { + return targetType; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( conversionAssignment.getImportTypes() ); + importTypes.add( targetType ); + return importTypes; + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + conversionAssignment.setAssignment( assignment ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return targetType.getName() + ".of( " + conversionAssignment.toString() + " )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java new file mode 100644 index 0000000000..9064640bb4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +/** + * @author Filip Hrisafov + */ +public class OptionalGetWrapper extends AssignmentWrapper { + + private final Type optionalType; + + public OptionalGetWrapper(Assignment decoratedAssignment, Type optionalType) { + super( decoratedAssignment, false ); + this.optionalType = optionalType; + } + + public Type getOptionalType() { + return optionalType; + } + + @Override + public String toString() { + if ( optionalType.getFullyQualifiedName().equals( "java.util.Optional" ) ) { + return getAssignment() + ".get()"; + } + return getAssignment() + ".getAs" + Strings.capitalize( optionalType.getOptionalBaseType().getName() ) + "()"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 73ec84b6b7..c37b12e311 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -325,6 +325,9 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; Type noBoundsType = newType.withoutBounds(); + if ( noBoundsType.isOptionalType() ) { + noBoundsType = noBoundsType.getOptionalBaseType(); + } ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); if ( readAccessor != null ) { PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); @@ -465,4 +468,11 @@ public List push(TypeFactory typeFactory, FormattingMessager me return result; } + public SourceReference withParameter(Parameter parameter) { + if ( parameter == null || getParameter() == parameter ) { + return this; + } + return new SourceReference( parameter, getPropertyEntries(), isValid() ); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index 96d3d6fe78..d3a29e30a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -21,6 +21,13 @@ public interface ConversionContext { */ Type getTargetType(); + /** + * Returns the source type of this conversion. + * + * @return The source type of this conversion. + */ + Type getSourceType(); + /** * Returns the date format if this conversion or built-in method is from String to a date type (e.g. {@link Date}) * or vice versa. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index 159f1663e2..4e5eed47a8 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -61,6 +61,11 @@ public Type getTargetType() { return targetType; } + @Override + public Type getSourceType() { + return sourceType; + } + @Override public String getNumberFormat() { return numberFormat; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index aaab7f46ca..c5091869a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -51,12 +51,13 @@ private Parameter(Element element, Type type, boolean varArgs) { this.varArgs = varArgs; } - private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + private Parameter(String name, String originalName, Type type, boolean mappingTarget, boolean targetType, + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; - this.originalName = name; + this.originalName = originalName; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; @@ -67,7 +68,11 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false, false ); + this( name, name, type ); + } + + public Parameter(String name, String originalName, Type type) { + this( name, originalName, type, false, false, false, false, false, false ); } public Element getElement() { @@ -141,6 +146,20 @@ public boolean isSourceParameter() { !isTargetPropertyName(); } + public Parameter withName(String name) { + return new Parameter( + name, + this.name, + type, + mappingTarget, + targetType, + mappingContext, + sourcePropertyName, + targetPropertyName, + varArgs + ); + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -176,6 +195,7 @@ public static Parameter forElementAndType(VariableElement element, Type paramete public static Parameter forForgedMappingTarget(Type parameterType) { return new Parameter( + "mappingTarget", "mappingTarget", parameterType, true, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8fb10090c2..29bb3cd906 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -397,11 +397,32 @@ private boolean isType(Class type) { return type.getName().equals( getFullyQualifiedName() ); } - private boolean isOptionalType() { + public boolean isOptionalType() { return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || isType( OptionalLong.class ); } + public Type getOptionalBaseType() { + if ( isType( Optional.class ) ) { + return getTypeParameters().get( 0 ); + } + + if ( isType( OptionalInt.class ) ) { + return typeFactory.getType( int.class ); + } + + if ( isType( OptionalDouble.class ) ) { + return typeFactory.getType( double.class ); + } + + if ( isType( OptionalLong.class ) ) { + return typeFactory.getType( long.class ); + } + + throw new IllegalStateException( "getOptionalBaseType should only be called for Optional types." ); + + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java new file mode 100644 index 0000000000..d727c30f24 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java @@ -0,0 +1,77 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.version.VersionInformation; + +/** + * Presence checker for {@link java.util.Optional} types. + * + * @author Ken Wang + */ +public class OptionalPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final VersionInformation versionInformation; + private final boolean negate; + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation) { + this( sourceReference, versionInformation, false ); + } + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation, boolean negate) { + this.sourceReference = sourceReference; + this.versionInformation = versionInformation; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public VersionInformation getVersionInformation() { + return versionInformation; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new OptionalPresenceCheck( sourceReference, versionInformation, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + OptionalPresenceCheck that = (OptionalPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index ba32b12b46..d9c9ce41fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -182,15 +182,6 @@ default ConditionMethodOptions getConditionOptions() { return ConditionMethodOptions.empty(); } - /** - * - * @return true when @MappingTarget annotated parameter is the same type as the return type. The method has - * to be an update method in order for this to be true. - */ - default boolean isMappingTargetAssignableToReturnType() { - return isUpdateMethod() && getResultType().isAssignableTo( getReturnType() ); - } - /** * @return the first source type, intended for mapping methods from single source to target */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 84bd04d7db..79351ae868 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -106,8 +106,9 @@ public static SelectionContext forMappingMethods(Method mappingMethod, Type sour } public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType, - SelectionParameters selectionParameters, - TypeFactory typeFactory) { + SelectionParameters selectionParameters, + TypeFactory typeFactory, + Supplier> additionalParameterBindingsProvider) { SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters ); return new SelectionContext( null, @@ -115,12 +116,16 @@ public static SelectionContext forLifecycleMethods(Method mappingMethod, Type ta mappingMethod, targetType, mappingMethod.getResultType(), - () -> getAvailableParameterBindingsFromMethod( - mappingMethod, - targetType, - criteria.getSourceRHS(), - typeFactory - ) + () -> { + List parameterBindings = getAvailableParameterBindingsFromMethod( + mappingMethod, + targetType, + criteria.getSourceRHS(), + typeFactory + ); + parameterBindings.addAll( additionalParameterBindingsProvider.get() ); + return parameterBindings; + } ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index 055bfe6095..0457a66da4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -41,6 +41,7 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast11; private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -54,6 +55,7 @@ public class DefaultVersionInformation implements VersionInformation { this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast11 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 4; this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @@ -82,6 +84,11 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast11() { + return sourceVersionAtLeast11; + } + @Override public boolean isSourceVersionAtLeast19() { return sourceVersionAtLeast19; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index ba24e868aa..03e8bf977f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -119,6 +119,7 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L elementUtils, typeUtils, messager, + versionInformation, accessorNaming, context.getEnumMappingStrategy(), context.getEnumTransformationStrategies(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index ba903b6048..2a36f6b890 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -464,7 +464,7 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy Assignment conversion = conversionProvider.to( ctx ); if ( conversion != null ) { - return new ConversionAssignment( sourceType, targetType, conversionProvider.to( ctx ) ); + return new ConversionAssignment( sourceType, targetType, conversion ); } return null; } @@ -673,7 +673,16 @@ Assignment getAssignment() { void reportMessageWhenNarrowing(FormattingMessager messager, ResolvingAttempt attempt) { - if ( NativeTypes.isNarrowing( sourceType.getFullyQualifiedName(), targetType.getFullyQualifiedName() ) ) { + Type source = sourceType; + if ( sourceType.isOptionalType() ) { + source = sourceType.getOptionalBaseType(); + } + + Type target = targetType; + if ( targetType.isOptionalType() ) { + target = targetType.getOptionalBaseType(); + } + if ( NativeTypes.isNarrowing( source.getFullyQualifiedName(), target.getFullyQualifiedName() ) ) { ReportingPolicyGem policy = attempt.mappingMethod.getOptions().getMapper().typeConversionPolicy(); if ( policy == ReportingPolicyGem.WARN ) { report( messager, attempt, Message.CONVERSION_LOSSY_WARNING ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 5e1972fcae..411e11bf9e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -21,6 +21,8 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast11(); + boolean isSourceVersionAtLeast19(); boolean isEclipseJDTCompiler(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 059e2d77d4..4ad6ae20db 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -21,14 +21,22 @@ <#list beforeMappingReferencesWithFinalizedReturnType as callback> - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> <#if !callback_has_next> <#if !mapNullToDefault && !sourcePresenceChecks.empty> if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && ) { - return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; + <#if returnType.name == "void"> + return; + <#else> + <#if existingInstanceMapping> + <@createReturn applyOptionalAfterMapping=false>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /> + <#else> + return ${returnType.null}; + + } @@ -52,6 +60,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -71,6 +84,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -102,6 +120,11 @@ <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -117,6 +140,11 @@ <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -133,24 +161,24 @@ <#if returnType.name != "void"> - <#if finalizerMethod??> - <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> - <@includeModel object=returnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + <#if finalizerMethod??> + <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> + <@includeModel object=finalizedReturnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; - <#list afterMappingReferencesWithFinalizedReturnType as callback> - <#if callback_index = 0> + <#list afterMappingReferencesWithFinalizedReturnType as callback> + <#if callback_index = 0> - - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> - + + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> + - return ${finalizedResultName}; + <@createReturn>${finalizedResultName} + <#else> + <@createReturn>${resultName}.<@includeModel object=finalizerMethod /> + <#else> - return ${resultName}.<@includeModel object=finalizerMethod />; + <@createReturn>${resultName} - <#else> - return ${resultName}; - <#if hasSubclassMappings()> @@ -164,4 +192,27 @@ <#if exceptionType_has_next>, <#t> + +<#macro createReturn applyOptionalAfterMapping=true> +<#-- <@compress single_line=true>--> + <#if returnType.optionalType> + <#if (afterMappingReferencesWithOptionalReturnType?size > 0)> + <@includeModel object=returnType /> ${optionalResultName} = <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#list afterMappingReferencesWithOptionalReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=optionalResultName targetType=returnType/> + + + return ${optionalResultName}; + <#else> + return <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#else> + return <#nested/>; + +<#-- --> + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl new file mode 100644 index 0000000000..50ab97e5b4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.FromOptionalTypeConversion" --> +<@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl new file mode 100644 index 0000000000..48e20e7017 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ToOptionalTypeConversion" --> +<@compress single_line=true> +<@includeModel object=targetType.asRawType() />.of( <@_assignment/> ) +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl new file mode 100644 index 0000000000..7bbc089d7c --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper" --> +<@compress single_line=true> +<#if optionalType.optionalBaseType.isPrimitive()> +${assignment}.getAs${optionalType.optionalBaseType.name?cap_first}() +<#else> +${assignment}.get() + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index ea8eed2438..d8ed4b53f6 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -6,6 +6,8 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.UpdateWrapper" --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#import '../macro/CommonMacros.ftl' as lib > <@lib.handleExceptions> <#if includeSourceNullCheck> @@ -16,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 278b441aa9..9c4ad26e03 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -13,6 +13,8 @@ requires: caller to implement boolean:getIncludeSourceNullCheck() --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#macro handleSourceReferenceNullCheck> <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference @@ -23,7 +25,7 @@ } <@elseDefaultAssignment/> <#elseif includeSourceNullCheck || ext.defaultValueAssignment??> - if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}<#if sourceType.optionalType>.isPresent()<#else> != null ) { <#nested> } <@elseDefaultAssignment/> @@ -43,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } @@ -156,7 +158,7 @@ Performs a default assignment with a default value. <#if factoryMethod??> <@includeModel object=factoryMethod targetType=ext.targetType/> <#else> - <@constructTargetObject/> + <@constructTargetObject targetType=ext.targetType/> <#-- @@ -164,15 +166,18 @@ Performs a default assignment with a default value. purpose: Either call the constructor of the target object directly or of the implementing type. --> -<#macro constructTargetObject><@compress single_line=true> - <#if ext.targetType.implementationType??> - new <@includeModel object=ext.targetType.implementationType/>() - <#elseif ext.targetType.arrayType> - new <@includeModel object=ext.targetType.componentType/>[0] - <#elseif ext.targetType.sensibleDefault??> - ${ext.targetType.sensibleDefault} +<#-- @ftlvariable name="targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#macro constructTargetObject targetType><@compress single_line=true> + <#if targetType.implementationType??> + new <@includeModel object=targetType.implementationType/>() + <#elseif targetType.arrayType> + new <@includeModel object=targetType.componentType/>[0] + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.optionalType> + <@includeModel object=targetType.asRawType()/>.of( <@constructTargetObject targetType=targetType.optionalBaseType/> ) <#else> - new <@includeModel object=ext.targetType/>() + new <@includeModel object=targetType/>() <#-- @@ -181,6 +186,7 @@ Performs a default assignment with a default value. purpose: assignment for source local variables. The sourceLocalVarName replaces the sourceReference in the assignmentcall. --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Assignment" --> <#macro sourceLocalVarAssignment> <#if sourceLocalVarName??> <@includeModel object=sourceType/> ${sourceLocalVarName} = ${sourceReference}; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl new file mode 100644 index 0000000000..c321ce20ec --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl @@ -0,0 +1,19 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck" --> +<@compress single_line=true> + <#if isNegate()> + <#if versionInformation.isSourceVersionAtLeast11()> + ${sourceReference}.isEmpty() + <#else> + !${sourceReference}.isPresent() + + <#else> + ${sourceReference}.isPresent() + + \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java new file mode 100644 index 0000000000..e1e557a897 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java @@ -0,0 +1,73 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion._enum; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@WithClasses({ + OptionalEnumToIntegerSource.class, + EnumToIntegerTarget.class, + OptionalEnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class OptionalEnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + OptionalEnumToIntegerSource source = new OptionalEnumToIntegerSource(); + + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { + source.setEnumValue( Optional.of( value ) ); + + EnumToIntegerTarget target = OptionalEnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( value.ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerEnum[] enumValues = EnumToIntegerEnum.values(); + int numberOfValues = enumValues.length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).contains( enumValues[target.getEnumValue()] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEmpty(); + assertThat( source.getInvalidEnumValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java new file mode 100644 index 0000000000..d07d9b879c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalEnumToIntegerMapper { + OptionalEnumToIntegerMapper INSTANCE = Mappers.getMapper( OptionalEnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(OptionalEnumToIntegerSource source); + + OptionalEnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java new file mode 100644 index 0000000000..700c3198bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion._enum; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalEnumToIntegerSource { + private Optional enumValue = Optional.empty(); + + private Optional invalidEnumValue = Optional.empty(); + + public Optional getEnumValue() { + return enumValue; + } + + public void setEnumValue(Optional enumValue) { + this.enumValue = enumValue; + } + + public Optional getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Optional invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java new file mode 100644 index 0000000000..986de6a0ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigDecimalOptionalMapper { + + BigDecimalOptionalMapper INSTANCE = Mappers.getMapper( BigDecimalOptionalMapper.class ); + + BigDecimalTarget sourceToTarget(BigDecimalOptionalSource source); + + BigDecimalOptionalSource targetToSource(BigDecimalTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java new file mode 100644 index 0000000000..bf4e53e5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java @@ -0,0 +1,139 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import java.math.BigDecimal; +import java.util.Optional; + +public class BigDecimalOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + private Optional bigInteger; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } + + public Optional getBigInteger() { + return bigInteger; + } + + public void setBigInteger(Optional bigInteger) { + this.bigInteger = bigInteger; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java new file mode 100644 index 0000000000..d39b48c7e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigIntegerOptionalMapper { + + BigIntegerOptionalMapper INSTANCE = Mappers.getMapper( BigIntegerOptionalMapper.class ); + + BigIntegerTarget sourceToTarget(BigIntegerOptionalSource source); + + BigIntegerOptionalSource targetToSource(BigIntegerTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java new file mode 100644 index 0000000000..f4bb0f03c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java @@ -0,0 +1,131 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import java.math.BigInteger; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class BigIntegerOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java index 69f87f3980..2ddddec02e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Optional; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; @@ -100,6 +101,79 @@ public void shouldApplyReverseBigIntegerConversions() { assertThat( source.getString() ).isEqualTo( new BigInteger( "13" ) ); } + @ProcessorTest + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyOptionalBigIntegerConversions() { + BigIntegerOptionalSource source = new BigIntegerOptionalSource(); + source.setB( Optional.of( new BigInteger( "1" ) ) ); + source.setBb( Optional.of( new BigInteger( "2" ) ) ); + source.setS( Optional.of( new BigInteger( "3" ) ) ); + source.setSs( Optional.of( new BigInteger( "4" ) ) ); + source.setI( Optional.of( new BigInteger( "5" ) ) ); + source.setIi( Optional.of( new BigInteger( "6" ) ) ); + source.setL( Optional.of( new BigInteger( "7" ) ) ); + source.setLl( Optional.of( new BigInteger( "8" ) ) ); + source.setF( Optional.of( new BigInteger( "9" ) ) ); + source.setFf( Optional.of( new BigInteger( "10" ) ) ); + source.setD( Optional.of( new BigInteger( "11" ) ) ); + source.setDd( Optional.of( new BigInteger( "12" ) ) ); + source.setString( Optional.of( new BigInteger( "13" ) ) ); + + BigIntegerTarget target = BigIntegerOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.0f ); + assertThat( target.getFf() ).isEqualTo( 10.0f ); + assertThat( target.getD() ).isEqualTo( 11.0d ); + assertThat( target.getDd() ).isEqualTo( 12.0d ); + assertThat( target.getString() ).isEqualTo( "13" ); + } + + @ProcessorTest + @IssueKey("21") + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyReverseOptionalBigIntegerConversions() { + BigIntegerTarget target = new BigIntegerTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13" ); + + BigIntegerOptionalSource source = BigIntegerOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigInteger( "1" ) ); + assertThat( source.getBb() ).contains( new BigInteger( "2" ) ); + assertThat( source.getS() ).contains( new BigInteger( "3" ) ); + assertThat( source.getSs() ).contains( new BigInteger( "4" ) ); + assertThat( source.getI() ).contains( new BigInteger( "5" ) ); + assertThat( source.getIi() ).contains( new BigInteger( "6" ) ); + assertThat( source.getL() ).contains( new BigInteger( "7" ) ); + assertThat( source.getLl() ).contains( new BigInteger( "8" ) ); + assertThat( source.getF() ).contains( new BigInteger( "9" ) ); + assertThat( source.getFf() ).contains( new BigInteger( "10" ) ); + assertThat( source.getD() ).contains( new BigInteger( "11" ) ); + assertThat( source.getDd() ).contains( new BigInteger( "12" ) ); + assertThat( source.getString() ).contains( new BigInteger( "13" ) ); + } + @ProcessorTest @IssueKey("21") @WithClasses({ BigDecimalSource.class, BigDecimalTarget.class, BigDecimalMapper.class }) @@ -178,6 +252,82 @@ public void shouldApplyReverseBigDecimalConversions() { assertThat( source.getBigInteger() ).isEqualTo( new BigDecimal( "14" ) ); } + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyOptionalBigDecimalConversions() { + BigDecimalOptionalSource source = new BigDecimalOptionalSource(); + source.setB( Optional.of( new BigDecimal( "1.45" ) ) ); + source.setBb( Optional.of( new BigDecimal( "2.45" ) ) ); + source.setS( Optional.of( new BigDecimal( "3.45" ) ) ); + source.setSs( Optional.of( new BigDecimal( "4.45" ) ) ); + source.setI( Optional.of( new BigDecimal( "5.45" ) ) ); + source.setIi( Optional.of( new BigDecimal( "6.45" ) ) ); + source.setL( Optional.of( new BigDecimal( "7.45" ) ) ); + source.setLl( Optional.of( new BigDecimal( "8.45" ) ) ); + source.setF( Optional.of( new BigDecimal( "9.45" ) ) ); + source.setFf( Optional.of( new BigDecimal( "10.45" ) ) ); + source.setD( Optional.of( new BigDecimal( "11.45" ) ) ); + source.setDd( Optional.of( new BigDecimal( "12.45" ) ) ); + source.setString( Optional.of( new BigDecimal( "13.45" ) ) ); + source.setBigInteger( Optional.of( new BigDecimal( "14.45" ) ) ); + + BigDecimalTarget target = BigDecimalOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.45f ); + assertThat( target.getFf() ).isEqualTo( 10.45f ); + assertThat( target.getD() ).isEqualTo( 11.45d ); + assertThat( target.getDd() ).isEqualTo( 12.45d ); + assertThat( target.getString() ).isEqualTo( "13.45" ); + assertThat( target.getBigInteger() ).isEqualTo( new BigInteger( "14" ) ); + } + + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyReverseOptionalBigDecimalConversions() { + BigDecimalTarget target = new BigDecimalTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13.45" ); + target.setBigInteger( new BigInteger( "14" ) ); + + BigDecimalOptionalSource source = BigDecimalOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigDecimal( "1" ) ); + assertThat( source.getBb() ).contains( new BigDecimal( "2" ) ); + assertThat( source.getS() ).contains( new BigDecimal( "3" ) ); + assertThat( source.getSs() ).contains( new BigDecimal( "4" ) ); + assertThat( source.getI() ).contains( new BigDecimal( "5" ) ); + assertThat( source.getIi() ).contains( new BigDecimal( "6" ) ); + assertThat( source.getL() ).contains( new BigDecimal( "7" ) ); + assertThat( source.getLl() ).contains( new BigDecimal( "8" ) ); + assertThat( source.getF() ).contains( new BigDecimal( "9.0" ) ); + assertThat( source.getFf() ).contains( new BigDecimal( "10.0" ) ); + assertThat( source.getD() ).contains( new BigDecimal( "11.0" ) ); + assertThat( source.getDd() ).contains( new BigDecimal( "12.0" ) ); + assertThat( source.getString() ).contains( new BigDecimal( "13.45" ) ); + assertThat( source.getBigInteger() ).contains( new BigDecimal( "14" ) ); + } + @ProcessorTest @IssueKey("1009") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java new file mode 100644 index 0000000000..1a9bb50b67 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java @@ -0,0 +1,466 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Optional; +import java.util.TimeZone; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for conversions to/from Java 8 date and time types wrapped in {@link Optional}. + */ +@WithClasses({ OptionalSource.class, Target.class, OptionalSourceTargetMapper.class }) +public class Java8OptionalTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalSourceTargetMapper.class ); + } + + @ProcessorTest + public void testDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + } + + @ProcessorTest + public void testLocalDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + } + + @ProcessorTest + public void testLocalDateToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + } + + @ProcessorTest + public void testLocalTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testSourceToTargetMappingForStrings() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + + // with given format + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + + // and now with default mappings + target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testStringToDateTime() { + String dateTimeAsString = "01.01.2014 00:00 UTC"; + Target target = new Target(); + target.setZonedDateTime( dateTimeAsString ); + ZonedDateTime sourceDateTime = + ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDateTime() { + String dateTimeAsString = "01.01.2014 00:00"; + Target target = new Target(); + target.setLocalDateTime( dateTimeAsString ); + LocalDateTime sourceDateTime = + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDate() { + String dateTimeAsString = "01.01.2014"; + Target target = new Target(); + target.setLocalDate( dateTimeAsString ); + LocalDate sourceDate = + LocalDate.of( 2014, 1, 1 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDate() ).contains( sourceDate ); + } + + @ProcessorTest + public void testStringToLocalTime() { + String dateTimeAsString = "00:00"; + Target target = new Target(); + target.setLocalTime( dateTimeAsString ); + LocalTime sourceTime = + LocalTime.of( 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalTime() ).contains( sourceTime ); + } + + @ProcessorTest + public void testTargetToSourceNullMapping() { + Target target = new Target(); + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).isEmpty(); + assertThat( src.getLocalDate() ).isEmpty(); + assertThat( src.getLocalDateTime() ).isEmpty(); + assertThat( src.getLocalTime() ).isEmpty(); + } + + @ProcessorTest + public void testTargetToSourceMappingForStrings() { + Target target = new Target(); + + target.setZonedDateTime( "01.01.2014 00:00 UTC" ); + target.setLocalDateTime( "01.01.2014 00:00" ); + target.setLocalDate( "01.01.2014" ); + target.setLocalTime( "00:00" ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src.getZonedDateTime() ).contains( + ZonedDateTime.of( + LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) + ) ); + assertThat( src.getLocalDateTime() ).contains( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + assertThat( src.getLocalDate() ).contains( LocalDate.of( 2014, 1, 1 ) ); + assertThat( src.getLocalTime() ).contains( LocalTime.of( 0, 0 ) ); + } + + @ProcessorTest + public void testCalendarMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForCalendarConversion( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target.getForCalendarConversion() ).isNotNull(); + assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( + TimeZone.getTimeZone( + "UTC" ) ); + assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( + dateTime.getMonthValue() - 1 ); + assertThat( target.getForCalendarConversion().get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( target.getForCalendarConversion().get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( target.getForCalendarConversion().get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForCalendarConversion() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("UTC") + public void testZonedDateTimeToDateMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForDateConversionWithZonedDateTime( Optional.of( dateTime ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithZonedDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithZonedDateTime() ).contains( dateTime ); + } + + @ProcessorTest + public void testInstantToDateMapping() { + Instant instant = Instant.ofEpochMilli( 1539366615000L ); + + OptionalSource source = new OptionalSource(); + source.setForDateConversionWithInstant( Optional.ofNullable( instant ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + Date date = target.getForDateConversionWithInstant(); + assertThat( date ).isNotNull(); + assertThat( date.getTime() ).isEqualTo( 1539366615000L ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDateConversionWithInstant() ).contains( instant ); + } + + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + OptionalSource source = new OptionalSource(); + source.setForLocalDateTimeConversionWithLocalDate( Optional.of( localDate ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateTimeToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); + source.setForDateConversionWithLocalDateTime( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDateTime() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToSqlDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForSqlDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForSqlDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForSqlDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForSqlDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + public void testInstantToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.ofNullable( Instant.ofEpochSecond( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); + } + + @ProcessorTest + public void testInstantToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).contains( Instant.EPOCH ); + } + + @ProcessorTest + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testPeriodToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.ofNullable( Period.ofDays( 42 ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @ProcessorTest + public void testPeriodToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).contains( Period.of( 1, 2, 3 ) ); + } + + @ProcessorTest + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testDurationToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.ofNullable( Duration.ofMinutes( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @ProcessorTest + public void testDurationToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @ProcessorTest + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).contains( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @ProcessorTest + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java new file mode 100644 index 0000000000..cf94f31869 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java @@ -0,0 +1,160 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalSource { + + private Optional zonedDateTime = Optional.empty(); + + private Optional localDateTime = Optional.empty(); + + private Optional localDate = Optional.empty(); + + private Optional localTime = Optional.empty(); + + private Optional forCalendarConversion = Optional.empty(); + + private Optional forDateConversionWithZonedDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDate = Optional.empty(); + + private Optional forSqlDateConversionWithLocalDate = Optional.empty(); + + private Optional forDateConversionWithInstant = Optional.empty(); + + private Optional forLocalDateTimeConversionWithLocalDate = Optional.empty(); + + private Optional forInstantConversionWithString = Optional.empty(); + + private Optional forPeriodConversionWithString = Optional.empty(); + + private Optional forDurationConversionWithString = Optional.empty(); + + public Optional getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(Optional dateTime) { + this.zonedDateTime = dateTime; + } + + public Optional getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(Optional localDateTime) { + this.localDateTime = localDateTime; + } + + public Optional getLocalDate() { + return localDate; + } + + public void setLocalDate(Optional localDate) { + this.localDate = localDate; + } + + public Optional getLocalTime() { + return localTime; + } + + public void setLocalTime(Optional localTime) { + this.localTime = localTime; + } + + public Optional getForCalendarConversion() { + return forCalendarConversion; + } + + public void setForCalendarConversion(Optional forCalendarConversion) { + this.forCalendarConversion = forCalendarConversion; + } + + public Optional getForDateConversionWithZonedDateTime() { + return forDateConversionWithZonedDateTime; + } + + public void setForDateConversionWithZonedDateTime(Optional forDateConversionWithZonedDateTime) { + this.forDateConversionWithZonedDateTime = forDateConversionWithZonedDateTime; + } + + public Optional getForDateConversionWithLocalDateTime() { + return forDateConversionWithLocalDateTime; + } + + public void setForDateConversionWithLocalDateTime(Optional forDateConversionWithLocalDateTime) { + this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; + } + + public Optional getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(Optional forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public Optional getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(Optional forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Optional getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Optional forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public Optional getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate( + Optional forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public Optional getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Optional forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Optional getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Optional forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Optional getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Optional forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java new file mode 100644 index 0000000000..a266309b61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java @@ -0,0 +1,70 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalSourceTargetMapper { + + String DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm z"; + + String LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm"; + + String LOCAL_DATE_FORMAT = "dd.MM.yyyy"; + + String LOCAL_TIME_FORMAT = "HH:mm"; + + OptionalSourceTargetMapper INSTANCE = Mappers.getMapper( OptionalSourceTargetMapper.class ); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTarget(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetDefaultMapping(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + Target sourceToTargetDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + Target sourceToTargetLocalDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + Target sourceToTargetLocalDateMapped(OptionalSource source); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetLocalTimeMapped(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSource(Target target); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + OptionalSource targetToSourceDateTimeMapped(Target target); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + OptionalSource targetToSourceLocalDateTimeMapped(Target target); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + OptionalSource targetToSourceLocalDateMapped(Target target); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSourceLocalTimeMapped(Target target); + + @InheritInverseConfiguration(name = "sourceToTarget") + OptionalSource targetToSourceDefaultMapping(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java new file mode 100644 index 0000000000..d83b368bce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper1 { + + ErroneousKitchenDrawerOptionalMapper1 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper1.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfForks", source = "numberOfForks" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java new file mode 100644 index 0000000000..7d1824a5b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR, uses = VerySpecialNumberMapper.class ) +public interface ErroneousKitchenDrawerOptionalMapper3 { + + ErroneousKitchenDrawerOptionalMapper3 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper3.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfSpoons", source = "numberOfSpoons" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java new file mode 100644 index 0000000000..edd33c71d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper4 { + + ErroneousKitchenDrawerOptionalMapper4 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper4.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "depth", source = "depth" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java new file mode 100644 index 0000000000..bad921a7c2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper5 { + + ErroneousKitchenDrawerOptionalMapper5 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper5.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "length", source = "length" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..acd2e0876f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java new file mode 100644 index 0000000000..92862d3320 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface KitchenDrawerOptionalMapper2 { + + KitchenDrawerOptionalMapper2 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper2.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfKnifes", source = "numberOfKnifes" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..2b3e982584 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN, uses = VerySpecialNumberMapper.class ) +public interface KitchenDrawerOptionalMapper6 { + + KitchenDrawerOptionalMapper6 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper6.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "height", source = "height" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index 629f727273..604ff9bffa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -22,6 +22,7 @@ */ @WithClasses({ OversizedKitchenDrawerDto.class, + OversizedKitchenDrawerOptionalDto.class, RegularKitchenDrawerEntity.class, VerySpecialNumber.class, VerySpecialNumberMapper.class, @@ -63,6 +64,19 @@ public void testNoErrorCase() { public void testConversionFromLongToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper1.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"OptionalLong numberOfForks\". It has a possibly lossy conversion from " + + "OptionalLong to int.") + }) + public void testConversionFromOptionalLongToInt() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper2.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -76,6 +90,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper2.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper2.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"Optional numberOfKnifes\" has a possibly lossy conversion " + + "from Optional to Integer.") + }) + public void testConversionFromOptionalBigIntegerToInteger() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -89,6 +116,19 @@ public void testConversionFromBigIntegerToInteger() { public void testConversionFromStringToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"Optional drawerId\". It has a possibly lossy conversion from " + + "Optional to int.") + }) + public void testConversionFromOptionalStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -102,6 +142,19 @@ public void testConversionFromStringToInt() { public void test2StepConversionFromBigIntegerToLong() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper3.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Optional numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") + }) + public void test2StepConversionFromOptionalBigIntegerToLong() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper4.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -115,6 +168,20 @@ public void test2StepConversionFromBigIntegerToLong() { public void testConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper4.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional depth\". " + + "It has a possibly lossy conversion from Optional to float.") + }) + public void testConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper5.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -128,6 +195,20 @@ public void testConversionFromDoubleToFloat() { public void testConversionFromBigDecimalToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper5.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional length\". " + + "It has a possibly lossy conversion from Optional to Float.") + }) + public void testConversionFromOptionalBigDecimalToFloat() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -140,6 +221,19 @@ public void testConversionFromBigDecimalToFloat() { public void test2StepConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"OptionalDouble height\" has a possibly lossy conversion from " + + "OptionalDouble to float.") + }) + public void test2StepConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ListMapper.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java new file mode 100644 index 0000000000..f214fecd1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java @@ -0,0 +1,84 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalLong; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OversizedKitchenDrawerOptionalDto { + + /* yes, its a big drawer */ + private OptionalLong numberOfForks; + private Optional numberOfKnifes; + private Optional numberOfSpoons; + private Optional depth; + private Optional length; + private OptionalDouble height; + private Optional drawerId; + + public OptionalLong getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(OptionalLong numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public Optional getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(Optional numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Optional getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(Optional numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public Optional getDepth() { + return depth; + } + + public void setDepth(Optional depth) { + this.depth = depth; + } + + public Optional getLength() { + return length; + } + + public void setLength(Optional length) { + this.length = length; + } + + public OptionalDouble getHeight() { + return height; + } + + public void setHeight(OptionalDouble height) { + this.height = height; + } + + public Optional getDrawerId() { + return drawerId; + } + + public void setDrawerId(Optional drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java index d49a263deb..00b543c728 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.conversion.lossy; import java.math.BigInteger; +import java.util.Optional; /** * @author Sjaak Derksen @@ -19,4 +20,9 @@ VerySpecialNumber fromFloat(float f) { BigInteger toBigInteger(VerySpecialNumber v) { return new BigInteger( "10" ); } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + BigInteger toBigInteger(Optional v) { + return new BigInteger( "10" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java new file mode 100644 index 0000000000..a92b029b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ByteWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java new file mode 100644 index 0000000000..e469c44390 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalDouble; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleOptionalSource { + + private OptionalDouble b = OptionalDouble.empty(); + private OptionalDouble bb = OptionalDouble.empty(); + private OptionalDouble s = OptionalDouble.empty(); + private OptionalDouble ss = OptionalDouble.empty(); + private OptionalDouble i = OptionalDouble.empty(); + private OptionalDouble ii = OptionalDouble.empty(); + private OptionalDouble l = OptionalDouble.empty(); + private OptionalDouble ll = OptionalDouble.empty(); + private OptionalDouble f = OptionalDouble.empty(); + private OptionalDouble ff = OptionalDouble.empty(); + private OptionalDouble d = OptionalDouble.empty(); + private OptionalDouble dd = OptionalDouble.empty(); + + public OptionalDouble getB() { + return b; + } + + public void setB(OptionalDouble b) { + this.b = b; + } + + public OptionalDouble getBb() { + return bb; + } + + public void setBb(OptionalDouble bb) { + this.bb = bb; + } + + public OptionalDouble getS() { + return s; + } + + public void setS(OptionalDouble s) { + this.s = s; + } + + public OptionalDouble getSs() { + return ss; + } + + public void setSs(OptionalDouble ss) { + this.ss = ss; + } + + public OptionalDouble getI() { + return i; + } + + public void setI(OptionalDouble i) { + this.i = i; + } + + public OptionalDouble getIi() { + return ii; + } + + public void setIi(OptionalDouble ii) { + this.ii = ii; + } + + public OptionalDouble getL() { + return l; + } + + public void setL(OptionalDouble l) { + this.l = l; + } + + public OptionalDouble getLl() { + return ll; + } + + public void setLl(OptionalDouble ll) { + this.ll = ll; + } + + public OptionalDouble getF() { + return f; + } + + public void setF(OptionalDouble f) { + this.f = f; + } + + public OptionalDouble getFf() { + return ff; + } + + public void setFf(OptionalDouble ff) { + this.ff = ff; + } + + public OptionalDouble getD() { + return d; + } + + public void setD(OptionalDouble d) { + this.d = d; + } + + public OptionalDouble getDd() { + return dd; + } + + public void setDd(OptionalDouble dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java new file mode 100644 index 0000000000..ecf0d9bc41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java new file mode 100644 index 0000000000..f20e6fa5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class FloatWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java new file mode 100644 index 0000000000..6543b7bc0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalInt; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntOptionalSource { + + private OptionalInt b = OptionalInt.empty(); + private OptionalInt bb = OptionalInt.empty(); + private OptionalInt s = OptionalInt.empty(); + private OptionalInt ss = OptionalInt.empty(); + private OptionalInt i = OptionalInt.empty(); + private OptionalInt ii = OptionalInt.empty(); + private OptionalInt l = OptionalInt.empty(); + private OptionalInt ll = OptionalInt.empty(); + private OptionalInt f = OptionalInt.empty(); + private OptionalInt ff = OptionalInt.empty(); + private OptionalInt d = OptionalInt.empty(); + private OptionalInt dd = OptionalInt.empty(); + + public OptionalInt getB() { + return b; + } + + public void setB(OptionalInt b) { + this.b = b; + } + + public OptionalInt getBb() { + return bb; + } + + public void setBb(OptionalInt bb) { + this.bb = bb; + } + + public OptionalInt getS() { + return s; + } + + public void setS(OptionalInt s) { + this.s = s; + } + + public OptionalInt getSs() { + return ss; + } + + public void setSs(OptionalInt ss) { + this.ss = ss; + } + + public OptionalInt getI() { + return i; + } + + public void setI(OptionalInt i) { + this.i = i; + } + + public OptionalInt getIi() { + return ii; + } + + public void setIi(OptionalInt ii) { + this.ii = ii; + } + + public OptionalInt getL() { + return l; + } + + public void setL(OptionalInt l) { + this.l = l; + } + + public OptionalInt getLl() { + return ll; + } + + public void setLl(OptionalInt ll) { + this.ll = ll; + } + + public OptionalInt getF() { + return f; + } + + public void setF(OptionalInt f) { + this.f = f; + } + + public OptionalInt getFf() { + return ff; + } + + public void setFf(OptionalInt ff) { + this.ff = ff; + } + + public OptionalInt getD() { + return d; + } + + public void setD(OptionalInt d) { + this.d = d; + } + + public OptionalInt getDd() { + return dd; + } + + public void setDd(OptionalInt dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java new file mode 100644 index 0000000000..b1243d9c1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java new file mode 100644 index 0000000000..1df7baaec9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongOptionalSource { + + private OptionalLong b = OptionalLong.empty(); + private OptionalLong bb = OptionalLong.empty(); + private OptionalLong s = OptionalLong.empty(); + private OptionalLong ss = OptionalLong.empty(); + private OptionalLong i = OptionalLong.empty(); + private OptionalLong ii = OptionalLong.empty(); + private OptionalLong l = OptionalLong.empty(); + private OptionalLong ll = OptionalLong.empty(); + private OptionalLong f = OptionalLong.empty(); + private OptionalLong ff = OptionalLong.empty(); + private OptionalLong d = OptionalLong.empty(); + private OptionalLong dd = OptionalLong.empty(); + + public OptionalLong getB() { + return b; + } + + public void setB(OptionalLong b) { + this.b = b; + } + + public OptionalLong getBb() { + return bb; + } + + public void setBb(OptionalLong bb) { + this.bb = bb; + } + + public OptionalLong getS() { + return s; + } + + public void setS(OptionalLong s) { + this.s = s; + } + + public OptionalLong getSs() { + return ss; + } + + public void setSs(OptionalLong ss) { + this.ss = ss; + } + + public OptionalLong getI() { + return i; + } + + public void setI(OptionalLong i) { + this.i = i; + } + + public OptionalLong getIi() { + return ii; + } + + public void setIi(OptionalLong ii) { + this.ii = ii; + } + + public OptionalLong getL() { + return l; + } + + public void setL(OptionalLong l) { + this.l = l; + } + + public OptionalLong getLl() { + return ll; + } + + public void setLl(OptionalLong ll) { + this.ll = ll; + } + + public OptionalLong getF() { + return f; + } + + public void setF(OptionalLong f) { + this.f = f; + } + + public OptionalLong getFf() { + return ff; + } + + public void setFf(OptionalLong ff) { + this.ff = ff; + } + + public OptionalLong getD() { + return d; + } + + public void setD(OptionalLong d) { + this.d = d; + } + + public OptionalLong getDd() { + return dd; + } + + public void setDd(OptionalLong dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java new file mode 100644 index 0000000000..bed8ab6b59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java new file mode 100644 index 0000000000..0857b20473 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java @@ -0,0 +1,357 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ByteTarget.class, + ByteWrapperOptionalSource.class, + ByteWrapperTarget.class, + ShortTarget.class, + ShortWrapperOptionalSource.class, + ShortWrapperTarget.class, + IntOptionalSource.class, + IntTarget.class, + IntWrapperOptionalSource.class, + IntWrapperTarget.class, + LongOptionalSource.class, + LongTarget.class, + LongWrapperOptionalSource.class, + LongWrapperTarget.class, + FloatWrapperOptionalSource.class, + FloatWrapperTarget.class, + DoubleOptionalSource.class, + DoubleTarget.class, + DoubleWrapperOptionalSource.class, + DoubleWrapperTarget.class, + OptionalNumberConversionMapper.class +}) +public class NumberOptionalConversionTest { + + @ProcessorTest + public void shouldApplyByteWrapperConversions() { + ByteWrapperOptionalSource source = new ByteWrapperOptionalSource(); + source.setB( Optional.of( (byte) 1 ) ); + source.setBb( Optional.of( (byte) 2 ) ); + source.setS( Optional.of( (byte) 3 ) ); + source.setSs( Optional.of( (byte) 4 ) ); + source.setI( Optional.of( (byte) 5 ) ); + source.setIi( Optional.of( (byte) 6 ) ); + source.setL( Optional.of( (byte) 7 ) ); + source.setLl( Optional.of( (byte) 8 ) ); + source.setF( Optional.of( (byte) 9 ) ); + source.setFf( Optional.of( (byte) 10 ) ); + source.setD( Optional.of( (byte) 11 ) ); + source.setDd( Optional.of( (byte) 12 ) ); + + ByteWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyShortWrapperConversions() { + ShortWrapperOptionalSource source = new ShortWrapperOptionalSource(); + source.setB( Optional.of( (short) 1 ) ); + source.setBb( Optional.of( (short) 2 ) ); + source.setS( Optional.of( (short) 3 ) ); + source.setSs( Optional.of( (short) 4 ) ); + source.setI( Optional.of( (short) 5 ) ); + source.setIi( Optional.of( (short) 6 ) ); + source.setL( Optional.of( (short) 7 ) ); + source.setLl( Optional.of( (short) 8 ) ); + source.setF( Optional.of( (short) 9 ) ); + source.setFf( Optional.of( (short) 10 ) ); + source.setD( Optional.of( (short) 11 ) ); + source.setDd( Optional.of( (short) 12 ) ); + + ShortWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntConversions() { + IntOptionalSource source = new IntOptionalSource(); + source.setB( OptionalInt.of( 1 ) ); + source.setBb( OptionalInt.of( 2 ) ); + source.setS( OptionalInt.of( 3 ) ); + source.setSs( OptionalInt.of( 4 ) ); + source.setI( OptionalInt.of( 5 ) ); + source.setIi( OptionalInt.of( 6 ) ); + source.setL( OptionalInt.of( 7 ) ); + source.setLl( OptionalInt.of( 8 ) ); + source.setF( OptionalInt.of( 9 ) ); + source.setFf( OptionalInt.of( 10 ) ); + source.setD( OptionalInt.of( 11 ) ); + source.setDd( OptionalInt.of( 12 ) ); + + IntTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntWrapperConversions() { + IntWrapperOptionalSource source = new IntWrapperOptionalSource(); + source.setB( Optional.of( 1 ) ); + source.setBb( Optional.of( 2 ) ); + source.setS( Optional.of( 3 ) ); + source.setSs( Optional.of( 4 ) ); + source.setI( Optional.of( 5 ) ); + source.setIi( Optional.of( 6 ) ); + source.setL( Optional.of( 7 ) ); + source.setLl( Optional.of( 8 ) ); + source.setF( Optional.of( 9 ) ); + source.setFf( Optional.of( 10 ) ); + source.setD( Optional.of( 11 ) ); + source.setDd( Optional.of( 12 ) ); + + IntWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongConversions() { + LongOptionalSource source = new LongOptionalSource(); + source.setB( OptionalLong.of( 1 ) ); + source.setBb( OptionalLong.of( 2 ) ); + source.setS( OptionalLong.of( 3 ) ); + source.setSs( OptionalLong.of( 4 ) ); + source.setI( OptionalLong.of( 5 ) ); + source.setIi( OptionalLong.of( 6 ) ); + source.setL( OptionalLong.of( 7 ) ); + source.setLl( OptionalLong.of( 8 ) ); + source.setF( OptionalLong.of( 9 ) ); + source.setFf( OptionalLong.of( 10 ) ); + source.setD( OptionalLong.of( 11 ) ); + source.setDd( OptionalLong.of( 12 ) ); + + LongTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongWrapperConversions() { + LongWrapperOptionalSource source = new LongWrapperOptionalSource(); + source.setB( Optional.of( (long) 1 ) ); + source.setBb( Optional.of( (long) 2 ) ); + source.setS( Optional.of( (long) 3 ) ); + source.setSs( Optional.of( (long) 4 ) ); + source.setI( Optional.of( (long) 5 ) ); + source.setIi( Optional.of( (long) 6 ) ); + source.setL( Optional.of( (long) 7 ) ); + source.setLl( Optional.of( (long) 8 ) ); + source.setF( Optional.of( (long) 9 ) ); + source.setFf( Optional.of( (long) 10 ) ); + source.setD( Optional.of( (long) 11 ) ); + source.setDd( Optional.of( (long) 12 ) ); + + LongWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyFloatWrapperConversions() { + FloatWrapperOptionalSource source = new FloatWrapperOptionalSource(); + source.setB( Optional.of( 1f ) ); + source.setBb( Optional.of( 2f ) ); + source.setS( Optional.of( 3f ) ); + source.setSs( Optional.of( 4f ) ); + source.setI( Optional.of( 5f ) ); + source.setIi( Optional.of( 6f ) ); + source.setL( Optional.of( 7f ) ); + source.setLl( Optional.of( 8f ) ); + source.setF( Optional.of( 9f ) ); + source.setFf( Optional.of( 10f ) ); + source.setD( Optional.of( 11f ) ); + source.setDd( Optional.of( 12f ) ); + + FloatWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleConversions() { + DoubleOptionalSource source = new DoubleOptionalSource(); + source.setB( OptionalDouble.of( 1 ) ); + source.setBb( OptionalDouble.of( 2 ) ); + source.setS( OptionalDouble.of( 3 ) ); + source.setSs( OptionalDouble.of( 4 ) ); + source.setI( OptionalDouble.of( 5 ) ); + source.setIi( OptionalDouble.of( 6 ) ); + source.setL( OptionalDouble.of( 7 ) ); + source.setLl( OptionalDouble.of( 8 ) ); + source.setF( OptionalDouble.of( 9 ) ); + source.setFf( OptionalDouble.of( 10 ) ); + source.setD( OptionalDouble.of( 11 ) ); + source.setDd( OptionalDouble.of( 12 ) ); + + DoubleTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleWrapperConversions() { + DoubleWrapperOptionalSource source = new DoubleWrapperOptionalSource(); + source.setB( Optional.of( 1d ) ); + source.setBb( Optional.of( 2d ) ); + source.setS( Optional.of( 3d ) ); + source.setSs( Optional.of( 4d ) ); + source.setI( Optional.of( 5d ) ); + source.setIi( Optional.of( 6d ) ); + source.setL( Optional.of( 7d ) ); + source.setLl( Optional.of( 8d ) ); + source.setF( Optional.of( 9d ) ); + source.setFf( Optional.of( 10d ) ); + source.setD( Optional.of( 11d ) ); + source.setDd( Optional.of( 12d ) ); + + DoubleWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + @IssueKey("229") + public void wrapperToPrimitiveIsNullSafe() { + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ByteWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new DoubleWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ShortWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new IntWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new FloatWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new LongWrapperOptionalSource() ) ) + .isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java new file mode 100644 index 0000000000..94472586fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalNumberConversionMapper { + + OptionalNumberConversionMapper INSTANCE = Mappers.getMapper( OptionalNumberConversionMapper.class ); + + ByteWrapperTarget sourceToTarget(ByteWrapperOptionalSource source); + + ByteWrapperOptionalSource targetToSource(ByteWrapperTarget target); + + ShortWrapperTarget sourceToTarget(ShortWrapperOptionalSource source); + + ShortWrapperOptionalSource targetToSource(ShortWrapperTarget target); + + IntTarget sourceToTarget(IntOptionalSource source); + + IntOptionalSource targetToSource(IntTarget target); + + IntWrapperTarget sourceToTarget(IntWrapperOptionalSource source); + + IntWrapperOptionalSource targetToSource(IntWrapperTarget target); + + LongTarget sourceToTarget(LongOptionalSource source); + + LongOptionalSource targetToSource(LongTarget target); + + LongWrapperTarget sourceToTarget(LongWrapperOptionalSource source); + + LongWrapperOptionalSource targetToSource(LongWrapperTarget target); + + FloatWrapperTarget sourceToTarget(FloatWrapperOptionalSource source); + + FloatWrapperOptionalSource targetToSource(FloatWrapperTarget target); + + DoubleTarget sourceToTarget(DoubleOptionalSource source); + + DoubleOptionalSource targetToSource(DoubleTarget target); + + DoubleWrapperTarget sourceToTarget(DoubleWrapperOptionalSource source); + + DoubleWrapperOptionalSource targetToSource(DoubleWrapperTarget target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java new file mode 100644 index 0000000000..16acf6c69b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ShortWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java new file mode 100644 index 0000000000..30dca2cf39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalBeforeAfterMapper { + + OptionalBeforeAfterMapper INSTANCE = Mappers.getMapper( OptionalBeforeAfterMapper.class ); + + Target toTarget(Source source); + + @BeforeMapping + default void beforeDeepOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeDeepOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterDeepOptionalSourceWithNonOptionalTarget(@MappingTarget Target.SubType target, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithOptionalTarget(@MappingTarget Optional target, + Optional source) { + } + + @AfterMapping + default void afterDeepNonOptionalSourceOptionalTarget(@MappingTarget Optional target, + Source.SubType source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterShallowOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterShallowOptionalSourceWithNonOptionalTarget(@MappingTarget String target, + Optional source) { + } + + @AfterMapping + default void afterShallowNonOptionalSourceOptionalTarget(@MappingTarget Optional target, String source) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java new file mode 100644 index 0000000000..ba1a7b09d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + OptionalBeforeAfterMapper.class, Source.class, Target.class +}) +class OptionalBeforeAfterTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalBeforeAfterMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java new file mode 100644 index 0000000000..1284bca7ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java @@ -0,0 +1,87 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional deepOptionalToOptional; + private final Optional deepOptionalToNonOptional; + private final SubType deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final Optional shallowOptionalToNonOptional; + private final String shallowNonOptionalToOptional; + + public Source(Optional deepOptionalToOptional, Optional deepOptionalToNonOptional, + SubType deepNonOptionalToOptional, Optional shallowOptionalToOptional, + Optional shallowOptionalToNonOptional, String shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public Optional getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public SubType getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public Optional getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public String getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java new file mode 100644 index 0000000000..592e3338fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java @@ -0,0 +1,86 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional deepOptionalToOptional; + private final SubType deepOptionalToNonOptional; + private final Optional deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final String shallowOptionalToNonOptional; + private final Optional shallowNonOptionalToOptional; + + public Target(Optional deepOptionalToOptional, SubType deepOptionalToNonOptional, + Optional deepNonOptionalToOptional, Optional shallowOptionalToOptional, + String shallowOptionalToNonOptional, Optional shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public SubType getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public Optional getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public String getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public Optional getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java new file mode 100644 index 0000000000..9dd4b01256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.builder; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class OptionalBuilderTest { + + @ProcessorTest + @WithClasses(SimpleOptionalBuilderMapper.class) + void simpleOptionalBuilder() { + Optional targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( null ); + assertThat( targetOpt ).isEmpty(); + + targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( new SimpleOptionalBuilderMapper.Source( "test" ) ); + assertThat( targetOpt ).isNotEmpty(); + SimpleOptionalBuilderMapper.Target target = targetOpt.get(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java new file mode 100644 index 0000000000..5f24aa0cae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java @@ -0,0 +1,67 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.builder; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleOptionalBuilderMapper { + + SimpleOptionalBuilderMapper INSTANCE = Mappers.getMapper( SimpleOptionalBuilderMapper.class ); + + Optional map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + private Target(TargetBuilder builder) { + this.value = builder.value; + } + + public String getValue() { + return value; + } + + public static TargetBuilder builder() { + return new TargetBuilder(); + } + + } + + class TargetBuilder { + + private String value; + + public TargetBuilder value(String value) { + this.value = value; + return this; + } + + public Target build() { + return new Target(this); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java new file mode 100644 index 0000000000..a6f8c155d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.custom; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@SuppressWarnings("OptionalAssignedToNull") +public interface CustomOptionalMapper { + + CustomOptionalMapper INSTANCE = Mappers.getMapper( CustomOptionalMapper.class ); + + Target map(Source source); + + void update(@MappingTarget Target target, Source source); + + @Condition + default boolean isPresent(Optional optional) { + return optional != null; + } + + default E unwrapFromOptional(Optional optional) { + return optional == null + ? null + : optional.orElse( null ); + } + + class Target { + private String value = "initial"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Source { + private final Optional value; + + public Source(Optional value) { + this.value = value; + } + + public Optional getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java new file mode 100644 index 0000000000..51991ac478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java @@ -0,0 +1,60 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.custom; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(CustomOptionalMapper.class) +class CustomOptionalTest { + + @ProcessorTest + void shouldUseCustomMethodsWhenMapping() { + CustomOptionalMapper.Target target = CustomOptionalMapper.INSTANCE. + map( new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + void shouldUseCustomMethodsWhenUpdating() { + CustomOptionalMapper.Target target = new CustomOptionalMapper.Target(); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = new CustomOptionalMapper.Target(); + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java new file mode 100644 index 0000000000..5a276b4745 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalDifferentTypesMapper { + + OptionalDifferentTypesMapper INSTANCE = Mappers.getMapper( OptionalDifferentTypesMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java new file mode 100644 index 0000000000..74ab85e174 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java @@ -0,0 +1,204 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalDifferentTypesMapper.class, Source.class, Target.class +}) +class OptionalDifferentTypesTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalDifferentTypesMapper.class ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( new Source.SubType( "some value" ) ), Optional.empty(), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( Optional.empty(), Optional.of( new Source.SubType( "some value" ) ), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getConstructorOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( Optional.empty(), Optional.empty(), new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void optionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void optionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToOptional( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.publicOptionalToNonOptional; + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = new Source.SubType( "some value" ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java new file mode 100644 index 0000000000..0c0648000a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java @@ -0,0 +1,89 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.empty(); + private Optional optionalToNonOptional = Optional.empty(); + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source() { + this( Optional.empty(), Optional.empty(), null ); + } + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java new file mode 100644 index 0000000000..4f2b21c8a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java @@ -0,0 +1,84 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.of( new SubType( "initial" ) ); + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.of( new SubType( "initial" ) ); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java new file mode 100644 index 0000000000..3624efb00e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java @@ -0,0 +1,108 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeWithoutParameters() { + invokedMethods.add( "beforeWithoutParameters" ); + } + + @BeforeMapping + public void beforeWithOptionalSource(Optional source) { + invokedMethods.add( "beforeWithOptionalSource" ); + } + + @BeforeMapping + public void beforeWithSource(Source source) { + invokedMethods.add( "beforeWithSource" ); + } + + @BeforeMapping + public void beforeWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "beforeWithTargetType" ); + } + + @BeforeMapping + public void beforeWithBuilderTargetType(@TargetType Class targetBuilderClass) { + invokedMethods.add( "beforeWithBuilderTargetType" ); + } + + @BeforeMapping + public void beforeWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "beforeWithOptionalTarget" ); + } + + @BeforeMapping + public void beforeWithTarget(@MappingTarget Target target) { + invokedMethods.add( "beforeWithTarget" ); + } + + @BeforeMapping + public void beforeWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "beforeWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithoutParameters() { + invokedMethods.add( "afterWithoutParameters" ); + } + + @AfterMapping + public void afterWithOptionalSource(Optional source) { + invokedMethods.add( "afterWithOptionalSource" ); + } + + @AfterMapping + public void afterWithSource(Source source) { + invokedMethods.add( "afterWithSource" ); + } + + @AfterMapping + public void afterWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithTargetType" ); + } + + @AfterMapping + public void afterWithBuilderTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithBuilderTargetType" ); + } + + @AfterMapping + public void afterWithTarget(@MappingTarget Target target) { + invokedMethods.add( "afterWithTarget" ); + } + + @AfterMapping + public void afterWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "afterWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "afterWithOptionalTarget" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java new file mode 100644 index 0000000000..c80dfcb6b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java @@ -0,0 +1,165 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + MappingContext.class, + Source.class, + Target.class, +}) +public class OptionalLifecycleTest { + + @ProcessorTest + @WithClasses(OptionalToOptionalMapper.class) + void optionalToOptional() { + MappingContext context = new MappingContext(); + + OptionalToOptionalMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToOptionalWithBuilderMapper.class) + void optionalToOptionalWithBuilder() { + MappingContext context = new MappingContext(); + + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType", + "beforeWithTargetBuilder", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithBuilderTargetType", + "afterWithTarget", + "afterWithTargetBuilder", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMapper.class) + void optionalToType() { + MappingContext context = new MappingContext(); + + OptionalToTypeMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMultiSourceMapper.class) + void optionalToTypeMultiSource() { + MappingContext context = new MappingContext(); + + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.of( new Source() ), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), "Test", context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java new file mode 100644 index 0000000000..d7c5d08173 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalMapper { + + OptionalToOptionalMapper INSTANCE = Mappers.getMapper( OptionalToOptionalMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java new file mode 100644 index 0000000000..8cfab7fd00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalWithBuilderMapper { + + OptionalToOptionalWithBuilderMapper INSTANCE = Mappers.getMapper( OptionalToOptionalWithBuilderMapper.class ); + + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java new file mode 100644 index 0000000000..6a6f3e096e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMapper { + + OptionalToTypeMapper INSTANCE = Mappers.getMapper( OptionalToTypeMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java new file mode 100644 index 0000000000..d672f2ec05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMultiSourceMapper { + + OptionalToTypeMultiSourceMapper INSTANCE = Mappers.getMapper( OptionalToTypeMultiSourceMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, String otherValue, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java new file mode 100644 index 0000000000..c7005c6ca6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java new file mode 100644 index 0000000000..d5f68a1844 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static class Builder { + + private String value; + + public Builder value(String value) { + this.value = value; + return this; + } + + public Target build() { + Target target = new Target(); + target.setValue( value ); + return target; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java new file mode 100644 index 0000000000..e6ce6faa65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java @@ -0,0 +1,81 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nested; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Artist { + private final String name; + private final Label label; + + public Artist(String name, Label label) { + this.name = name; + this.label = label; + } + + public boolean hasName() { + return name != null; + } + + public String getName() { + return name; + } + + public boolean hasLabel() { + return label != null; + } + + public Optional
    *

    @@ -446,13 +445,11 @@ * If not possible, MapStruct will try to apply a user defined mapping method. * * - *

    * *

  • other *

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

    *

  • * *

    diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java index f5463b9026..4d635d8aa3 100644 --- a/core/src/main/java/org/mapstruct/SubclassMapping.java +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java @@ -73,12 +73,17 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Experimental public @interface SubclassMapping { + /** + * The source subclass to check for before using the default mapping as fallback. + * * @return the source subclass to check for before using the default mapping as fallback. */ Class source(); /** + * The target subclass to map the source to. + * * @return the target subclass to map the source to. */ Class target(); diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java index 2ddafaf516..d6aac264d4 100644 --- a/core/src/main/java/org/mapstruct/SubclassMappings.java +++ b/core/src/main/java/org/mapstruct/SubclassMappings.java @@ -51,6 +51,8 @@ public @interface SubclassMappings { /** + * The subclassMappings that should be applied. + * * @return the subclassMappings to apply. */ SubclassMapping[] value(); diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index bb593f53d1..95d4c7d5ce 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -44,6 +44,11 @@ @Retention(RetentionPolicy.CLASS) public @interface ValueMappings { + /** + * The value mappings that should be applied. + * + * @return the value mappings + */ ValueMapping[] value(); } diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java index c44797602a..1cb5bf2bb1 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControl.java +++ b/core/src/main/java/org/mapstruct/control/MappingControl.java @@ -99,8 +99,16 @@ @MappingControl( MappingControl.Use.COMPLEX_MAPPING ) public @interface MappingControl { + /** + * The type of mapping control that should be used. + * + * @return What should be used for the mapping control + */ Use value(); + /** + * Defines the options that can be used for the mapping control. + */ enum Use { /** diff --git a/core/src/main/java/org/mapstruct/control/MappingControls.java b/core/src/main/java/org/mapstruct/control/MappingControls.java index a3efef661f..c1663e551e 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControls.java +++ b/core/src/main/java/org/mapstruct/control/MappingControls.java @@ -21,5 +21,10 @@ @Target(ElementType.ANNOTATION_TYPE) public @interface MappingControls { + /** + * The mapping controls that should be applied to the annotated class. + * + * @return The mapping controls that should be applied to the annotated class. + */ MappingControl[] value(); } diff --git a/core/src/main/java/org/mapstruct/util/Experimental.java b/core/src/main/java/org/mapstruct/util/Experimental.java index a945a87d7c..5fd5eb3b5c 100644 --- a/core/src/main/java/org/mapstruct/util/Experimental.java +++ b/core/src/main/java/org/mapstruct/util/Experimental.java @@ -17,5 +17,11 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface Experimental { + + /** + * The reason why the feature is considered experimental. + * + * @return the reason why the feature is considered experimental. + */ String value() default ""; } diff --git a/processor/pom.xml b/processor/pom.xml index 01b8cbe263..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -172,6 +172,13 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java index 31d4d27585..b392281bf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java @@ -46,6 +46,8 @@ public interface ConversionProvider { Assignment from(ConversionContext conversionContext); /** + * Retrieves any helper methods required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any helper methods when required. @@ -53,6 +55,8 @@ public interface ConversionProvider { List getRequiredHelperMethods(ConversionContext conversionContext); /** + * Retrieves any fields required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any fields when required. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java index 99797f898c..3f32994734 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ConversionUtils.currency; /** + * Conversion between {@link Currency} and {@link String}. + * * @author Darren Rambaud */ public class CurrencyToStringConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java index 2b3e1bd960..db9480662d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -17,6 +17,9 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; /** + * A conversion provider that wraps / unwraps the underlying conversion in Optional. + * e.g., For conversion from {@code Optional} to {@code Integer}. + * * @author Filip Hrisafov */ public class OptionalWrapperConversionProvider implements ConversionProvider { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java index ad488d1a8f..dbab6bc4ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; /** + * Conversion between {@link java.util.Optional Optional} and its base type. + * * @author Filip Hrisafov */ public class TypeToOptionalConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 3b6e3c1686..80b900bb57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -19,6 +19,8 @@ /** * An abstract builder that can be reused for building {@link MappingMethod}(s). * + * @param the builder itself that needs to be used for chaining + * @param the method that the builder builds * @author Filip Hrisafov */ public abstract class AbstractMappingMethodBuilder, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java index 3f0b2123ae..cb9289e057 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -50,6 +50,8 @@ import static javax.lang.model.util.ElementFilter.methodsIn; /** + * Helper class which is responsible for collecting all additional annotations that should be added. + * * @author Ben Zegveld * @since 1.5 */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java index 5614376ea4..8f60b052b9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A method in a generated type that represents a setter with annotations. + * * @author Lucas Resch */ public class AnnotatedSetter extends GeneratedTypeMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java index 9b7912f752..9b0507f399 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate builder finisher method. + * * @author Filip Hrisafov */ public class BuilderFinisherMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java index f8c596a368..e1c320e16b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from an optional source to it's value. + * * @author Filip Hrisafov */ public class FromOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java index f02cc16b2c..8d6a999665 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java @@ -8,6 +8,8 @@ import org.mapstruct.ap.internal.model.common.ModelElement; /** + * Base class for methods available in a generated type. + * * @author Filip Hrisafov */ public abstract class GeneratedTypeMethod extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java index 879a76efbe..29904ff416 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that is based on a {@link MethodReference}. + * * @author Filip Hrisafov */ public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 89f295981f..8b526b9177 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -26,6 +26,7 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate object factory method. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 784342b8d7..2de61089ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -25,6 +25,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Factory for creating {@link PresenceCheck}s. + * * @author Filip Hrisafov */ public final class PresenceCheckMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java index 727518dad8..0d97c1a523 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents a service entry for the service loader file. + * * @author Christophe Labouisse on 14/07/2015. */ public class ServicesEntry extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java index 5925540e10..45bbc9d4b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from a source to an optional of the source. + * * @author Filip Hrisafov */ public class ToOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java index 6ecde886bf..f242b51f47 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents an annotation element. + * * @author Ben Zegveld */ public class AnnotationElement extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java index a334312007..eaa2fe68ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Decorates the assignment as an {@link Enum} constant access. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java index 9064640bb4..6c405c871d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -10,6 +10,8 @@ import org.mapstruct.ap.internal.util.Strings; /** + * Decorates the assignment as an {@link java.util.Optional#get()} call. + * * @author Filip Hrisafov */ public class OptionalGetWrapper extends AssignmentWrapper { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index b94bb82efd..9300f683ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -14,6 +14,9 @@ import org.mapstruct.ap.spi.BuilderInfo; /** + * Represents the information about a builder. + * How it can be constructed, the type it is building etc. + * * @author Filip Hrisafov */ public class BuilderType { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java index 48cee4bbe5..7ef818630a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java @@ -10,6 +10,7 @@ import javax.lang.model.element.Element; /** + * Represent information about the configured formatting. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java index f8844edcdd..78357affb8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java @@ -9,6 +9,8 @@ import java.util.Set; /** + * A {@link PresenceCheck} that negates the result of another presence check. + * * @author Filip Hrisafov */ public class NegatePresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java index 6bb191214f..d71f67a2a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if all the given presence checks are present. + * * @author Filip Hrisafov */ public class AllPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java index 0b5770e42f..5f8fac9623 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if any of the given presence checks are present. + * * @author Filip Hrisafov */ public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java index e100be7e79..0c7c132023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls a Java expression. + * * @author Filip Hrisafov */ public class JavaExpressionPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java index 3496486e1d..2da4ae4023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java @@ -14,6 +14,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A presence check that checks if the source reference is null. + * * @author Filip Hrisafov */ public class NullPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java index 0f75fbba69..d35e89fce2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls the suffix on the source reference. + * * @author Filip Hrisafov */ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java index 936d049af8..83e2bf2819 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -23,6 +23,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Represents a condition configuration as configured via {@code @Condition}. + * * @author Filip Hrisafov */ public class ConditionOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java index 85734c3083..41b74aa13a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java @@ -21,6 +21,8 @@ import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_NO_ELEMENTS; /** + * Represents an enum mapping as configured via {@code @EnumMapping}. + * * @author Filip Hrisafov */ public class EnumMappingOptions extends DelegatingOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java index 50ea5df6e0..7d0ab3a7ec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java @@ -10,6 +10,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Utility class for mapping methods. + * * @author Filip Hrisafov */ public final class MappingMethodUtils { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java index e2ccc3f476..df9c8e0b0c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import org.mapstruct.ap.internal.util.XmlConstants; /** + * Base class for built-in methods for converting from a particular type to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java index c8cd89169b..3461538c41 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Calendar} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class CalendarToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java index 036edd83a2..fb556e68a1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Date} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class DateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java index d0b06300c4..ef57e03175 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.util.JodaTimeConstants; /** + * A built-in method for converting from Joda {@code DateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java index 80fa9ba326..285ebb29fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java index 37c89ef360..8163aa2fb2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDate} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java index b29c7aec54..4fca4e0ed3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java index 178a77b5fa..b52c7fb586 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Andrei Arlou */ public class LocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java index 2e2544ade0..e2ec7d7696 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDate} to {@code XMLGregorianCalendar}. + * * @author Gunnar Morling */ public class LocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java index 9f0d2ad207..635853d0fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link String} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class StringToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java index 03927fd869..e52b306af0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java index 38f76a66ee..fd3d9a33a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Date}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java index ab6658bfc5..93ec3e8f96 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import java.util.Calendar; import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; @@ -16,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code DateTime} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java index f45488755c..9a9c1561fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDate}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java index af7fd9e9fd..a8d36a13cf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDateTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java index 3725691134..b26450d560 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * Conversion from {@code XMLGregorianCalendar} to Joda {@code LocalTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java index 2c9ced3413..7bcd5902a5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDate}. + * * @author Gunnar Morling */ public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java index b676bd03a4..21b4c57854 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDateTime}. + * * @author Andrei Arlou */ public class XmlGregorianCalendarToLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java index c932da6675..e293a9c80b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java @@ -18,6 +18,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link String}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToString extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java index 27a39cd2b7..d9e39b388e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link ZonedDateTime} to {@code XMLGregorianCalendar}. + * * @author Christian Bandowski */ public class ZonedDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 23af0a55f9..87ec3b4dc1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.source.Method; /** + * A {@link MethodSelector} that selects the most specific result type. + * * @author Filip Hrisafov */ public class MostSpecificResultTypeSelector implements MethodSelector { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java index 7acb1af210..1702a8d4d6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java @@ -14,6 +14,8 @@ /** * A selected method with additional metadata that might be required for further usage of the selected method. * + * @param the type of the method + * * @author Andreas Gudian */ public class SelectedMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java index d35b2c0dc6..fedbe997a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.internal.option; /** + * The different compiler mapping options that are available in MapStruct. + * * @author Filip Hrisafov */ public enum MappingOption { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java index 72a44d106f..67ce6e10ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java @@ -34,6 +34,11 @@ import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.methodsIn; +/** + * MapStruct specific abstract implementation of {@link ElementUtils}. + * This allows us to provide different implementations for different compilers and / or + * to allow us for easier implementation of using the module system. + */ public abstract class AbstractElementUtilsDecorator implements ElementUtils { private final Elements delegate; @@ -312,6 +317,8 @@ private TypeElement asTypeElement(TypeMirror mirror) { } /** + * Checks whether the {@code executable} does not have a private modifier. + * * @param executable the executable to check * @return {@code true}, iff the executable does not have a private modifier */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java index f2328a91cc..96768daf56 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java @@ -16,6 +16,11 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with meta annotations. + * + * @param The type of the processed annotation + * @param The type of the underlying holder for the processed annotation + * * @author Filip Hrisafov */ public abstract class MetaAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java index accae77718..76126bf488 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java @@ -16,6 +16,12 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with repeatable annotations. + * + * @param The singular annotation type + * @param The multiple annotation type + * @param The underlying holder for the processed annotations + * * @author Ben Zegveld */ public abstract class RepeatableAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java index 9b2981d7c5..5ebc835ba1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} which delegates all calls to another {@link Accessor}. + * * @author Filip Hrisafov */ public abstract class DelegateAccessor implements Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java index cc974b939f..d96788b7f7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java @@ -8,6 +8,8 @@ import javax.lang.model.element.ExecutableElement; /** + * Accessor for presence checks. + * * @author Filip Hrisafov */ public interface PresenceCheckAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 31edf3a6da..4be3c26ff9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} that can be used for reading a property from a bean. + * * @author Filip Hrisafov */ public interface ReadAccessor extends Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java index 608c1b21cf..aa93936662 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java @@ -6,6 +6,9 @@ package org.mapstruct.ap.internal.util.accessor; /** + * {@link ReadAccessor} that delegates to another {@link Accessor} and requires an implementation of + * {@link #getSimpleName()} + * * @author Filip Hrisafov */ public abstract class ReadDelegateAccessor extends DelegateAccessor implements ReadAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 511504c937..e7529dcd08 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -188,7 +188,6 @@ private boolean isAdderWithUpperCase4thCharacter(ExecutableElement method) { * {@link #getElementName(ExecutableElement) }. *

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

    * * @param method to be analyzed * diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index afcee7d247..333a11dbcc 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -317,7 +317,7 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type *

    * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * {@code builderElement} should be ignored, i.e. not checked for build elements. - *

    + * * @param builderElement the element for the builder * @param typeElement the element for the type that is being built * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java index 62d6c32809..11e1257be1 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java @@ -10,6 +10,8 @@ import javax.lang.model.util.Types; /** + * The default implementation of the {@link EnumMappingStrategy} service provider interface. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java index 9dae81cda2..2a1cacfb1f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -15,7 +15,6 @@ *

    * When generating the implementation of a mapping method, MapStruct will apply the following routine for each * attribute pair in the source and target object: - *

    *

      *
    • If source and target attribute have the same type, the value will be simply copied from source to target. * If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target diff --git a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java index abb6e9cb4f..226251dfab 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that prepends a prefix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java index d3f0c515b3..dec7af6de4 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a prefix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java index cf239600d4..3b76354fa7 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a suffix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java index 4cd92b9af8..1f4eb89217 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that appends a suffix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java index 7b8f374cf7..93bccf51d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java @@ -4,4 +4,7 @@ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ +/** + * Utility classes for the SPI package. + */ package org.mapstruct.ap.spi.util; From d59e6a04aa3fb4bb1ca313959d4eecc9891287c9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:09:36 +0100 Subject: [PATCH 139/214] Cleanup checks for JRE prior 21 in processor test module The processor test module is compiled using Java 21. Therefore, all tests that were configured to run prior Java 21 are no longer executed and can be safely removed --- .../conversion/date/DateConversionTest.java | 75 ------------------- .../jodatime/JodaConversionTest.java | 32 -------- .../runner/JdkCompilingExtension.java | 33 -------- 3 files changed, 140 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index bc90228206..346f34a054 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -13,9 +13,6 @@ import java.util.GregorianCalendar; import java.util.List; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; @@ -40,38 +37,6 @@ public class DateConversionTest { @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversions() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "06.07.2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); - } - - @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionsWithCustomLocale() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -85,8 +50,6 @@ public void shouldApplyDateFormatForConversionsJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -100,42 +63,6 @@ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { } @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMapping() { - Target target = new Target(); - target.setDate( "06.07.2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMappingWithCustomLocale() { - Target target = new Target(); - target.setDate( "juillet 06, 2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { Target target = new Target(); target.setDate( "06.07.2013" ); @@ -151,8 +78,6 @@ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { Target target = new Target(); target.setDate( "juillet 06, 2013" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java index 8574c7a6be..d447c455ff 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java @@ -13,8 +13,6 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -71,8 +69,6 @@ public void testLocalTimeToString() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_21) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -98,34 +94,6 @@ public void testSourceToTargetMappingForStrings() { assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); } - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void testSourceToTargetMappingForStringsJdk11() { - Source src = new Source(); - src.setLocalTime( new LocalTime( 0, 0 ) ); - src.setLocalDate( new LocalDate( 2014, 1, 1 ) ); - src.setLocalDateTime( new LocalDateTime( 2014, 1, 1, 0, 0 ) ); - src.setDateTime( new DateTime( 2014, 1, 1, 0, 0, 0, DateTimeZone.UTC ) ); - - // with given format - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( src ); - - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); - - // and now with default mappings - target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); - } - @ProcessorTest public void testStringToDateTime() { String dateTimeAsString = "01.01.2014 00:00 UTC"; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 115618382c..1e9a1bc22f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -12,9 +12,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.processing.Processor; -import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; @@ -23,10 +21,8 @@ import javax.tools.StandardLocation; import javax.tools.ToolProvider; -import org.junit.jupiter.api.condition.JRE; import org.mapstruct.ap.MappingProcessor; import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; /** * Extension that uses the JDK compiler to compile. @@ -140,35 +136,6 @@ private static List asFiles(List paths) { return classpath; } - /** - * The JDK 8 compiler needs some special treatment for the diagnostics. - * See comment in the function. - */ - @Override - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - if ( JRE.currentVersion() != JRE.JAVA_8 ) { - // The JDK 8+ compilers report all ERROR diagnostics properly. Also when there are multiple per line. - return expectedDiagnostics; - } - List filtered = new ArrayList<>(expectedDiagnostics.size()); - - // The JDK 8 compiler only reports the first message of kind ERROR that is reported for one source file line, - // so we filter out the surplus diagnostics. The input list is already sorted by file name and line number, - // with the order for the diagnostics in the same line being kept at the order as given in the test. - DiagnosticDescriptor previous = null; - for ( DiagnosticDescriptor diag : expectedDiagnostics ) { - if ( diag.getKind() != Kind.ERROR - || previous == null - || !previous.getSourceFileName().equals( diag.getSourceFileName() ) - || !Objects.equals( previous.getLine(), diag.getLine() ) ) { - filtered.add( diag ); - previous = diag; - } - } - - return filtered; - } - private static FilteringParentClassLoader newFilteringClassLoaderForJdk() { return new FilteringParentClassLoader( "kotlin.", From 73f042bdc5906a3d5a0774c6535dabce64a83799 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:25:06 +0100 Subject: [PATCH 140/214] Use explicit version instead of JRE#OTHER to disable on Java 27-ea `@DisabledOnJre(JRE.OTHER)` no longer disables the test if the JRE is unknown. --- .../org/mapstruct/itest/tests/MavenIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 4596efd630..60784d206a 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(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokBuilderTest() { } @@ -98,7 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokModuleTest() { } @@ -155,7 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void kotlinDataTest() { } From 6708e3592539ec5b4f94c09643c039e359b78eae Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 19:49:02 +0100 Subject: [PATCH 141/214] Remove testing with no longer maintained org.bsc.maven:maven-processor-plugin The last release of the plugin was in April 2024. In addition to that, the maven-compiler-plugin is de-facto the standard for compiling Java. Therefore, we are removing testing with the maven-processor-plugin. --- .../ProcessorInvocationInterceptor.java | 23 +++++------- .../testutil/extension/ProcessorTest.java | 5 +-- integrationtest/src/test/resources/pom.xml | 37 ------------------- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 85ce64952f..39cd5fdae6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -136,21 +136,16 @@ private void addAdditionalCliArguments(Verifier verifier) private void configureProcessor(Verifier verifier) { ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); String compilerId = processor.getCompilerId(); - if ( compilerId != null ) { - String profile = processor.getProfile(); - if ( profile == null ) { - profile = "generate-via-compiler-plugin"; + String profile = processor.getProfile(); + if ( profile == null ) { + profile = "generate-via-compiler-plugin"; + } + verifier.addCliOption( "-P" + profile ); + verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); } - verifier.addCliOption( "-P" + profile ); - verifier.addCliOption( "-Dcompiler-id=" + compilerId ); - if ( processor == ProcessorTest.ProcessorType.JAVAC ) { - if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { - verifier.addCliOption( "-Dmaven.compiler.proc=full" ); - } - } - } - else { - verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); } } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java index 95480eee9d..d5b4860d5b 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java @@ -46,9 +46,7 @@ enum ProcessorType { JAVAC( "javac" ), JAVAC_WITH_PATHS( "javac", JRE.OTHER, "generate-via-compiler-plugin-with-annotation-processor-paths" ), - ECLIPSE_JDT( "jdt", JRE.JAVA_8 ), - - PROCESSOR_PLUGIN( null, JRE.JAVA_8 ); + ECLIPSE_JDT( "jdt", JRE.JAVA_8 ); private final String compilerId; private final JRE max; @@ -111,7 +109,6 @@ ProcessorType[] processorTypes() default { ProcessorType.JAVAC, ProcessorType.JAVAC_WITH_PATHS, ProcessorType.ECLIPSE_JDT, - ProcessorType.PROCESSOR_PLUGIN }; /** diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c8a19228f0..c2ece3b38d 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -97,43 +97,6 @@ - - generate-via-processor-plugin - - false - - - - - org.bsc.maven - maven-processor-plugin - 3.3.3 - - - process - generate-sources - - process - - - - - \${project.build.directory}/generated-sources/mapstruct - - org.mapstruct.ap.MappingProcessor - - - - - ${project.groupId} - mapstruct-processor - ${mapstruct.version} - - - - - - debug-forked-javac From df97305df070ee5a33233a9072bd4c38d8d99e53 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 12:09:53 +0100 Subject: [PATCH 142/214] #3940 Use deterministic order for supporting fields and methods --- .../ap/internal/model/BeanMappingMethod.java | 4 ++-- .../ap/internal/model/SupportingField.java | 2 +- .../creation/MappingResolverImpl.java | 7 ++++--- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- .../OptionalSourceTargetMapperImpl.java | 20 +++++++++---------- .../java8time/SourceTargetMapperImpl.java | 20 +++++++++---------- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index c6fc7753f2..d67b550ad4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -301,8 +301,8 @@ else if ( !method.isUpdateMethod() ) { } // get bean mapping (when specified as annotation ) - this.missingIgnoredSourceProperties = new HashSet<>(); - this.redundantIgnoredSourceProperties = new HashSet<>(); + this.missingIgnoredSourceProperties = new LinkedHashSet<>(); + this.redundantIgnoredSourceProperties = new LinkedHashSet<>(); if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { // Get source properties explicitly mapped using @Mapping annotations Set mappedSourceProperties = method.getOptions().getMappings().stream() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java index caf6d6160e..2cac1ef351 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java @@ -47,7 +47,7 @@ public static void addAllFieldsIn(Set supportingMapping for ( SupportingMappingMethod supportingMappingMethod : supportingMappingMethods ) { Field field = supportingMappingMethod.getSupportingField(); if ( field != null ) { - targets.add( supportingMappingMethod.getSupportingField() ); + targets.add( field ); } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 2a36f6b890..67cdc09fc9 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -97,12 +98,12 @@ public class MappingResolverImpl implements MappingResolver { * Private methods which are not present in the original mapper interface and are added to map certain property * types. */ - private final Set usedSupportedMappings = new HashSet<>(); + private final Set usedSupportedMappings = new LinkedHashSet<>(); /** * Private fields which are added to map certain property types. */ - private final Set usedSupportedFields = new HashSet<>(); + private final Set usedSupportedFields = new LinkedHashSet<>(); public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtils, TypeUtils typeUtils, TypeFactory typeFactory, List sourceModel, @@ -199,7 +200,7 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; - this.supportingMethodCandidates = new HashSet<>(); + this.supportingMethodCandidates = new LinkedHashSet<>(); this.selectionCriteria = criteria; this.positionHint = positionHint; this.forger = forger; diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java index ccf8042e6b..941bd929a7 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -509,16 +509,16 @@ public Map targetToSourceWithCustomLocale(Map targetToSourceWithCustomLocale(Map Date: Sun, 1 Feb 2026 12:41:20 +0100 Subject: [PATCH 143/214] #1830 Add support for NullValuePropertyMappingStrategy.CLEAR strategy Using this strategy will clear target collections / maps when the source property is null --- NEXT_RELEASE_CHANGELOG.md | 1 + .../NullValuePropertyMappingStrategy.java | 9 +- ...apter-10-advanced-mapping-options.asciidoc | 3 + .../NullValuePropertyMappingStrategyGem.java | 3 +- ...nceSetterWrapperForCollectionsAndMaps.java | 27 ++-- .../model/source/MapperConfigOptions.java | 2 +- .../internal/model/source/MapperOptions.java | 2 +- ...anceSetterWrapperForCollectionsAndMaps.ftl | 8 +- .../nullvaluepropertymapping/clear/Bean.java | 40 ++++++ .../clear/BeanDTO.java | 40 ++++++ .../clear/BeanDTOWithId.java | 19 +++ .../clear/BeanMapper.java | 34 +++++ .../clear/BeanMapperWithStrategyOnMapper.java | 25 ++++ .../clear/NestedBean.java | 18 +++ .../NullValuePropertyMappingClearTest.java | 116 ++++++++++++++++++ 15 files changed, 329 insertions(+), 18 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 59fed4fed3..476f3e025d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -22,6 +22,7 @@ it will be treated as a potential builder. Builders through static methods on the type have a precedence. * Behaviour change: Warning when the target has no target properties (#1140) +* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index a58eed8a2a..0ab30d5271 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -56,5 +56,12 @@ public enum NullValuePropertyMappingStrategy { * If a source bean property equals {@code null} the target bean property will be ignored and retain its * existing value. */ - IGNORE; + IGNORE, + + /** + * If a source bean property equals {@code null} the target bean property will be cleared. + * This strategy is only applicable to target properties that are of type {@link java.util.Collection Collection} + * or {@link java.util.Map Map}. + */ + CLEAR; } diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 43f8fc38a0..34f1e19817 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -254,6 +254,9 @@ For all other objects an new instance is created. Please note that a default con 2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target. +3. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the target collection or map will be cleared when the source property is `null`. +This strategy only applies to `Collection` and `Map` target properties. + The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MapperConfig#nullValuePropertyMappingStrategy`. [NOTE] diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java index 77e4aa48d6..93c98b73ac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java @@ -15,5 +15,6 @@ public enum NullValuePropertyMappingStrategyGem { SET_TO_NULL, SET_TO_DEFAULT, - IGNORE; + IGNORE, + CLEAR; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java index 2dd7f81a0b..875368b014 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -5,19 +5,18 @@ */ package org.mapstruct.ap.internal.model.assignment; -import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; - import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; -import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; /** * This wrapper handles the situation where an assignment is done for an update method. @@ -34,8 +33,8 @@ public class ExistingInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { - private final boolean includeElseBranch; - private final boolean mapNullToDefault; + private final NullValuePropertyMappingStrategyGem nvpms; + private final NullValueCheckStrategyGem nvcs; private final Type targetType; public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, @@ -53,9 +52,9 @@ public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAs typeFactory, fieldAssignment ); - this.mapNullToDefault = SET_TO_DEFAULT == nvpms; + this.nvcs = nvcs; + this.nvpms = nvpms; this.targetType = targetType; - this.includeElseBranch = ALWAYS != nvcs && IGNORE != nvpms; } @Override @@ -68,11 +67,15 @@ public Set getImportTypes() { } public boolean isIncludeElseBranch() { - return includeElseBranch; + return nvcs != ALWAYS && nvpms != IGNORE; } public boolean isMapNullToDefault() { - return mapNullToDefault; + return nvpms == NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; + } + + public boolean isMapNullToClear() { + return nvpms == NullValuePropertyMappingStrategyGem.CLEAR; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index d606658878..2766faa64c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -8,8 +8,8 @@ import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 9c2203efd5..21cb7813c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -12,8 +12,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index 95662c4c79..8e771f0ddb 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -15,8 +15,12 @@ ${ext.targetBeanName}.${ext.targetReadAccessorName}.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && includeElseBranch>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; - } + <#if mapNullToClear> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + <#else > + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; + + } } else { diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java new file mode 100644 index 0000000000..545ddffe8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class Bean { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java new file mode 100644 index 0000000000..9529da8113 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class BeanDTO { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java new file mode 100644 index 0000000000..152a872d82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +public class BeanDTOWithId extends BeanDTO { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java new file mode 100644 index 0000000000..4d453579ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(target = "list", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", source = "bean.map", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO mapWithBeanMapping(Bean source, @MappingTarget BeanDTO target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java new file mode 100644 index 0000000000..0e26b91b5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) +public interface BeanMapperWithStrategyOnMapper { + + BeanMapperWithStrategyOnMapper INSTANCE = Mappers.getMapper( BeanMapperWithStrategyOnMapper.class ); + + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list") + @Mapping(target = "map", source = "bean.map") + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java new file mode 100644 index 0000000000..448645c107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +public class NestedBean { + private Bean bean; + + public Bean getBean() { + return bean; + } + + public void setBean(Bean bean) { + this.bean = bean; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java new file mode 100644 index 0000000000..20cbcdc2b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java @@ -0,0 +1,116 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Bean.class, + BeanDTO.class, + NestedBean.class, +}) +class NullValuePropertyMappingClearTest { + + @ProcessorTest + @WithClasses(BeanMapper.class) + void generatedMapperMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.mapWithBeanMapping( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + + @ProcessorTest + @WithClasses(BeanMapperWithStrategyOnMapper.class) + void generatedMapperWithMappingDefinedInConfigMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapperWithStrategyOnMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapperWithStrategyOnMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + +} From a5614dd798dbb6c07ed41e21708eaa2ecd153aad Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 19:41:07 +0100 Subject: [PATCH 144/214] Prepare release notes and update copyright.txt --- NEXT_RELEASE_CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++----- copyright.txt | 14 +++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 476f3e025d..c4ba49e24d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -13,7 +13,7 @@ - Data classes with multiple constructors - Data classes with all default parameters - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes - +* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` ### Enhancements @@ -21,22 +21,59 @@ * Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, it will be treated as a potential builder. Builders through static methods on the type have a precedence. -* Behaviour change: Warning when the target has no target properties (#1140) +* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. * Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) - - +* Use deterministic order for supporting fields and methods (#3940) +* Support `@AnnotatedWith` on decorators (#3659) +* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) +* Behaviour change: Warning when the target has no target properties (#1140) +* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) +* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) ### Bugs * Improve error message when mapping non-iterable to array (#3786) +* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) +* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. +* Support generic `@Context` (#3711) +* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) +* Properly recognize the type of public generic fields (#3807) +* Fix method in Record is treated as a fluent setter (#3886) +* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) +* Fix Compiler error when mapping an object named `Override` (#3905) ### Documentation +* General Improvements + * Javadoc + * Typos in comments + * Small code refactorings + ### Build +* Move Windows and MacOS builds outside of the main workflow +* Update release to release using the new Maven Central Portal +* Skip codecov coverage on forks +* Improve testing support for Kotlin + ### Behaviour Change -#### Warning when the target has no target properties +#### Warning when the target has no target properties (#1140) With this change, if the target bean does not have any target properties, a warning will be shown. This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. + +#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) + +With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. +The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. + +#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) + +With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. + +#### Mark `String` to `Number` as lossy conversion (#3848) + +With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. +This is similar to what is happening when mapping `long` to `int`, etc. +The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/copyright.txt b/copyright.txt index bd0034b076..356deb904d 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,12 +1,14 @@ Contributors ============ +Aleksey Ivashin - https://github.com/xumk Alexandr Shalugin - https://github.com/shalugin Amine Touzani - https://github.com/ttzn Andreas Gudian - https://github.com/agudian Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime +Cause Chung - https://github.com/cuzfrog Christian Bandowski - https://github.com/chris922 Chris DeLashmutt - https://github.com/cdelashmutt-pivotal Christian Kosmowski - https://github.com/ckosmowski @@ -15,6 +17,7 @@ Christophe Labouisse - https://github.com/ggtools Ciaran Liedeman - https://github.com/cliedeman Cindy Wang - https://github.com/birdfriend Cornelius Dirmeier - https://github.com/cornzy +Dennis Melzer - https://github.com/ David Feinblum - https://github.com/dvfeinblum Darren Rambaud - https://github.com/xyzst Dekel Pilli - https://github.com/dekelpilli @@ -53,7 +56,11 @@ Pavel Makhov - https://github.com/streetturtle Peter Larson - https://github.com/pjlarson Remko Plantenga - https://github.com/sonata82 Remo Meier - https://github.com/remmeier +Roel Mangelschots - https://github.com/rmschots +Ritesh Chopade - https://github.com/codeswithritesh Richard Lea - https://github.com/chigix +Roman Obolonskyi - https://github.com/Obolrom +Samil Can - https://github.com/SamilCan Saheb Preet Singh - https://github.com/sahebpreet Samuel Wright - https://github.com/samwright Sebastian Haberey - https://github.com/sebastianhaberey @@ -65,10 +72,15 @@ Taras Mychaskiw - https://github.com/twentylemon Thibault Duperron - https://github.com/Zomzog Tomáš Poledný - https://github.com/Saljack Tobias Meggendorfer - https://github.com/incaseoftrouble +Tran Ngoc Nhan - https://github.com/ Tillmann Gaida - https://github.com/Tillerino Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 -Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file +Xiu Hong Kooi - https://github.com/kooixh +Yang Tang - https://github.com/tangyang9464 +Zegveld - https://github.com/Zegveld +znight1020 - https://github.com/znight1020 +zral - https://github.com/zyberzebra From 14f9f6e2877e5ae1b28ce3b59e381628f72fa349 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:10:29 +0000 Subject: [PATCH 145/214] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f2b85d4969..ae877ef10c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:10:28Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From 561d5dad858a4ba621bcab1f50452ea3851c17eb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:56:00 +0100 Subject: [PATCH 146/214] Adjust release to use Maven Central Publishing Portal --- .github/workflows/release.yml | 2 ++ parent/pom.xml | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4dffe0586..a4feca94a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,8 @@ jobs: JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} + JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN: ${{ secrets.SONATYPE_CENTRAL_TOKEN }} JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} run: | diff --git a/parent/pom.xml b/parent/pom.xml index ae877ef10c..0043cfb44a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -880,24 +880,21 @@ - - - ALWAYS - https://oss.sonatype.org/service/local - https://oss.sonatype.org/content/repositories/snapshots/ - true - true + + + RELEASE + https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy - - - org.mapstruct - mapstruct-jdk8 - false - false - - - - + + + + org.mapstruct + mapstruct-jdk8 + false + false + + + From 6389f2a8056b01e792d02acee6dd1bf9ef3a3718 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:58:29 +0100 Subject: [PATCH 147/214] Revert "Releasing version 1.7.0.Beta1" This reverts commit 14f9f6e2877e5ae1b28ce3b59e381628f72fa349. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 0043cfb44a..01b97c10bf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:10:28Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From ab72ced6fb7a71591d51343493bf736379c61beb Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:59:33 +0000 Subject: [PATCH 148/214] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 01b97c10bf..f81ea2c5d1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:59:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From fc2d5e520c5bceff54c302641431465410bdf031 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:20 +0100 Subject: [PATCH 149/214] Move jReleaser artifactOverrides in the right place --- parent/pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index f81ea2c5d1..962839bf19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -885,15 +885,15 @@ RELEASE https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy + + + org.mapstruct + mapstruct-jdk8 + false + false + + - - - org.mapstruct - mapstruct-jdk8 - false - false - - From 744c398326e9fa281134330dafdbdfecf1653c02 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:32 +0100 Subject: [PATCH 150/214] Revert "Releasing version 1.7.0.Beta1" This reverts commit ab72ced6fb7a71591d51343493bf736379c61beb. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 962839bf19..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:59:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 84318113ae02adae62e1d92ee5b884d6b58337b1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:33 +0000 Subject: [PATCH 151/214] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..02fd68fcff 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T21:45:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From c7a0385903bfadfb8ef4779be2707b469fc2ace1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:02:16 +0000 Subject: [PATCH 152/214] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 69 --------------------------------------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 +-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 10 insertions(+), 79 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index c4ba49e24d..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,79 +1,10 @@ ### Features -* Support for Java 21 Sequenced Collections (#3240) -* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: - - `Optional` to `Optional` - Both source and target wrapped in `Optional` - - `Optional` to Non-`Optional` - Unwrapping `Optional` values - - Non-`Optional` to `Optional` - Wrapping values in `Optional` - - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. -* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - - Single field data classes - - Proper primary constructor detection - - Data classes with multiple constructors - - Data classes with all default parameters - - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes -* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` - ### Enhancements -* Add support for locale parameter for numberFormat and dateFormat (#3628) -* Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, -it will be treated as a potential builder. -Builders through static methods on the type have a precedence. -* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. -* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) -* Use deterministic order for supporting fields and methods (#3940) -* Support `@AnnotatedWith` on decorators (#3659) -* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) -* Behaviour change: Warning when the target has no target properties (#1140) -* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) -* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) - ### Bugs -* Improve error message when mapping non-iterable to array (#3786) -* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) -* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. -* Support generic `@Context` (#3711) -* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) -* Properly recognize the type of public generic fields (#3807) -* Fix method in Record is treated as a fluent setter (#3886) -* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) -* Fix Compiler error when mapping an object named `Override` (#3905) - ### Documentation -* General Improvements - * Javadoc - * Typos in comments - * Small code refactorings - ### Build -* Move Windows and MacOS builds outside of the main workflow -* Update release to release using the new Maven Central Portal -* Skip codecov coverage on forks -* Improve testing support for Kotlin - -### Behaviour Change - -#### Warning when the target has no target properties (#1140) - -With this change, if the target bean does not have any target properties, a warning will be shown. -This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. - -#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) - -With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. -The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. - -#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) - -With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. - -#### Mark `String` to `Number` as lossy conversion (#3848) - -With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. -This is similar to what is happening when mapping `long` to `int`, etc. -The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 02fd68fcff..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T21:45:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 5cf1a98d29975aa32fa4a6e2a78e2808bc8fdec9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 11:48:24 +0100 Subject: [PATCH 153/214] Fix location for Javadoc when generating distribution zip --- distribution/src/main/assembly/dist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml index f0c727f8da..e0bc00496b 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -76,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api From 06e27d575365147ed17a991c5cc90a1f63436eda Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:56:16 +0100 Subject: [PATCH 154/214] Remove unnecessary `keySet()` invocation (#3989) --- .../ap/internal/model/NestedTargetPropertyMappingHolder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 2b407e240c..f7b28b3f84 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -151,8 +151,7 @@ public NestedTargetPropertyMappingHolder build() { entryByTP.getValue(), groupedByTP.singleTargetReferences.get( targetProperty ) ); - boolean multipleSourceParametersForTP = - groupedBySourceParam.groupedBySourceParameter.keySet().size() > 1; + boolean multipleSourceParametersForTP = groupedBySourceParam.groupedBySourceParameter.size() > 1; // All not processed mappings that should have been applied to all are part of the unprocessed // defined targets From aff971d7b713bcc15eb750840b00369b61cb28f5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:13 +0100 Subject: [PATCH 155/214] Let GitHub determine whether or not the released version is the latest or not --- parent/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..ffa9952de5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -904,6 +904,11 @@ ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + legacy From e9ebec2307fed606fb58f60d2e2226caf9f47614 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:56 +0100 Subject: [PATCH 156/214] Factory method for LinkedHashMap and LinkedHashSet is always there for SequencedSet and SequencedMap (#3990) --- .../mapstruct/ap/internal/model/common/TypeFactory.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 c2646f63db..eb2c1dee07 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 @@ -152,15 +152,11 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_SET_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_MAP_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) ); this.loggingVerbose = loggingVerbose; From 15312d6e46fb7744cf63e90ec70f20f518190457 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:14:53 +0100 Subject: [PATCH 157/214] Fix self check in equals of Type (#3995) --- .../java/org/mapstruct/ap/internal/model/common/Type.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 29bb3cd906..8a91c80f03 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1303,8 +1303,8 @@ public boolean equals(Object obj) { Type other = (Type) obj; if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { - return ( this.hasExtendsBound() == this.hasExtendsBound() - || this.hasSuperBound() == this.hasSuperBound() ) + return ( this.hasExtendsBound() == other.hasExtendsBound() + || this.hasSuperBound() == other.hasSuperBound() ) && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); } else { From 588006727dea2beb8e0c6019e52bab95ec57993e Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:53:45 +0100 Subject: [PATCH 158/214] Improve performance of `Type.describe()` by removing regex matching (#3991) --- .../org/mapstruct/ap/internal/model/common/Type.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8a91c80f03..3e81cd622d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1331,7 +1331,7 @@ public String describe() { } else { // name allows for inner classes - String name = getFullyQualifiedName().replaceFirst( "^" + getPackageName() + ".", "" ); + String name = getNameKeepingInnerClasses(); List typeParams = getTypeParameters(); if ( typeParams.isEmpty() ) { return name; @@ -1343,6 +1343,15 @@ public String describe() { } } + private String getNameKeepingInnerClasses() { + String packageNamePrefix = getPackageName() + "."; + String fullyQualifiedName = getFullyQualifiedName(); + if (fullyQualifiedName.startsWith( packageNamePrefix ) ) { + return fullyQualifiedName.substring( packageNamePrefix.length() ); + } + return fullyQualifiedName; + } + /** * * @return an identification that can be used as part in a forged method name. From 4a75490e6d8b622083720bf60596680b49ce5e31 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:56:19 +0100 Subject: [PATCH 159/214] Simplified boolean logic in ValueMappingMethod by removing inversion (#4007) --- .../ap/internal/model/ValueMappingMethod.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java index 66b6a75961..46af8f0efd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java @@ -184,8 +184,8 @@ private List enumToEnumMapping(Method method, Type sourceType, Typ List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean targetErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean targetErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); if ( sourceErrorOccurred || targetErrorOccurred ) { return mappings; } @@ -268,8 +268,8 @@ private List enumToStringMapping(Method method, Type sourceType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean anyRemainingUsedError = !reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean anyRemainingUsedError = reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); if ( sourceErrorOccurred || anyRemainingUsedError ) { return mappings; } @@ -298,7 +298,7 @@ private List stringToEnumMapping(Method method, Type targetType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( targetType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); reportWarningIfAnyRemainingOrAnyUnMappedMissing( method ); if ( sourceErrorOccurred ) { return mappings; @@ -372,7 +372,7 @@ else if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { foundIncorrectMapping = true; } } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method method) { @@ -388,7 +388,7 @@ private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method meth ); foundIncorrectMapping = true; } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private void reportWarningIfAnyRemainingOrAnyUnMappedMissing(Method method) { @@ -462,7 +462,7 @@ else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != n ); } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private Type determineUnexpectedValueMappingException() { From 66625e61a4c4cbe72b58cb4187fde2cdf8e84fe4 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:01:38 +0100 Subject: [PATCH 160/214] Simplify fail in assertCheckstyleRules (#4003) --- .../org/mapstruct/ap/testutil/runner/CompilingExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a02ab2fe47..2599e62c2f 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 @@ -55,6 +55,7 @@ import org.xml.sax.InputSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; @@ -237,8 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { String errorLog = errorStream.toString( "UTF-8" ); - assertThat( true ).describedAs( "Expected checkstyle compliant output, but got errors:\n" + errorLog ) - .isEqualTo( false ); + fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } } From 876a62d282af0ca0c5b6bf21070c5441db95c9b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:04:23 +0100 Subject: [PATCH 161/214] Update license plugin (#3999) --- .../build.gradle | 5 ++ .../settings.gradle | 5 ++ parent/pom.xml | 72 ++++++++++--------- pom.xml | 10 ++- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle index e62a8d087f..0df032f0f2 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ plugins { id 'java' } diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle index f62a77ab7b..b6ca918e91 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle @@ -1 +1,6 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ rootProject.name = 'gradle-incremental-compilation-test' \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index ffa9952de5..ad95ffacf3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -509,9 +509,9 @@ ${kotlin.version} - com.mycila.maven-license-plugin - maven-license-plugin - 1.10.b1 + com.mycila + license-maven-plugin + 4.6 org.codehaus.mojo @@ -649,39 +649,43 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
      ${basedir}/../etc/license.txt
      true - - **/.idea/** - **/.mvn/** - **/build-config/checkstyle.xml - **/build-config/import-control.xml - copyright.txt - **/LICENSE.txt - **/mapstruct.xml - **/ci-settings.xml - **/eclipse-formatter-config.xml - **/forbidden-apis.txt - **/checkstyle-for-generated-sources.xml - **/nb-configuration.xml - **/junit-platform.properties - maven-settings.xml - readme.md - CONTRIBUTING.md - NEXT_RELEASE_CHANGELOG.md - .gitattributes - .gitignore - .factorypath - .checkstyle - *.yml - mvnw* - **/*.asciidoc - **/binding.xjb - **/*.flattened-pom.xml - + + +
      ${basedir}/../etc/license.txt
      + + **/.idea/** + **/.mvn/** + **/build-config/checkstyle.xml + **/build-config/import-control.xml + copyright.txt + **/LICENSE.txt + **/mapstruct.xml + **/ci-settings.xml + **/eclipse-formatter-config.xml + **/forbidden-apis.txt + **/checkstyle-for-generated-sources.xml + **/nb-configuration.xml + **/junit-platform.properties + maven-settings.xml + readme.md + CONTRIBUTING.md + NEXT_RELEASE_CHANGELOG.md + .gitattributes + .gitignore + .factorypath + .checkstyle + *.yml + mvnw* + **/*.asciidoc + **/binding.xjb + **/*.flattened-pom.xml + +
      +
      SLASHSTAR_STYLE SLASHSTAR_STYLE diff --git a/pom.xml b/pom.xml index f55f0955d3..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -35,10 +35,14 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
      etc/license.txt
      + + +
      etc/license.txt
      +
      +
      XML_STYLE SLASHSTAR_STYLE From f0d488712f76e02a51218e5eeaefb65bd2680a32 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:43:48 +0100 Subject: [PATCH 162/214] Use StandardCharsets.UTF_8 in tests (#4002) --- .../mapstruct/ap/testutil/runner/CompilingExtension.java | 3 ++- .../org/mapstruct/ap/testutil/runner/GeneratedSource.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) 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 2599e62c2f..cb12be725c 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 @@ -10,6 +10,7 @@ import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -237,7 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { - String errorLog = errorStream.toString( "UTF-8" ); + String errorLog = errorStream.toString( StandardCharsets.UTF_8 ); fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } 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 18a53b4971..6785ea5cf2 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -6,9 +6,9 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; -import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -119,7 +119,7 @@ public JavaFileAssert forJavaFile(String path) { return new JavaFileAssert( new File( sourceOutputDir.get() + "/" + path ) ); } - private void handleFixtureComparison() throws UnsupportedEncodingException { + private void handleFixtureComparison() { for ( Class fixture : fixturesFor ) { String fixtureName = getMapperName( fixture ); URL expectedFile = getExpectedResource( fixtureName ); @@ -131,7 +131,7 @@ private void handleFixtureComparison() throws UnsupportedEncodingException { ) ); } else { - File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), "UTF-8" ) ); + File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), StandardCharsets.UTF_8 ) ); forMapper( fixture ).hasSameMapperContent( expectedResource ); } fixture.getPackage().getName(); From 1f3524570e362c34f4d0d7ce983c3596c83ad7c7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:25:03 +0100 Subject: [PATCH 163/214] Add missing self reference in GeneratedTypeBuilder (#4009) --- .../java/org/mapstruct/ap/internal/model/GeneratedType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 0bc3ec7bf0..4b39e92273 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -30,9 +30,9 @@ public abstract class GeneratedType extends ModelElement { private static final String JAVA_LANG_PACKAGE = "java.lang"; - protected abstract static class GeneratedTypeBuilder { + protected abstract static class GeneratedTypeBuilder> { - private T myself; + private final T myself; protected TypeFactory typeFactory; protected ElementUtils elementUtils; protected Options options; From bff3efd10640170a5ad10dea8a5fa0e4173a932a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Mar 2026 00:25:54 +0100 Subject: [PATCH 164/214] Remove obsolete override of AssertJ version in integration tests (#4015) --- integrationtest/src/test/resources/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c2ece3b38d..3053b211e1 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -26,8 +26,6 @@ ${mapstruct.version} - - 1.7.1 From 94da2c3f05af31ab9693056aab9785549212940b Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:30:40 +0100 Subject: [PATCH 165/214] Remove unsued methods in Fields leftover from c2e803403027f3fae92bd15b0ba50ab7df5063e6 (#4010) --- .../mapstruct/ap/internal/util/Fields.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java index 8e16a95274..b2f8ac1442 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java @@ -6,13 +6,7 @@ package org.mapstruct.ap.internal.util; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * Provides functionality around {@link VariableElement}s. @@ -35,17 +29,4 @@ static boolean isPublic(VariableElement method) { private static boolean isNotStatic(VariableElement method) { return !method.getModifiers().contains( Modifier.STATIC ); } - - private static TypeElement asTypeElement(TypeMirror mirror) { - return (TypeElement) ( (DeclaredType) mirror ).asElement(); - } - - private static boolean hasNonObjectSuperclass(TypeElement element) { - if ( element.getSuperclass().getKind() == TypeKind.ERROR ) { - throw new TypeHierarchyErroneousException( element ); - } - - return element.getSuperclass().getKind() == TypeKind.DECLARED - && !asTypeElement( element.getSuperclass() ).getQualifiedName().toString().equals( "java.lang.Object" ); - } } From ded3daa5fcad390d60fe04d4d8e9479466722d63 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:31:23 +0100 Subject: [PATCH 166/214] Upgrade Visitor6 to Visitor8 (#4011) --- .../src/main/java/org/mapstruct/ap/MappingProcessor.java | 4 ++-- .../mapstruct/ap/internal/util/AccessorNamingUtils.java | 8 ++++---- .../mapstruct/ap/spi/DefaultAccessorNamingStrategy.java | 8 ++++---- .../java/org/mapstruct/ap/spi/DefaultBuilderProvider.java | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index a66762d739..32e65c7047 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -30,7 +30,7 @@ import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementKindVisitor6; +import javax.lang.model.util.ElementKindVisitor8; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; @@ -402,7 +402,7 @@ private R process(ProcessorContext context, ModelElementProcessor p private TypeElement asTypeElement(Element element) { return element.accept( - new ElementKindVisitor6() { + new ElementKindVisitor8() { @Override public TypeElement visitTypeAsInterface(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java index 0f17946f72..8025acfeb1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java @@ -10,8 +10,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; @@ -84,7 +84,7 @@ public String getElementNameForAdder(Accessor adderMethod) { private static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -98,7 +98,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index e7529dcd08..a726e9691f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -14,8 +14,8 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; import kotlin.Metadata; @@ -273,7 +273,7 @@ public String getElementName(ExecutableElement adderMethod) { */ protected static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -287,7 +287,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 333a11dbcc..c1126fa9fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -21,8 +21,8 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; /** @@ -126,7 +126,7 @@ private TypeElement getTypeElement(DeclaredType declaredType) { } return declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; @@ -148,7 +148,7 @@ private DeclaredType getDeclaredType(TypeMirror type) { throw new TypeHierarchyErroneousException( type ); } return type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; From 0780d98fc2045092adbfe4417e46d853ecd946b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:00 +0100 Subject: [PATCH 167/214] Upgrade freemarker to 2.3.34 (#4012) --- parent/pom.xml | 2 +- .../org/mapstruct/ap/internal/model/Annotation.ftl | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index ad95ffacf3..88bfd5053c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -119,7 +119,7 @@ org.freemarker freemarker - 2.3.32 + 2.3.34 org.assertj diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index 81f3d2ba8b..0b6737fbe4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -7,12 +7,10 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> <#switch properties?size> - <#case 0> + <#on 0> @<@includeModel object=type/><#rt> - <#break> - <#case 1> + <#on 1> @<@includeModel object=type/>(<@includeModel object=properties[0]/>)<#rt> - <#break> <#default> @<@includeModel object=type/>( <#list properties as property> From e0fa28868d8e01ecdb9df23440464aa9f1f07832 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:41 +0100 Subject: [PATCH 168/214] Remove deprecated Number api usage from tests (#4013) --- .../ap/test/selection/twosteperror/ErroneousMapperMC.java | 2 +- .../mapstruct/ap/test/source/constants/SourceConstantsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java index 4b7dcf90a3..b6381cf6da 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java @@ -22,7 +22,7 @@ default BigDecimal methodX1(SourceType s) { } default Double methodX2(SourceType s) { - return new Double( s.t1 ); + return Double.valueOf( s.t1 ); } // CHECKSTYLE:OFF diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java index b331a2efba..1b1764fe56 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java @@ -47,7 +47,7 @@ public void shouldMapSameSourcePropertyToSeveralTargetProperties() throws ParseE assertThat( target.getStringConstant() ).isEqualTo( "stringConstant" ); assertThat( target.getEmptyStringConstant() ).isEqualTo( "" ); assertThat( target.getIntegerConstant() ).isEqualTo( 14 ); - assertThat( target.getLongWrapperConstant() ).isEqualTo( new Long( 3001L ) ); + assertThat( target.getLongWrapperConstant() ).isEqualTo( Long.valueOf( 3001L ) ); assertThat( target.getDateConstant() ).isEqualTo( getDate( "dd-MM-yyyy", "09-01-2014" ) ); assertThat( target.getNameConstants() ).isEqualTo( Arrays.asList( "jack", "jill", "tom" ) ); assertThat( target.getCountry() ).isEqualTo( CountryEnum.THE_NETHERLANDS ); From dab3eaf237eb025a0d59f997263fea515cb6db64 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:44:29 +0100 Subject: [PATCH 169/214] #3949: Support SET_TO_NULL for overloaded target methods, requiring a cast (#3988) --- .../model/CollectionAssignmentBuilder.java | 3 +- .../ap/internal/model/PropertyMapping.java | 34 ++++++- .../model/assignment/SetterWrapper.java | 24 ++++- .../model/assignment/UpdateWrapper.java | 9 +- .../ap/internal/model/common/Type.java | 2 +- .../model/assignment/UpdateWrapper.ftl | 2 +- .../ap/internal/model/macro/CommonMacros.ftl | 2 +- .../ap/test/bugs/_3949/DateSource.java | 14 +++ .../test/bugs/_3949/Issue3949ClassMapper.java | 29 ++++++ .../bugs/_3949/Issue3949InterfaceMapper.java | 29 ++++++ .../ap/test/bugs/_3949/Issue3949Test.java | 94 +++++++++++++++++++ .../ap/test/bugs/_3949/ParentSource.java | 12 +++ .../ap/test/bugs/_3949/ParentTarget.java | 25 +++++ .../bugs/_3949/ParentTargetInterface.java | 14 +++ .../ap/test/bugs/_3949/StringSource.java | 12 +++ .../ap/test/bugs/_3949/TargetDate.java | 33 +++++++ .../test/bugs/_3949/TargetDateInterface.java | 18 ++++ .../ap/test/bugs/_3949/TargetString.java | 34 +++++++ .../bugs/_3949/TargetStringInterface.java | 18 ++++ 19 files changed, 399 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 76e56cd976..db9e012d78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -163,7 +163,8 @@ public Assignment build() { targetType, true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + false ); } else if ( method.isUpdateMethod() && !targetImmutable ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 622ba9cef0..c120f035b2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -13,6 +13,8 @@ import java.util.Set; import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; @@ -454,7 +456,8 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { targetType, !rhs.isSourceReferenceParameter(), nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName() ); } else { @@ -471,7 +474,10 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { isFieldAssignment(), includeSourceNullCheck, includeSourceNullCheck && nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT ); + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType + ); } } @@ -547,12 +553,33 @@ else if ( result.getSourceType().isStreamType() ) { isFieldAssignment(), true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType ); } return result; } + private boolean hasTwoOrMoreSettersWithName() { + Element enclosingClass = this.targetWriteAccessor.getElement().getEnclosingElement(); + if ( enclosingClass == null || !( ElementKind.CLASS.equals( enclosingClass.getKind() ) + || ElementKind.INTERFACE.equals( enclosingClass.getKind() ) ) ) { + return false; + } + String simpleWriteAccessorName = this.targetWriteAccessor.getSimpleName(); + boolean firstMatchFound = false; + for ( Accessor setter : ctx.getTypeFactory().getType( enclosingClass.asType() ).getSetters() ) { + if ( setter.getSimpleName().equals( simpleWriteAccessorName ) ) { + if ( firstMatchFound ) { + return true; + } + firstMatchFound = true; + } + } + return false; + } + private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType, Assignment rhs) { return new CollectionAssignmentBuilder() @@ -991,6 +1018,7 @@ public PropertyMapping build() { targetType, false, false, + false, false ); } else { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java index 7be948c857..94b031c622 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.model.assignment; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; @@ -22,19 +24,25 @@ public class SetterWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; + private final Type nullCastType; public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault) { + boolean setExplicitlyToDefault, + boolean mustCastForNull, + Type nullCastType) { super( rhs, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; + this.nullCastType = nullCastType; } public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment ) { @@ -43,6 +51,8 @@ public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fi this.includeSourceNullCheck = false; this.setExplicitlyToNull = false; this.setExplicitlyToDefault = false; + this.mustCastForNull = false; + this.nullCastType = null; } @Override @@ -59,6 +69,15 @@ public List getThrownTypes() { return result; } + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isSetExplicitlyToNull() && isMustCastForNull() ) { + imported.add( nullCastType ); + } + return imported; + } + public boolean isSetExplicitlyToNull() { return setExplicitlyToNull; } @@ -71,4 +90,7 @@ public boolean isIncludeSourceNullCheck() { return includeSourceNullCheck; } + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index ff5089d6c2..c3d3f9c446 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -26,6 +26,7 @@ public class UpdateWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; public UpdateWrapper( Assignment decoratedAssignment, List thrownTypesToExclude, @@ -34,7 +35,8 @@ public UpdateWrapper( Assignment decoratedAssignment, Type targetType, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault ) { + boolean setExplicitlyToDefault, + boolean mustCastForNull) { super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; @@ -42,6 +44,7 @@ public UpdateWrapper( Assignment decoratedAssignment, this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; } private static Type determineImplType(Assignment factoryMethod, Type targetType) { @@ -100,4 +103,8 @@ public boolean isSetExplicitlyToNull() { public boolean isSetExplicitlyToDefault() { return setExplicitlyToDefault; } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 3e81cd622d..6287403ba3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1061,7 +1061,7 @@ private TypeMirror boxed(TypeMirror possiblePrimitive) { * * @return an unmodifiable list of all setters */ - private List getSetters() { + public List getSetters() { if ( setters == null ) { setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) ); } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index d8ed4b53f6..933612f579 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -18,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 9c4ad26e03..f409fda793 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -45,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull!false>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java new file mode 100644 index 0000000000..e542c388d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class DateSource { + public LocalDate getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java new file mode 100644 index 0000000000..135aa79056 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949ClassMapper { + + Issue3949ClassMapper INSTANCE = Mappers.getMapper( Issue3949ClassMapper.class ); + + void overwriteDate(@MappingTarget TargetDate target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetString target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDate target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetString target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTarget target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java new file mode 100644 index 0000000000..c25041ad2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949InterfaceMapper { + + Issue3949InterfaceMapper INSTANCE = Mappers.getMapper( Issue3949InterfaceMapper.class ); + + void overwriteDate(@MappingTarget TargetDateInterface target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetStringInterface target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDateInterface target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetStringInterface target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTargetInterface target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java new file mode 100644 index 0000000000..b6abee819c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java @@ -0,0 +1,94 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests if overloaded targets are correctly cast when set to null + * + * @author hduelme + */ +@IssueKey("3949") +@WithClasses({ + ParentSource.class, + ParentTargetInterface.class, + ParentTarget.class, + StringSource.class, + TargetStringInterface.class, + TargetString.class, + DateSource.class, + TargetDateInterface.class, + TargetDate.class +}) +public class Issue3949Test { + + @ProcessorTest + @WithClasses({ + Issue3949ClassMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForClass() { + TargetDate shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetString shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTarget parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949ClassMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + Issue3949InterfaceMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForInterface() { + TargetDateInterface shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetStringInterface shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTargetInterface parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949InterfaceMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java new file mode 100644 index 0000000000..551501aa24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public class ParentSource { + public ParentSource getChild() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java new file mode 100644 index 0000000000..3d8ca3b51b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public class ParentTarget implements ParentTargetInterface { + private ParentTarget child; + + @Override + public ParentTarget getChild() { + return child; + } + + @Override + public void setChild(String child) { + throw new IllegalArgumentException(); + } + + @Override + public void setChild(ParentTarget child) { + this.child = child; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java new file mode 100644 index 0000000000..5311fb4c9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public interface ParentTargetInterface { + ParentTarget getChild(); + + void setChild(String child); + + void setChild(ParentTarget child); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java new file mode 100644 index 0000000000..11ba1419d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public class StringSource { + public String getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java new file mode 100644 index 0000000000..85b12a618d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetDate implements TargetDateInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public LocalDate getDate() { + return date; + } + + @Override + public String getString() { + return string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java new file mode 100644 index 0000000000..df28936914 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetDateInterface { + void setDate(LocalDate date); + + void setDate(String date); + + LocalDate getDate(); + + String getString(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java new file mode 100644 index 0000000000..b8cbbb2bbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetString implements TargetStringInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public String getDate() { + return string; + } + + @Override + public LocalDate getDateValue() { + return date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java new file mode 100644 index 0000000000..dd82231a51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetStringInterface { + void setDate(LocalDate date); + + void setDate(String date); + + String getDate(); + + LocalDate getDateValue(); +} From 6d61c3ab9ea577ef12c0ff2e77e723f946696791 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:34:11 +0100 Subject: [PATCH 170/214] #3972: Update maven compiler plugin (#4022) * Revert "Block plexus.snapshots repository in GitHub actions #3972" This reverts commit 9d75a48df5e40c1ed87d2f68d96fbb35807e3b91 and updates the maven compiler plugin to 3.15.0 --- .github/workflows/java-ea.yml | 3 --- .github/workflows/macos.yml | 3 --- .github/workflows/main.yml | 6 ------ .github/workflows/release.yml | 4 ---- .github/workflows/windows.yml | 3 --- parent/pom.xml | 2 +- 6 files changed, 1 insertion(+), 20 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index 70b81c40f1..fc3a4a0fb1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -17,8 +17,5 @@ jobs: with: website: jdk.java.net release: EA - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 98b4e156e0..833bb6ba39 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,8 +16,5 @@ jobs: with: distribution: 'zulu' java-version: 21 - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca66a22291..c4def2a89d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,9 +23,6 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' @@ -62,8 +59,5 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4feca94a0..666a74656d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,10 +26,6 @@ jobs: distribution: 'zulu' cache: maven - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - - name: Set release version id: version run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fc37af6a9d..bda38f8783 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,8 +16,5 @@ jobs: with: distribution: 'zulu' java-version: 21 - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw %MAVEN_ARGS% install diff --git a/parent/pom.xml b/parent/pom.xml index 88bfd5053c..c4ba63948d 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -413,7 +413,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 org.apache.maven.plugins From 0d3104908196b3ad22101dd5928ea5e6f4d85c88 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:35:47 +0100 Subject: [PATCH 171/214] Use multi-catch in generated code (#4021) --- .../model/assignment/Java8FunctionWrapper.ftl | 10 +++------- .../model/assignment/LocalVarWrapper.ftl | 10 +++------- .../ap/internal/model/macro/CommonMacros.ftl | 11 +++++++--- .../adder/SourceTargetMapperImpl.java | 20 ++++--------------- 4 files changed, 18 insertions(+), 33 deletions(-) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl index ce1fcdcd6c..4c3b6d4804 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl @@ -6,6 +6,7 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> <#assign sourceVarName><#if assignment.sourceLocalVarName?? >${assignment.sourceLocalVarName}<#else>${assignment.sourceReference} <#if (thrownTypes?size == 0) > <#compress> @@ -17,14 +18,9 @@ <#else> <#compress> ${sourceVarName} -> { - try { + <@lib.handleExceptions> return <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl index 97ea856ece..163a52896a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl @@ -6,18 +6,14 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.LocalVarWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> <#if (thrownTypes?size == 0) > <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName} = <@_assignment/>; <#else> <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName}; - try { + <@lib.handleExceptions> ${ext.targetWriteAccessorName} = <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + <#macro _assignment> <@includeModel object=assignment diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index f409fda793..e857a807af 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -104,11 +104,16 @@ try { <#nested> } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { + <@compress single_line=true>catch ( + <#list thrownTypes as exceptionType> + <#if exceptionType_index > 0> | + <@includeModel object=exceptionType/> + + e ) { + + throw new RuntimeException( e ); } - <#-- diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java index 823fbb1908..52d661c3f7 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java @@ -59,10 +59,7 @@ public Source toSource(Target source) { try { source1.setPets( petMapper.toSourcePets( source.getPets() ) ); } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } @@ -82,10 +79,7 @@ public void toExistingTarget(Source source, Target target) { } } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } } @@ -161,10 +155,7 @@ public TargetViaTargetType toTargetViaTargetType(Source source) { } } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } @@ -184,10 +175,7 @@ public Target fromSingleElementSource(SingleElementSource source) { target.addPet( petMapper.toPet( source.getPet() ) ); } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } From 2d51aa0695272b1198d6c4cbe3458ca3ecbdeb9f Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:39:41 +0100 Subject: [PATCH 172/214] Refactor TypeFactory.getTypeParameters (#4020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves readability and avoids unnecessary overhead: * Stores `typeArguments` in a local variable (since calling `getTypeArguments()` is not cached in eclipse compiler) * Replace if/else with a ternary expression * Avoids repeated `getType(...)` calls --- .../ap/internal/model/common/TypeFactory.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 eb2c1dee07..606bbce69d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -523,15 +523,12 @@ private List getTypeParameters(TypeMirror mirror, boolean isImplementation } DeclaredType declaredType = (DeclaredType) mirror; - List typeParameters = new ArrayList<>( declaredType.getTypeArguments().size() ); + List typeArguments = declaredType.getTypeArguments(); + List typeParameters = new ArrayList<>( typeArguments.size() ); - for ( TypeMirror typeParameter : declaredType.getTypeArguments() ) { - if ( isImplementationType ) { - typeParameters.add( getType( typeParameter ).getTypeBound() ); - } - else { - typeParameters.add( getType( typeParameter ) ); - } + for ( TypeMirror typeParameter : typeArguments) { + Type type = getType( typeParameter ); + typeParameters.add( isImplementationType ? type.getTypeBound() : type ); } return typeParameters; From 9aea5a8e93c28fb824b59bd883b5c88bcb6c2e6d Mon Sep 17 00:00:00 2001 From: mk868 Date: Fri, 27 Mar 2026 07:44:34 +0100 Subject: [PATCH 173/214] #4018 Add URI to String built-in conversions (#4019) --- .../chapter-5-data-type-conversions.asciidoc | 3 + .../internal/conversion/ConversionUtils.java | 12 ++++ .../ap/internal/conversion/Conversions.java | 10 ++-- .../conversion/URIToStringConversion.java | 37 ++++++++++++ .../conversion/uri/URIConversionTest.java | 56 +++++++++++++++++++ .../ap/test/conversion/uri/URIMapper.java | 19 +++++++ .../ap/test/conversion/uri/URISource.java | 33 +++++++++++ .../ap/test/conversion/uri/URITarget.java | 30 ++++++++++ 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java 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 9da7ff75c1..d95e6c7a62 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -127,6 +127,9 @@ public interface CarMapper { * Between `String` and `StringBuilder` +* Between `java.net.URI` and `String`. +** When converting from a `String`, the value needs to be a valid https://datatracker.ietf.org/doc/html/rfc2396[RFC 2396 URI] otherwise an `IllegalArgumentException` is thrown. + * Between `java.net.URL` and `String`. ** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 96960c4a11..ee052b5405 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; @@ -269,6 +270,17 @@ public static String uuid(ConversionContext conversionContext) { return typeReferenceName( conversionContext, UUID.class ); } + /** + * Name for {@link java.net.URI}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String uri(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, URI.class ); + } + /** * Name for {@link java.net.URL}. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 076cd2f42e..7b2f8702b1 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; @@ -201,7 +202,7 @@ public Conversions(TypeFactory typeFactory) { register( UUID.class, String.class, new UUIDToStringConversion() ); register( Locale.class, String.class, new LocaleToStringConversion() ); - registerURLConversion(); + registerJavaNetConversions(); } private void registerJodaConversions() { @@ -306,13 +307,14 @@ private void registerBigDecimalConversion(Class targetType) { } } - private void registerURLConversion() { - if ( isJavaURLAvailable() ) { + private void registerJavaNetConversions() { + if ( isJavaNetAvailable() ) { + register( URI.class, String.class, new URIToStringConversion() ); register( URL.class, String.class, new URLToStringConversion() ); } } - private boolean isJavaURLAvailable() { + private boolean isJavaNetAvailable() { return typeFactory.isTypeAvailable( "java.net.URL" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java new file mode 100644 index 0000000000..c25383ae05 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.net.URI; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.uri; +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link java.net.URI} and {@link String}. + * + * @author Maciej Kucharczyk + */ +public class URIToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return uri( conversionContext ) + ".create( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URI.class ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java new file mode 100644 index 0000000000..d7d3ef4142 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java @@ -0,0 +1,56 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import java.net.URI; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.net.URI} and String. + * + * @author Maciej Kucharczyk + */ +@IssueKey("4018") +@WithClasses({ URISource.class, URITarget.class, URIMapper.class }) +public class URIConversionTest { + + @ProcessorTest + public void shouldApplyURIConversion() { + URISource source = new URISource(); + source.setUriA( URI.create( "https://mapstruct.org/" ) ); + + URITarget target = URIMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getUriA() ).isEqualTo( source.getUriA().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseURIConversion() { + URITarget target = new URITarget(); + target.setUriA( "https://mapstruct.org/" ); + + URISource source = URIMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getUriA() ).isEqualTo( URI.create( target.getUriA() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidURIString() { + URITarget target = new URITarget(); + target.setInvalidURI( "ht!tp://example.com" ); + + assertThatThrownBy( () -> URIMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( IllegalArgumentException.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java new file mode 100644 index 0000000000..cac94a4efa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface URIMapper { + + URIMapper INSTANCE = Mappers.getMapper( URIMapper.class ); + + URITarget sourceToTarget(URISource source); + + URISource targetToSource(URITarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java new file mode 100644 index 0000000000..502169fccb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import java.net.URI; + +/** + * @author Maciej Kucharczyk + */ +public class URISource { + private URI uriA; + + private URI invalidURI; + + public URI getUriA() { + return uriA; + } + + public void setUriA(URI uriA) { + this.uriA = uriA; + } + + public URI getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(URI invalidURI) { + this.invalidURI = invalidURI; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java new file mode 100644 index 0000000000..b221392127 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +/** + * @author Maciej Kucharczyk + */ +public class URITarget { + private String uriA; + private String invalidURI; + + public String getUriA() { + return uriA; + } + + public void setUriA(String uriA) { + this.uriA = uriA; + } + + public String getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(String invalidURI) { + this.invalidURI = invalidURI; + } +} From 6a567516be5e2e38a7314d892b08468c7823c6db Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:06:09 +0100 Subject: [PATCH 174/214] Upgrade integration tests to junit5 (#4023) --- .../GradleIncrementalCompilationTest.java | 50 +++++++++---------- .../itest/auto/value/AutoValueMapperTest.java | 8 +-- .../src/test/resources/cdiTest/pom.xml | 8 +-- .../itest/cdi/CdiBasedMapperTest.java | 14 +++--- .../test/java/DefaultPackageTest.java | 6 +-- .../itest/textBlocks/TextBlocksTest.java | 6 +-- .../itest/externalbeanjar/ConversionTest.java | 6 +-- .../generator/pom.xml | 4 +- .../usage/pom.xml | 4 +- .../usage/FaultyAstModifyingTestTest.java | 6 +-- .../freebuilder/FreeBuilderMapperTest.java | 8 +-- .../mapstruct/itest/simple/AnimalTest.java | 8 +-- .../immutables/ImmutablesMapperTest.java | 8 +-- .../jaxb/JakartaJaxbBasedMapperTest.java | 8 +-- .../ap/test/bugs/_603/Issue603Test.java | 7 ++- .../ap/test/bugs/_636/Issue636Test.java | 6 +-- .../itest/java8/Java8MapperTest.java | 6 +-- .../itest/jaxb/JaxbBasedMapperTest.java | 8 +-- .../src/test/resources/jsr330Test/pom.xml | 4 ++ .../itest/jsr330/Jsr330BasedMapperTest.java | 16 +++--- .../itest/kotlin/data/KotlinDataTest.java | 8 +-- .../itest/lombok/LombokMapperTest.java | 8 +-- .../itest/lombok/LombokMapperTest.java | 8 +-- .../mapstruct/itest/modules/ModulesTest.java | 6 +-- .../mapstruct/itest/naming/NamingTest.java | 6 +-- integrationtest/src/test/resources/pom.xml | 5 +- .../itest/protobuf/ProtobufMapperTest.java | 8 +-- .../itest/records/module2/RecordsTest.java | 6 +-- .../itest/records/mapper/RecordsTest.java | 8 +-- .../mapstruct/itest/records/RecordsTest.java | 22 ++++---- .../records/nested/NestedRecordsTest.java | 6 +-- .../sealedsubclass/SealedSubclassTest.java | 10 ++-- .../itest/simple/ConversionTest.java | 18 +++---- .../src/test/resources/springTest/pom.xml | 4 ++ .../itest/spring/SpringBasedMapperTest.java | 16 +++--- .../superTypeGenerationTest/generator/pom.xml | 4 +- .../superTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedBasesTest.java | 8 +-- .../generator/pom.xml | 4 +- .../targetTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedTargetTypeTest.java | 8 +-- .../usesTypeGenerationTest/generator/pom.xml | 4 +- .../usesTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedUsesTypeTest.java | 6 +-- parent/pom.xml | 7 +-- 45 files changed, 193 insertions(+), 190 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java index 3d496906c0..a12af7690f 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java @@ -13,47 +13,49 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.runners.Parameterized.Parameters; +import static org.assertj.core.api.Assertions.assertThat; import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE; -import static org.hamcrest.core.StringContains.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; /** *

      This is supposed to be run from the mapstruct root project folder. * Otherwise, use -Dmapstruct_root=path_to_project. */ @DisabledForJreRange(min = JRE.JAVA_11) -public class GradleIncrementalCompilationTest { +class GradleIncrementalCompilationTest { private static Path rootPath; - private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; - private static String compileTaskName = "compileJava"; + private static final String PROJECT_DIR = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; + private static final String COMPILE_TASK_NAME = "compileJava"; @TempDir - File testBuildDir; + private File testBuildDir; @TempDir - File testProjectDir; + private File testProjectDir; private GradleRunner runner; private File sourceDirectory; private List compileArgs; // Gradle compile task arguments - @Parameters(name = "Gradle {0}") - public static List gradleVersions() { - return Arrays.asList( "5.0", "6.0" ); + static Stream gradleVersions() { + return Stream.of( + Arguments.of( Named.of( "Gradle 5.0", "5.0" ) ), + Arguments.of( Named.of( "Gradle 6.0", "6.0" ) ) ); } private void replaceInFile(File file, CharSequence target, CharSequence replacement) throws IOException { @@ -68,15 +70,13 @@ private GradleRunner getRunner(String... additionalArguments) { } private void assertCompileOutcome(BuildResult result, TaskOutcome outcome) { - assertEquals( outcome, result.task( ":" + compileTaskName ).getOutcome() ); + assertEquals( outcome, result.task( ":" + COMPILE_TASK_NAME ).getOutcome() ); } private void assertRecompiled(BuildResult result, int recompiledCount) { assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE ); - assertThat( - result.getOutput(), - containsString( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ) - ); + assertThat( result.getOutput() ) + .contains( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ); } private List buildCompileArgs() { @@ -85,11 +85,11 @@ private List buildCompileArgs() { // Inject the path to the folder containing the mapstruct-processor JAR String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString(); - return Arrays.asList( compileTaskName, buildDirPropertyArg, jarDirectoryArg ); + return Arrays.asList( COMPILE_TASK_NAME, buildDirPropertyArg, jarDirectoryArg ); } @BeforeAll - public static void setupClass() throws Exception { + static void setupClass() { rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath(); } @@ -102,7 +102,7 @@ public void setup(String gradleVersion) throws IOException { testProjectDir.mkdirs(); } // Copy test project files to the temp dir - Path gradleProjectPath = rootPath.resolve( projectDir ); + Path gradleProjectPath = rootPath.resolve( PROJECT_DIR ); FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir ); compileArgs = buildCompileArgs(); sourceDirectory = new File( testProjectDir, "src/main/java" ); @@ -111,7 +111,7 @@ public void setup(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testBuildSucceeds(String gradleVersion) throws IOException { + void testBuildSucceeds(String gradleVersion) throws IOException { setup( gradleVersion ); // Make sure the test build setup actually compiles BuildResult buildResult = getRunner().build(); @@ -120,7 +120,7 @@ public void testBuildSucceeds(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testUpToDate(String gradleVersion) throws IOException { + void testUpToDate(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); BuildResult secondBuildResult = getRunner().build(); @@ -129,7 +129,7 @@ public void testUpToDate(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeConstant(String gradleVersion) throws IOException { + void testChangeConstant(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); // Change return value in class Target @@ -143,7 +143,7 @@ public void testChangeConstant(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeTargetField(String gradleVersion) throws IOException { + void testChangeTargetField(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); // Change target field in mapper interface @@ -157,7 +157,7 @@ public void testChangeTargetField(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeUnrelatedFile(String gradleVersion) throws IOException { + void testChangeUnrelatedFile(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" ); diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java index 8f640780f3..4ce5530a5d 100644 --- a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.auto.value; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class AutoValueMapperTest { +class AutoValueMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() .age( 33 ) .name( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml index 0b3c394e82..cb00fbaa01 100644 --- a/integrationtest/src/test/resources/cdiTest/pom.xml +++ b/integrationtest/src/test/resources/cdiTest/pom.xml @@ -30,8 +30,8 @@ javax.inject - org.jboss.arquillian.junit - arquillian-junit-container + org.jboss.arquillian.junit5 + arquillian-junit5-container test @@ -45,8 +45,8 @@ test - org.jboss.arquillian.junit - arquillian-junit-core + org.jboss.arquillian.junit5 + arquillian-junit5-core test diff --git a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java index 0c343fce27..4ede2eb78a 100644 --- a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java +++ b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java @@ -10,12 +10,12 @@ import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.cdi.other.DateMapper; /** @@ -23,8 +23,8 @@ * * @author Gunnar Morling */ -@RunWith( Arquillian.class ) -public class CdiBasedMapperTest { +@ExtendWith( ArquillianExtension.class ) +class CdiBasedMapperTest { @Inject private SourceTargetMapper mapper; @@ -45,7 +45,7 @@ public static JavaArchive createDeployment() { } @Test - public void shouldCreateCdiBasedMapper() { + void shouldCreateCdiBasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -56,7 +56,7 @@ public void shouldCreateCdiBasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java index ddc834cc8e..c3f84b6ab6 100644 --- a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java +++ b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java @@ -4,7 +4,7 @@ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -12,10 +12,10 @@ * @author Filip Hrisafov */ -public class DefaultPackageTest { +class DefaultPackageTest { @Test - public void shouldWorkCorrectlyInDefaultPackage() { + void shouldWorkCorrectlyInDefaultPackage() { DefaultPackageObject.CarDto carDto = DefaultPackageObject.CarMapper.INSTANCE.carToCarDto( new DefaultPackageObject.Car( "Morris", diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java index 42ed70b18e..504a961437 100644 --- a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java @@ -9,12 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class TextBlocksTest { +class TextBlocksTest { @Test - public void textBlockExpressionShouldWork() { + void textBlockExpressionShouldWork() { Car car = new Car(); car.setWheelPosition( new WheelPosition( "left" ) ); diff --git a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java index 8a18e9e4fc..af10a63428 100644 --- a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java +++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java @@ -8,15 +8,15 @@ import java.math.BigDecimal; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.externalbeanjar.Source; import org.mapstruct.itest.externalbeanjar.Issue1121Mapper; import org.mapstruct.itest.externalbeanjar.Target; -public class ConversionTest { +class ConversionTest { @Test - public void shouldApplyConversions() { + void shouldApplyConversions() { Source source = new Source(); source.setBigDecimal( new BigDecimal( "42" ) ); diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml index 08390085dd..60b73b856c 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml @@ -27,8 +27,8 @@ provided - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml index 90c95aa83b..976664f88d 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java index 06bfa3c533..ed6a5c0d89 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.faultyAstModifyingProcessor.usage; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Filip Hrisafov */ -public class FaultyAstModifyingTestTest { +class FaultyAstModifyingTestTest { @Test - public void testMapping() { + void testMapping() { Order order = new Order(); order.setItem( "my item" ); diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java index 5047451798..3b1caebf9c 100644 --- a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.freebuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class FreeBuilderMapperTest { +class FreeBuilderMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() .setAge( 33 ) .setName( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java index a180b4df52..14dd576fe0 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java +++ b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.ap.test.ignore.AnimalMapper; import org.mapstruct.ap.test.ignore.Animal; @@ -18,10 +18,10 @@ * * @author Gunnar Morling */ -public class AnimalTest { +class AnimalTest { @Test - public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { Animal animal = new Animal( "Bruno", 100, 23, "black" ); AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); @@ -34,7 +34,7 @@ public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { } @Test - public void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() { + void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() { AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23, "black" ); Animal animal = AnimalMapper.INSTANCE.animalDtoToAnimal( animalDto ); diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java index 421954fc90..9990f4278c 100644 --- a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.immutables; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class ImmutablesMapperTest { +class ImmutablesMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( ImmutablePerson.builder() .age( 33 ) .name( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java index b81c946d9c..afd5ba0289 100644 --- a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java @@ -18,7 +18,7 @@ import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory; import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType; import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType; @@ -28,10 +28,10 @@ * * @author Iaroslav Bogdanchikov */ -public class JakartaJaxbBasedMapperTest { +class JakartaJaxbBasedMapperTest { @Test - public void shouldMapJakartaJaxb() throws ParseException, JAXBException { + void shouldMapJakartaJaxb() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; @@ -81,7 +81,7 @@ public void shouldMapJakartaJaxb() throws ParseException, JAXBException { } @Test - public void underscores() throws ParseException, JAXBException { + void underscores() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java index c732b96f22..efe68a48ee 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java @@ -5,15 +5,14 @@ */ package org.mapstruct.ap.test.bugs._603; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class Issue603Test { +class Issue603Test { @Test - public void shouldMapDataFromJava8Interface() { + void shouldMapDataFromJava8Interface() { final Source source = new Source(); diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java index 99b7a515eb..837eb0b53a 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java @@ -5,14 +5,14 @@ */ package org.mapstruct.ap.test.bugs._636; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class Issue636Test { +class Issue636Test { @Test - public void shouldMapDataFromJava8Interface() { + void shouldMapDataFromJava8Interface() { final long idFoo = 123; final String idBar = "Bar456"; diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java index 556dd2f1e4..3f88889380 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java @@ -7,17 +7,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test for generation of Java8 based mapper implementations. * * @author Christian Schuster */ -public class Java8MapperTest { +class Java8MapperTest { @Test - public void shouldMapWithRepeatedMappingAnnotation() { + void shouldMapWithRepeatedMappingAnnotation() { Java8Mapper mapper = Java8Mapper.INSTANCE; Source source = new Source(); diff --git a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java index f54658ba06..4c81dc91d2 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java +++ b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java @@ -18,7 +18,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory; import org.mapstruct.itest.jaxb.xsd.test1.OrderType; import org.mapstruct.itest.jaxb.xsd.underscores.SubType; @@ -28,9 +28,9 @@ * * @author Sjaak Derksen */ -public class JaxbBasedMapperTest { +class JaxbBasedMapperTest { @Test - public void shouldMapJaxb() throws ParseException, JAXBException { + void shouldMapJaxb() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; @@ -80,7 +80,7 @@ public void shouldMapJaxb() throws ParseException, JAXBException { } @Test - public void underscores() throws ParseException, JAXBException { + void underscores() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; diff --git a/integrationtest/src/test/resources/jsr330Test/pom.xml b/integrationtest/src/test/resources/jsr330Test/pom.xml index 9a7dbcbc8a..e1cf959a90 100644 --- a/integrationtest/src/test/resources/jsr330Test/pom.xml +++ b/integrationtest/src/test/resources/jsr330Test/pom.xml @@ -18,6 +18,10 @@ jsr330Test jar + + + ${org.junit6.jupiter.version} + diff --git a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java index 525930f0ec..6cf9043472 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java +++ b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java @@ -8,13 +8,13 @@ import jakarta.inject.Inject; import jakarta.inject.Named; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.jsr330.Jsr330BasedMapperTest.SpringTestConfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -24,8 +24,8 @@ * @author Andreas Gudian */ @ContextConfiguration(classes = SpringTestConfig.class) -@RunWith(SpringJUnit4ClassRunner.class) -public class Jsr330BasedMapperTest { +@ExtendWith(SpringExtension.class) +class Jsr330BasedMapperTest { @Configuration @ComponentScan(basePackageClasses = Jsr330BasedMapperTest.class) public static class SpringTestConfig { @@ -43,7 +43,7 @@ public static class SpringTestConfig { private SecondDecoratedSourceTargetMapper secondDecoratedMapper; @Test - public void shouldInjectJsr330BasedMapper() { + void shouldInjectJsr330BasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -54,7 +54,7 @@ public void shouldInjectJsr330BasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -71,7 +71,7 @@ public void shouldInjectDecorator() { } @Test - public void shouldInjectSecondDecorator() { + void shouldInjectSecondDecorator() { Source source = new Source(); Target target = secondDecoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java index 513f080a8b..e46b76f5f0 100644 --- a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java +++ b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.kotlin.data.CustomerDto; import org.mapstruct.itest.kotlin.data.CustomerEntity; import org.mapstruct.itest.kotlin.data.CustomerMapper; -public class KotlinDataTest { +class KotlinDataTest { @Test - public void shouldMapData() { + void shouldMapData() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -24,7 +24,7 @@ public void shouldMapData() { } @Test - public void shouldMapIntoData() { + void shouldMapIntoData() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java index 6053f294c3..060fb5a990 100644 --- a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.lombok; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; @@ -15,10 +15,10 @@ * * @author Eric Martineau */ -public class LombokMapperTest { +class LombokMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.foo() .age( 33 ) .name( "Bob" ) @@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java index f8b702896d..bdf8f46a57 100644 --- a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java +++ b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.lombok; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; @@ -15,10 +15,10 @@ * * @author Eric Martineau */ -public class LombokMapperTest { +class LombokMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( new Person( "Bob", 33, new Address( "Wild Drive" ) ) ); assertThat( personDto.getAge() ).isEqualTo( 33 ); assertThat( personDto.getName() ).isEqualTo( "Bob" ); @@ -27,7 +27,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java index 805662c806..aae996c842 100644 --- a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java +++ b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.modules.CustomerDto; import org.mapstruct.itest.modules.CustomerEntity; import org.mapstruct.itest.modules.CustomerMapper; -public class ModulesTest { +class ModulesTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerDto dto = new CustomerDto(); dto.setName( "Kermit" ); dto.setEmail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java index d3a45db26b..adc7aac7cb 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.naming.GolfPlayer; import org.mapstruct.itest.naming.GolfPlayerDto; import org.mapstruct.itest.naming.GolfPlayerMapper; @@ -17,10 +17,10 @@ * * @author Gunnar Morling */ -public class NamingTest { +class NamingTest { @Test - public void shouldApplyCustomNamingStrategy() { + void shouldApplyCustomNamingStrategy() { GolfPlayer player = new GolfPlayer() .withName( "Jared" ) .withHandicap( 9.2D ); diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index 3053b211e1..f4cd0b51de 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -26,6 +26,7 @@ ${mapstruct.version} + 6.0.3 @@ -135,8 +136,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java index ae3740848f..ac97eba8b4 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.protobuf; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Christian Bandowski */ -public class ProtobufMapperTest { +class ProtobufMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder() .setAge( 33 ) .setName( "Bob" ) @@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java index 5f7a99273a..4064e0fe3f 100644 --- a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java @@ -7,14 +7,14 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.module1.SourceRootRecord; import org.mapstruct.itest.records.module1.SourceNestedRecord; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMap() { + void shouldMap() { SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) ); TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source ); diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java index 2f274792b8..218fd82c6a 100644 --- a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.api.CustomerDto; import org.mapstruct.itest.records.mapper.CustomerEntity; import org.mapstruct.itest.records.mapper.CustomerMapper; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -24,7 +24,7 @@ public void shouldMapRecord() { } @Test - public void shouldMapIntoRecord() { + void shouldMapIntoRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java index 2f77e8d49f..2a510edafd 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -9,15 +9,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.CustomerDto; import org.mapstruct.itest.records.CustomerEntity; import org.mapstruct.itest.records.CustomerMapper; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -26,7 +26,7 @@ public void shouldMapRecord() { } @Test - public void shouldMapIntoRecord() { + void shouldMapIntoRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); @@ -39,7 +39,7 @@ public void shouldMapIntoRecord() { } @Test - public void shouldMapIntoGenericRecord() { + void shouldMapIntoGenericRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); @@ -51,7 +51,7 @@ public void shouldMapIntoGenericRecord() { } @Test - public void shouldMapIntoRecordWithList() { + void shouldMapIntoRecordWithList() { Car car = new Car(); car.setWheelPositions( Arrays.asList( new WheelPosition( "left" ) ) ); @@ -63,7 +63,7 @@ public void shouldMapIntoRecordWithList() { } @Test - public void shouldMapMemberRecord() { + void shouldMapMemberRecord() { MemberEntity member = MemberMapper.INSTANCE.fromRecord( new MemberDto( true, false ) ); assertThat( member ).isNotNull(); @@ -72,7 +72,7 @@ public void shouldMapMemberRecord() { } @Test - public void shouldMapIntoMemberRecord() { + void shouldMapIntoMemberRecord() { MemberEntity entity = new MemberEntity(); entity.setIsActive( false ); entity.setPremium( true ); @@ -80,12 +80,12 @@ public void shouldMapIntoMemberRecord() { MemberDto value = MemberMapper.INSTANCE.toRecord( entity ); assertThat( value ).isNotNull(); - assertThat( value.isActive() ).isEqualTo( false ); - assertThat( value.premium() ).isEqualTo( true ); + assertThat( value.isActive() ).isFalse(); + assertThat( value.premium() ).isTrue(); } @Test - public void shouldUseDefaultConstructor() { + void shouldUseDefaultConstructor() { Task entity = new Task( "some-id", 1000L ); TaskDto value = TaskMapper.INSTANCE.toRecord( entity ); diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java index c8ccaf1a65..c70937fa84 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java @@ -9,12 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class NestedRecordsTest { +class NestedRecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CareProvider source = new CareProvider( "kermit", new Address( "Sesame Street", "New York" ) ); CareProviderDto target = CareProviderMapper.INSTANCE.map( source ); diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java index 379341ff66..171b6519c6 100644 --- a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java @@ -7,12 +7,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class SealedSubclassTest { +class SealedSubclassTest { @Test - public void mappingIsDoneUsingSubclassMapping() { + void mappingIsDoneUsingSubclassMapping() { VehicleCollection vehicles = new VehicleCollection(); vehicles.getVehicles().add( new Car() ); vehicles.getVehicles().add( new Bike() ); @@ -28,7 +28,7 @@ public void mappingIsDoneUsingSubclassMapping() { } @Test - public void inverseMappingIsDoneUsingSubclassMapping() { + void inverseMappingIsDoneUsingSubclassMapping() { VehicleCollectionDto vehicles = new VehicleCollectionDto(); vehicles.getVehicles().add( new CarDto() ); vehicles.getVehicles().add( new BikeDto() ); @@ -44,7 +44,7 @@ public void inverseMappingIsDoneUsingSubclassMapping() { } @Test - public void subclassMappingInheritsInverseMapping() { + void subclassMappingInheritsInverseMapping() { VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); CarDto carDto = new CarDto(); carDto.setMaker( "BenZ" ); diff --git a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java index 2b6a0bc93b..69c94322bd 100644 --- a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java +++ b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.simple.Source; import org.mapstruct.itest.simple.SourceTargetMapper; import org.mapstruct.itest.simple.Target; -public class ConversionTest { +class ConversionTest { @Test - public void shouldApplyConversions() { + void shouldApplyConversions() { Source source = new Source(); source.setFoo( 42 ); source.setBar( 23L ); @@ -30,18 +30,18 @@ public void shouldApplyConversions() { } @Test - public void shouldHandleNulls() { + void shouldHandleNulls() { Source source = new Source(); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 1 ) ); - assertThat( target.getBar() ).isEqualTo( 0 ); + assertThat( target.getBar() ).isZero(); assertThat( target.getZip() ).isEqualTo( "0" ); } @Test - public void shouldApplyConversionsToMappedProperties() { + void shouldApplyConversionsToMappedProperties() { Source source = new Source(); source.setQax( 42 ); source.setBaz( 23L ); @@ -54,7 +54,7 @@ public void shouldApplyConversionsToMappedProperties() { } @Test - public void shouldApplyConversionsForReverseMapping() { + void shouldApplyConversionsForReverseMapping() { Target target = new Target(); target.setFoo( 42L ); target.setBar( 23 ); @@ -69,7 +69,7 @@ public void shouldApplyConversionsForReverseMapping() { } @Test - public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { + void shouldApplyConversionsToMappedPropertiesForReverseMapping() { Target target = new Target(); target.setQax( 42 ); target.setBaz( 23L ); @@ -82,7 +82,7 @@ public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { } @Test - public void shouldWorkWithAbstractClass() { + void shouldWorkWithAbstractClass() { Source source = new Source(); source.setFoo( 42 ); source.setBar( 23L ); diff --git a/integrationtest/src/test/resources/springTest/pom.xml b/integrationtest/src/test/resources/springTest/pom.xml index 3c1fb54552..e4210c0cba 100644 --- a/integrationtest/src/test/resources/springTest/pom.xml +++ b/integrationtest/src/test/resources/springTest/pom.xml @@ -18,6 +18,10 @@ springTest jar + + + ${org.junit6.jupiter.version} + diff --git a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java index a5b3bb0e65..c0f3de4ce2 100644 --- a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java +++ b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java @@ -5,14 +5,14 @@ */ package org.mapstruct.itest.spring; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.spring.SpringBasedMapperTest.SpringTestConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -22,8 +22,8 @@ * @author Andreas Gudian */ @ContextConfiguration(classes = SpringTestConfig.class) -@RunWith(SpringJUnit4ClassRunner.class) -public class SpringBasedMapperTest { +@ExtendWith(SpringExtension.class) +class SpringBasedMapperTest { @Configuration @ComponentScan(basePackageClasses = SpringBasedMapperTest.class) @@ -40,7 +40,7 @@ public static class SpringTestConfig { private SecondDecoratedSourceTargetMapper secondDecoratedMapper; @Test - public void shouldInjectSpringBasedMapper() { + void shouldInjectSpringBasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -51,7 +51,7 @@ public void shouldInjectSpringBasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -68,7 +68,7 @@ public void shouldInjectDecorator() { } @Test - public void shouldInjectSecondDecorator() { + void shouldInjectSecondDecorator() { Source source = new Source(); Target target = secondDecoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 5ab2d0d18f..f122047093 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index d1e1dd7dff..4c5e4611f0 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java index 4a012283d3..f34bebe36d 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.itest.supertypegeneration.usage; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.supertypegeneration.usage.Order; import org.mapstruct.itest.supertypegeneration.usage.OrderDto; import org.mapstruct.itest.supertypegeneration.usage.OrderMapper; @@ -18,10 +18,10 @@ * * @author Gunnar Morling */ -public class GeneratedBasesTest { +class GeneratedBasesTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); order.setBaseName2( "my base name 2" ); diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index 67df383a18..89bd9c2d2f 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index bd06b79a49..0726580592 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java index d9e3f8d789..d7fb6b00fa 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.itest.targettypegeneration.usage; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Integration test for using MapStruct with another annotation processor that generates the target type of a mapping @@ -15,10 +15,10 @@ * * @author Gunnar Morling */ -public class GeneratedTargetTypeTest { +class GeneratedTargetTypeTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml index 6ac3a01297..24d3436533 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml index 79696df47d..d7154c6246 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java index e715e66620..55ea60a0ad 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.usestypegeneration.usage; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Filip Hrisafov */ -public class GeneratedUsesTypeTest { +class GeneratedUsesTypeTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); diff --git a/parent/pom.xml b/parent/pom.xml index c4ba63948d..f3b0fe25f9 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -141,11 +141,6 @@ gem-processor ${org.mapstruct.gem.version} - - junit - junit - 4.13.1 - com.puppycrawl.tools checkstyle @@ -201,7 +196,7 @@ org.jboss.arquillian arquillian-bom - 1.6.0.Final + 1.7.2.Final import pom From f76f11c08defcc8bb507c9247e7c217087397634 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Mar 2026 15:20:33 +0100 Subject: [PATCH 175/214] Enforce spaces inside parentheses for control flow statements via checkstyle --- .../src/main/resources/build-config/checkstyle.xml | 2 +- core/src/main/java/org/mapstruct/factory/Mappers.java | 4 ++-- .../main/java/org/mapstruct/ap/MappingProcessor.java | 2 +- .../mapstruct/ap/internal/conversion/Conversions.java | 2 +- .../ap/internal/model/AbstractBaseBuilder.java | 4 ++-- .../mapstruct/ap/internal/model/BeanMappingMethod.java | 2 +- .../ap/internal/model/DefaultMapperReference.java | 2 +- .../java/org/mapstruct/ap/internal/model/Mapper.java | 6 +++--- .../ap/internal/model/MappingBuilderContext.java | 2 +- .../ap/internal/model/NestedPropertyMappingMethod.java | 2 +- .../ap/internal/model/ObjectFactoryMethodResolver.java | 2 +- .../mapstruct/ap/internal/model/PropertyMapping.java | 10 +++++----- ...etterWrapperForCollectionsAndMapsWithNullCheck.java | 2 +- .../internal/model/beanmapping/MappingReference.java | 8 ++++---- .../internal/model/beanmapping/MappingReferences.java | 2 +- .../ap/internal/model/common/BuilderType.java | 2 +- .../org/mapstruct/ap/internal/model/common/Type.java | 8 ++++---- .../ap/internal/model/common/TypeFactory.java | 6 +++--- .../ap/internal/model/source/MappingMethodOptions.java | 4 ++-- .../ap/internal/model/source/MethodMatcher.java | 2 +- .../source/selector/JakartaXmlElementDeclSelector.java | 4 ++-- .../source/selector/JavaxXmlElementDeclSelector.java | 4 ++-- .../selector/MostSpecificResultTypeSelector.java | 2 +- .../internal/model/source/selector/TypeSelector.java | 2 +- .../processor/creation/MappingResolverImpl.java | 6 +++--- .../java/org/mapstruct/ap/internal/util/Strings.java | 4 ++-- .../ap/test/bugs/_1482/TargetSourceMapper.java | 2 +- .../ap/test/bugs/_1801/Issue1801BuilderProvider.java | 2 +- .../org/mapstruct/ap/test/bugs/_2663/JsonNullable.java | 2 +- .../org/mapstruct/ap/test/bugs/_2663/Nullable.java | 2 +- .../ap/test/bugs/_3089/Issue3089BuilderProvider.java | 2 +- ...nalMethodWithSourcePropertyNameInContextMapper.java | 2 +- ...nalMethodWithTargetPropertyNameInContextMapper.java | 2 +- .../mapstruct/ap/test/nestedbeans/RecursionTest.java | 6 +++--- .../selection/ExternalHandWrittenMapper.java | 2 +- 35 files changed, 59 insertions(+), 59 deletions(-) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index 71a7d35899..14567bf067 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -141,7 +141,7 @@ - + diff --git a/core/src/main/java/org/mapstruct/factory/Mappers.java b/core/src/main/java/org/mapstruct/factory/Mappers.java index 05b616fe9a..d20bd59fa3 100644 --- a/core/src/main/java/org/mapstruct/factory/Mappers.java +++ b/core/src/main/java/org/mapstruct/factory/Mappers.java @@ -84,10 +84,10 @@ private static T doGetMapper(Class clazz, ClassLoader classLoader) throws return constructor.newInstance(); } - catch (ClassNotFoundException e) { + catch ( ClassNotFoundException e ) { return getMapperFromServiceLoader( clazz, classLoader ); } - catch ( InstantiationException | InvocationTargetException | IllegalAccessException e) { + catch ( InstantiationException | InvocationTargetException | IllegalAccessException e ) { throw new RuntimeException( e ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 32e65c7047..01a2a35955 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -264,7 +264,7 @@ private Set getMappers(final Set annotations try { Set annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation ); - for (Element mapperElement : annotatedMappers) { + for ( Element mapperElement : annotatedMappers ) { TypeElement mapperTypeElement = asTypeElement( mapperElement ); // on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 7b2f8702b1..c29ad5141e 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -355,7 +355,7 @@ public ConversionProvider getConversion(Type sourceType, Type targetType) { else if ( targetType.isOptionalType() ) { // Type -> Optional Type targetBaseType = targetType.getOptionalBaseType(); - if ( targetBaseType.equals( sourceType )) { + if ( targetBaseType.equals( sourceType ) ) { return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; } ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 584e0260bc..8859e542b8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -115,8 +115,8 @@ Assignment createForgedAssignment(SourceRHS source, ForgedMethod methodRef, Mapp if ( mappingMethod == null ) { return null; } - if (methodRef.getMappingReferences().isRestrictToDefinedMappings() || - !ctx.getMappingsToGenerate().contains( mappingMethod )) { + if ( methodRef.getMappingReferences().isRestrictToDefinedMappings() || + !ctx.getMappingsToGenerate().contains( mappingMethod ) ) { // If the mapping options are restricted only to the defined mappings, then use the mapping method. // See https://github.com/mapstruct/mapstruct/issues/1148 ctx.getMappingsToGenerate().add( mappingMethod ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index d67b550ad4..a73bce56e3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -629,7 +629,7 @@ private boolean isCorrectlySealed(Type mappingSourceType) { List unusedPermittedSubclasses = new ArrayList<>( mappingSourceType.getPermittedSubclasses() ); method.getOptions().getSubclassMappings().forEach( subClassOption -> { - for (Iterator iterator = unusedPermittedSubclasses.iterator(); + for ( Iterator iterator = unusedPermittedSubclasses.iterator(); iterator.hasNext(); ) { if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) { iterator.remove(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java index 6ac0c1c74d..2728f99323 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java @@ -36,7 +36,7 @@ private DefaultMapperReference(Type type, boolean isAnnotatedMapper, boolean isS public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedMapper, boolean isSingleton, TypeFactory typeFactory, List otherMapperReferences) { Set importTypes = Collections.asSet( type ); - if ( isAnnotatedMapper && !isSingleton) { + if ( isAnnotatedMapper && !isSingleton ) { importTypes.add( typeFactory.getType( "org.mapstruct.factory.Mappers" ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index cd092ca4f4..bcf7e840e5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -199,12 +199,12 @@ protected String getTemplateName() { * @return the flat name for the type element */ public static String getFlatName(TypeElement element) { - if (!(element.getEnclosingElement() instanceof TypeElement)) { + if ( !(element.getEnclosingElement() instanceof TypeElement) ) { return element.getSimpleName().toString(); } StringBuilder nameBuilder = new StringBuilder( element.getSimpleName().toString() ); - for (Element enclosing = element.getEnclosingElement(); enclosing instanceof TypeElement; enclosing = - enclosing.getEnclosingElement()) { + for ( Element enclosing = element.getEnclosingElement(); enclosing instanceof TypeElement; enclosing = + enclosing.getEnclosingElement() ) { nameBuilder.insert( 0, '$' ); nameBuilder.insert( 0, enclosing.getSimpleName().toString() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index 1ebf99ffeb..59c3efd141 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -228,7 +228,7 @@ public List getReservedNames() { nameSet.add( method.getName() ); } // add existing names - for ( SourceMethod method : sourceModel) { + for ( SourceMethod method : sourceModel ) { if ( method.isAbstract() ) { nameSet.add( method.getName() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index ed841ba5bc..6fbd5e6f82 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -179,7 +179,7 @@ public List getPropertyEntries() { @Override public Set getImportTypes() { Set types = super.getImportTypes(); - for ( SafePropertyEntry propertyEntry : safePropertyEntries) { + for ( SafePropertyEntry propertyEntry : safePropertyEntries ) { types.addAll( propertyEntry.getType().getImportTypes() ); if ( propertyEntry.getPresenceChecker() != null ) { types.addAll( propertyEntry.getPresenceChecker().getImportTypes() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 8b526b9177..88965dedca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -74,7 +74,7 @@ public static MethodReference getFactoryMethod( Method method, ctx ); - if (matchingFactoryMethods.isEmpty()) { + if ( matchingFactoryMethods.isEmpty() ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index c120f035b2..5defa65bf5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -314,7 +314,7 @@ private Assignment forge( ) { else if ( sourceType.isMapType() && targetType.isMapType() ) { assignment = forgeMapMapping( sourceType, targetType, rightHandSide ); } - else if ( sourceType.isMapType() && !targetType.isMapType()) { + else if ( sourceType.isMapType() && !targetType.isMapType() ) { assignment = forgeMapping( sourceType, targetType.withoutBounds(), rightHandSide ); } else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) @@ -427,7 +427,7 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { Assignment factory = ObjectFactoryMethodResolver .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx ); - if ( factory == null && targetBuilderType != null) { + if ( factory == null && targetBuilderType != null ) { // If there is no dedicated factory method and the target has a builder we will try to use that MethodReference builderFactoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( targetType, @@ -732,9 +732,9 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc String variableName = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); Type variableType = propertyEntry.getType(); - for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { + for ( int i = 1; i < sourceReference.getPropertyEntries().size(); i++ ) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); - if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { + if ( entry.getPresenceChecker() != null && entry.getReadAccessor() != null ) { if ( variableType.isOptionalType() ) { presenceChecks.add( new OptionalPresenceCheck( variableName, @@ -852,7 +852,7 @@ private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sour // because we are forging a Mapping for a method with multiple source parameters. // If the target type is enum, then we can't create an update method if ( !targetType.isEnumType() && ( method.isUpdateMethod() || forceUpdateMethod ) - && targetWriteAccessorType != AccessorType.ADDER) { + && targetWriteAccessorType != AccessorType.ADDER ) { parameters.add( Parameter.forForgedMappingTarget( targetType ) ); returnType = ctx.getTypeFactory().createVoidType(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java index a0c9f799e9..b9f1d2acb0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java @@ -57,7 +57,7 @@ public Set getImportTypes() { imported.add( typeFactory.getType( EnumSet.class ) ); } } - if (isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { + if ( isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { imported.addAll( getNullCheckLocalVarType().getImportTypes() ); } return imported; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index d013a77a7a..9baa84dc77 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -45,7 +45,7 @@ public TargetReference getTargetReference() { public MappingReference popTargetReference() { if ( targetReference != null ) { TargetReference newTargetReference = targetReference.pop(); - if (newTargetReference != null ) { + if ( newTargetReference != null ) { return new MappingReference(mapping, newTargetReference, sourceReference ); } } @@ -55,7 +55,7 @@ public MappingReference popTargetReference() { public MappingReference popSourceReference() { if ( sourceReference != null ) { SourceReference newSourceReference = sourceReference.pop(); - if (newSourceReference != null ) { + if ( newSourceReference != null ) { return new MappingReference(mapping, targetReference, newSourceReference ); } } @@ -76,7 +76,7 @@ public boolean equals(Object o) { return false; } - if (!Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { + if ( !Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { return false; } @@ -97,7 +97,7 @@ public boolean equals(Object o) { } - if (!Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { + if ( !Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { return false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java index ced945bb90..edc5b3157e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -109,7 +109,7 @@ public boolean hasNestedTargetReferences() { for ( MappingReference mappingRef : mappingReferences ) { TargetReference targetReference = mappingRef.getTargetReference(); - if ( targetReference.isNested()) { + if ( targetReference.isNested() ) { return true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index 9300f683ce..f399c46dc9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -108,7 +108,7 @@ else if ( typeUtils.isSameType( builder.getTypeMirror(), builderCreationOwner ) // When the builderCreationMethod is constructor, its return type is Void. In this case the // builder type should be the owner type. - if (builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR) { + if ( builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR ) { builder = owner; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6287403ba3..8f512c3ace 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -285,7 +285,7 @@ public String createReferenceName() { return name; } - if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null) { + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { return nameWithTopLevelTypeName; } @@ -1346,7 +1346,7 @@ public String describe() { private String getNameKeepingInnerClasses() { String packageNamePrefix = getPackageName() + "."; String fullyQualifiedName = getFullyQualifiedName(); - if (fullyQualifiedName.startsWith( packageNamePrefix ) ) { + if ( fullyQualifiedName.startsWith( packageNamePrefix ) ) { return fullyQualifiedName.substring( packageNamePrefix.length() ); } return fullyQualifiedName; @@ -1665,7 +1665,7 @@ else if ( typeToMatch.hasSuperBound() && parameterized.getSuperBound() != null return new ResolvedPair( typeFactory.getType( parameterized ), declared ); } } - else if (parameterized.getSuperBound() != null ) { + else if ( parameterized.getSuperBound() != null ) { ResolvedPair match = visit( parameterized.getSuperBound(), declared ); if ( match.match != null ) { return new ResolvedPair( typeFactory.getType( parameterized ), declared ); @@ -1897,7 +1897,7 @@ public List getPermittedSubclasses() { if ( kotlinMetadata != null ) { return kotlinMetadata.getPermittedSubclasses(); } - if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { + if ( SEALED_PERMITTED_SUBCLASSES_METHOD == null ) { return emptyList(); } try { 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 606bbce69d..37c38ab590 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 @@ -294,7 +294,7 @@ else if ( mirror.getKind() == TypeKind.ARRAY ) { packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); qualifiedName = componentTypeElement.getQualifiedName().toString() + arraySuffix; } - else if (componentTypeMirror.getKind().isPrimitive()) { + else if ( componentTypeMirror.getKind().isPrimitive() ) { // When the component type is primitive and is annotated with ElementType.TYPE_USE then // the typeMirror#toString returns (@CustomAnnotation :: byte) for the javac compiler name = NativeTypes.getName( componentTypeMirror.getKind() ) + builder.toString(); @@ -497,7 +497,7 @@ public List getThrownTypes(ExecutableType method) { } public List getThrownTypes(Accessor accessor) { - if (accessor.getAccessorType().isFieldAssignment()) { + if ( accessor.getAccessorType().isFieldAssignment() ) { return new ArrayList<>(); } Element element = accessor.getElement(); @@ -526,7 +526,7 @@ private List getTypeParameters(TypeMirror mirror, boolean isImplementation List typeArguments = declaredType.getTypeArguments(); List typeParameters = new ArrayList<>( typeArguments.size() ); - for ( TypeMirror typeParameter : typeArguments) { + for ( TypeMirror typeParameter : typeArguments ) { Type type = getType( typeParameter ); typeParameters.add( isImplementationType ? type.getTypeBound() : type ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 08ac1a683a..ea62ee3657 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -246,11 +246,11 @@ private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, Sourc } private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) { - if (templateMethod.getParameters().size() != sourceMethod.getParameters().size()) { + if ( templateMethod.getParameters().size() != sourceMethod.getParameters().size() ) { return false; } for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) { - if (!templateMethod.getParameters().get( i ).getType().equals( + if ( !templateMethod.getParameters().get( i ).getType().equals( sourceMethod.getParameters().get( i ).getType() ) ) { return false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 3d8bfea135..c62012472c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -165,7 +165,7 @@ private boolean lineUp() { // Represent result as map. Map resolvedPairs = new HashMap<>(); for ( TypeVarCandidate candidate : methodParCandidates.values() ) { - for ( Type.ResolvedPair pair : candidate.pairs) { + for ( Type.ResolvedPair pair : candidate.pairs ) { resolvedPairs.put( pair.getParameter(), pair.getMatch() ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java index df5cd848a5..d5595ef6b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java @@ -28,7 +28,7 @@ class JakartaXmlElementDeclSelector extends XmlElementDeclSelector { XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } @@ -39,7 +39,7 @@ XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementRefInfo getXmlElementRefInfo(Element element) { XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java index 1d02e97e90..912da3e696 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java @@ -28,7 +28,7 @@ class JavaxXmlElementDeclSelector extends XmlElementDeclSelector { XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } @@ -39,7 +39,7 @@ XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementRefInfo getXmlElementRefInfo(Element element) { XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 87ec3b4dc1..88e302eba0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -23,7 +23,7 @@ public List> getMatchingMethods(List