Date: Mon, 12 Sep 2022 22:15:22 +0530
Subject: [PATCH 031/320] Avoid unnecessary unboxing of Boolean (#3003)
---
.../java/org/mapstruct/ap/MappingProcessor.java | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
index 4f9e0fa384..2a4af558f2 100644
--- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
@@ -8,7 +8,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@@ -119,7 +118,7 @@ public class MappingProcessor extends AbstractProcessor {
*
* If the hierarchy of a mapper's source/target types is never completed (i.e. the missing super-types are not
* generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error
- * due to the inconsistent Java types used as source or target anyways.
+ * due to the inconsistent Java types used as source or target anyway.
*/
private Set deferredMappers = new HashSet<>();
@@ -142,15 +141,15 @@ private Options createOptions() {
String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY );
return new Options(
- Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
- Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ),
+ 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.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
- Boolean.valueOf( processingEnv.getOptions().get( DISABLE_BUILDERS ) ),
- Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) )
+ Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
+ Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ),
+ Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) )
);
}
@@ -352,7 +351,7 @@ private R process(ProcessorContext context, ModelElementProcessor
p
/**
* Retrieves all model element processors, ordered by their priority value
- * (with the method retrieval processor having the lowest priority value (1)
+ * (with the method retrieval processor having the lowest priority value (1))
* and the code generation processor the highest priority value.
*
* @return A list with all model element processors.
@@ -372,7 +371,7 @@ private
R process(ProcessorContext context, ModelElementProcessor
p
processors.add( processorIterator.next() );
}
- Collections.sort( processors, new ProcessorComparator() );
+ processors.sort( new ProcessorComparator() );
return processors;
}
From 8d670e7db72961a6a3470a8fdc85ec164007ca51 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 4 Sep 2022 09:39:27 +0200
Subject: [PATCH 032/320] #3008 Replace old issue template with new GitHub
issue form templates
---
.github/ISSUE_TEMPLATE/bug_report.yml | 45 ++++++++++++++++++++++
.github/ISSUE_TEMPLATE/config.yml | 16 ++++++++
.github/ISSUE_TEMPLATE/feature_request.yml | 43 +++++++++++++++++++++
ISSUE_TEMPLATE.md | 9 -----
4 files changed, 104 insertions(+), 9 deletions(-)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml
create mode 100644 .github/ISSUE_TEMPLATE/config.yml
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml
delete mode 100644 ISSUE_TEMPLATE.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000000..b6703e1800
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,45 @@
+name: Bug report
+description: Create a report and help us improve
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please fill in all required fields with as many details as possible.
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ description: |
+ Describe what you were expecting MapStruct to do
+ placeholder: |
+ Here you can also add the generated code that you would like MapStruct to generate
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual behavior
+ description: |
+ Describe what you observed MapStruct did instead
+ placeholder: |
+ Here you can also add the generated code that MapStruct generated
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce the problem
+ description: |
+ - Share your mapping configuration
+ - An [MCVE (Minimal Complete Verifiable Example)](https://stackoverflow.com/help/minimal-reproducible-example) can be helpful to provide a complete reproduction case
+ placeholder: |
+ Share your MapStruct configuration
+ validations:
+ required: true
+ - type: input
+ id: mapstruct-version
+ attributes:
+ label: MapStruct Version
+ description: |
+ Which MapStruct version did you use?
+ Note: While you can obviously continue using older versions of MapStruct, it may well be that your bug is already fixed. If you're using an older version, please also try to reproduce the bug in the latest version of MapStruct before reporting it.
+ placeholder: ex. MapStruct 1.5.2
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..5b7ced86a4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,16 @@
+contact_links:
+ - name: MapStruct Discussions
+ url: https://github.com/mapstruct/mapstruct/discussions
+ about: Please use the MapStruct GitHub Discussions for open ended discussions and to reach out to the community.
+ - name: Stack Overflow
+ url: https://stackoverflow.com/questions/tagged/mapstruct
+ about: For questions about how to use MapStruct, consider asking your question on Stack Overflow, tagged [mapstruct].
+ - name: Documentation
+ url: https://mapstruct.org/documentation/stable/reference/html/
+ about: The MapStruct reference documentation.
+ - name: Gitter Chat
+ url: https://gitter.im/mapstruct/mapstruct-users
+ about: For realtime communication with the MapStruct community, consider writing in our Gitter chat room.
+ - name: MapStruct Examples
+ url: https://github.com/mapstruct/mapstruct-examples
+ about: Some examples of what can be achieved with MapStruct. (contributions are always welcome)
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000000..191bba8345
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,43 @@
+name: Feature Request
+description: Suggest an idea
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Use case
+ description: |
+ Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
+ placeholder: Describe the use-case here
+ validations:
+ required: true
+ - type: textarea
+ id: solution
+ attributes:
+ label: Generated Code
+ description: |
+ Please describe the possible generated code you'd like to see in MapStruct generate.
+ Please note, it's not always easy to describe a good solution. Describing the use-case above is much more important to us.
+ placeholder: Describe the possible solution here.
+ validations:
+ required: false
+ - type: textarea
+ id: workarounds
+ attributes:
+ label: Possible workarounds
+ description: |
+ Please describe the possible workarounds you've implemented to work around the lacking functionality.
+ placeholder: Describe the possible workarounds here.
+ validations:
+ required: false
+ - type: input
+ id: mapstruct-version
+ attributes:
+ label: MapStruct Version
+ description: What MapStruct version and edition did you try?
+ placeholder: ex. MapStruct 1.5.2
+ validations:
+ required: false
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
deleted file mode 100644
index ecaf441514..0000000000
--- a/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,9 +0,0 @@
-- [ ] Is this an issue (and hence not a question)?
-
-If this is a question how to use MapStruct there are several resources available.
-- Our reference- and API [documentation](http://mapstruct.org/documentation/reference-guide/).
-- Our [examples](https://github.com/mapstruct/mapstruct-examples) repository (contributions always welcome)
-- Our [FAQ](http://mapstruct.org/faq/)
-- [StackOverflow](https://stackoverflow.com), tag MapStruct
-- [Gitter](https://gitter.im/mapstruct/mapstruct-users) (you usually get fast feedback)
-- Our [google group](https://groups.google.com/forum/#!forum/mapstruct-users)
\ No newline at end of file
From 811cd569bb80ed45f0da74cb603d9bfea774ad0c Mon Sep 17 00:00:00 2001
From: Johnny Lim
Date: Tue, 27 Sep 2022 02:02:01 +0900
Subject: [PATCH 033/320] Javadoc and documentation polishing (#3026)
---
core/src/main/java/org/mapstruct/EnumMapping.java | 2 +-
.../java/org/mapstruct/NullValueCheckStrategy.java | 2 +-
.../mapstruct/ap/internal/model/HelperMethod.java | 2 +-
.../internal/model/source/builtin/BuiltInMethod.java | 2 +-
.../java/org/mapstruct/ap/internal/util/Message.java | 2 +-
.../ap/internal/model/StreamMappingMethod.ftl | 2 +-
.../mapstruct/ap/test/bugs/_1719/Issue1719Test.java | 4 ++--
.../test/context/ContextParameterErroneousTest.java | 2 +-
readme.md | 12 ++++++------
9 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/core/src/main/java/org/mapstruct/EnumMapping.java b/core/src/main/java/org/mapstruct/EnumMapping.java
index ba3a5e0cb0..375f969b01 100644
--- a/core/src/main/java/org/mapstruct/EnumMapping.java
+++ b/core/src/main/java/org/mapstruct/EnumMapping.java
@@ -98,7 +98,7 @@
*
* {@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* suffix to the source enum
- * {@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the the given {@link #configuration()}
+ * {@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the given {@link #configuration()}
* from the end of the source enum
* {@link MappingConstants#PREFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* prefix to the source enum
diff --git a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
index 446b879d92..22dba7c58b 100644
--- a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
+++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
@@ -8,7 +8,7 @@
/**
* Strategy for dealing with null source values.
*
- * Note: This strategy is not in effect when the a specific source presence check method is defined
+ * Note: This strategy is not in effect when a specific source presence check method is defined
* in the service provider interface (SPI).
*
* Note : some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java
index 35b35aee73..6ff85c585f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java
@@ -176,7 +176,7 @@ public boolean equals(Object obj) {
*
* @param parameter source
* @param returnType target
- * @return {@code true}, iff the the type variables match
+ * @return {@code true}, iff the type variables match
*/
public boolean doTypeVarsMatch(Type parameter, Type returnType) {
return true;
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java
index f46b201576..6d41872159 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java
@@ -190,7 +190,7 @@ public boolean equals(Object obj) {
*
* @param parameter source
* @param returnType target
- * @return {@code true}, iff the the type variables match
+ * @return {@code true}, iff the type variables match
*/
public boolean doTypeVarsMatch(Type parameter, Type returnType) {
return true;
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 92951ad521..29600489fb 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
@@ -159,7 +159,7 @@ public enum Message {
RETRIEVAL_NO_INPUT_ARGS( "Can't generate mapping method with no input arguments." ),
RETRIEVAL_DUPLICATE_MAPPING_TARGETS( "Can't generate mapping method with more than one @MappingTarget parameter." ),
RETRIEVAL_VOID_MAPPING_METHOD( "Can't generate mapping method with return type void." ),
- RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the the return type." ),
+ RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the return type." ),
RETRIEVAL_ITERABLE_TO_NON_ITERABLE( "Can't generate mapping method from iterable type from java stdlib to non-iterable type." ),
RETRIEVAL_MAPPING_HAS_TARGET_TYPE_PARAMETER( "Can't generate mapping method that has a parameter annotated with @TargetType." ),
RETRIEVAL_NON_ITERABLE_TO_ITERABLE( "Can't generate mapping method from non-iterable type to iterable type from java stdlib." ),
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 7b5b44bd56..29977bdd5d 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
@@ -114,7 +114,7 @@
<#else>
<#-- Streams are immutable so we can't update them -->
<#if !existingInstanceMapping>
- <#--TODO fhr: after the the result is no longer the same instance, how does it affect the
+ <#--TODO fhr: after the result is no longer the same instance, how does it affect the
Before mapping methods. Does it even make sense to have before mapping on a stream? -->
<#if sourceParameter.type.arrayType>
<@returnLocalVarDefOrUpdate>Stream.of( ${sourceParameter.name} )<@streamMapSupplier />;@returnLocalVarDefOrUpdate>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java
index 182e1849be..7a8665e49f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java
@@ -23,8 +23,8 @@ public class Issue1719Test {
/**
* For adder methods MapStuct cannot generate an update method. MapStruct would cannot know how to remove objects
- * from the child-parent relation. It cannot even assume that the the collection can be cleared at forehand.
- * Therefore the only sensible choice is for MapStruct to create a create method for the target elements.
+ * from the child-parent relation. It cannot even assume that the collection can be cleared at forehand.
+ * Therefore, the only sensible choice is for MapStruct to create a create method for the target elements.
*/
@ProcessorTest
@WithClasses(Issue1719Mapper.class)
diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java
index 77fefe756e..46bb08cb70 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java
@@ -19,7 +19,7 @@
/**
* Tests the erroneous usage of the {@link Context} annotation in the following situations:
*
- * using the the same context parameter type twice in the same method
+ * using the same context parameter type twice in the same method
*
*
* @author Andreas Gudian
diff --git a/readme.md b/readme.md
index e5811060a6..e1ee8f9deb 100644
--- a/readme.md
+++ b/readme.md
@@ -114,7 +114,7 @@ plugins {
dependencies {
...
- compile 'org.mapstruct:mapstruct:1.5.2.Final'
+ implementation 'org.mapstruct:mapstruct:1.5.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' // if you are using mapstruct in test code
@@ -122,7 +122,7 @@ dependencies {
...
```
-If you don't work with a dependency management tool, you can obtain a distribution bundle from [SourceForge](https://sourceforge.net/projects/mapstruct/files/).
+If you don't work with a dependency management tool, you can obtain a distribution bundle from [Releases page](https://github.com/mapstruct/mapstruct/releases).
## Documentation and getting help
@@ -132,16 +132,16 @@ To learn more about MapStruct, refer to the [project homepage](http://mapstruct.
MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run
- mvn clean install
+ ./mvnw clean install
from the root of the project directory. To skip the distribution module, run
- mvn clean install -DskipDistribution=true
+ ./mvnw clean install -DskipDistribution=true
## Importing into IDE
-MapStruct uses the gem annotation processor to generate mapping gems for it's own annotations.
-Therefore for seamless integration within an IDE annotation processing needs to be enabled.
+MapStruct uses the gem annotation processor to generate mapping gems for its own annotations.
+Therefore, for seamless integration within an IDE annotation processing needs to be enabled.
### IntelliJ
From 73e8fd6152153ba8c2f76fc5e9a84b16d33f6a0b Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Tue, 27 Sep 2022 09:35:54 +0200
Subject: [PATCH 034/320] #2840, #2913, #2921: MethodMatcher should not match
widening methods
In the MethodMatcher we need to do a special check when the target type is primitive.
The reason for that is that a Long is assignable to a primitive double.
However, doing that means that information can be lost and thus we should not pick such methods.
When the target type is primitive, then a method will be matched if and only if boxed equivalent of the target type is assignable to the boxed equivalent of the candidate return type
---
.../internal/model/source/MethodMatcher.java | 11 +++
.../ap/test/bugs/_2840/Issue2840Mapper.java | 51 +++++++++++
.../ap/test/bugs/_2840/Issue2840Test.java | 31 +++++++
.../ap/test/bugs/_2913/Issue2913Mapper.java | 85 +++++++++++++++++++
.../ap/test/bugs/_2913/Issue2913Test.java | 35 ++++++++
.../ap/test/bugs/_2921/Issue2921Mapper.java | 49 +++++++++++
.../ap/test/bugs/_2921/Issue2921Test.java | 28 ++++++
7 files changed, 290 insertions(+)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java
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 baa3d7eac5..42c26f09a0 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
@@ -82,6 +82,17 @@ boolean matches(List sourceTypes, Type targetType) {
// (the relation target / target type, target type being a class)
if ( !analyser.candidateReturnType.isVoid() ) {
+ if ( targetType.isPrimitive() ) {
+ // If the target type is primitive
+ // then we are going to check if its boxed equivalent
+ // is assignable to the candidate return type
+ // This is done because primitives can be assigned from their own narrower counterparts
+ // directly without any casting.
+ // e.g. a Long is assignable to a primitive double
+ // However, in order not to lose information we are not going to allow this
+ return targetType.getBoxedEquivalent()
+ .isAssignableTo( analyser.candidateReturnType.getBoxedEquivalent() );
+ }
if ( !( analyser.candidateReturnType.isAssignableTo( targetType ) ) ) {
return false;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java
new file mode 100644
index 0000000000..d2baac8cac
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2840;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue2840Mapper {
+
+ Issue2840Mapper INSTANCE =
+ Mappers.getMapper( Issue2840Mapper.class );
+
+ Issue2840Mapper.Target map(Short shortValue, Integer intValue);
+
+ default int toInt(Number number) {
+ return number.intValue() + 5;
+ }
+
+ default short toShort(Number number) {
+ return (short) (number.shortValue() + 10);
+ }
+
+ class Target {
+
+ private int intValue;
+ private short shortValue;
+
+ public int getIntValue() {
+ return intValue;
+ }
+
+ public void setIntValue(int intValue) {
+ this.intValue = intValue;
+ }
+
+ public short getShortValue() {
+ return shortValue;
+ }
+
+ public void setShortValue(short shortValue) {
+ this.shortValue = shortValue;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java
new file mode 100644
index 0000000000..1c6b19306f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2840;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2840")
+@WithClasses({
+ Issue2840Mapper.class,
+})
+class Issue2840Test {
+
+ @ProcessorTest
+ void shouldUseMethodWithMostSpecificReturnType() {
+ Issue2840Mapper.Target target = Issue2840Mapper.INSTANCE.map( (short) 10, 50 );
+
+ assertThat( target.getShortValue() ).isEqualTo( (short) 20 );
+ assertThat( target.getIntValue() ).isEqualTo( 55 );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java
new file mode 100644
index 0000000000..095f5457fd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2913;
+
+import java.math.BigDecimal;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue2913Mapper {
+
+ Issue2913Mapper INSTANCE = Mappers.getMapper( Issue2913Mapper.class );
+
+ @Mapping(target = "doublePrimitiveValue", source = "rounding")
+ @Mapping(target = "doubleValue", source = "rounding")
+ @Mapping(target = "longPrimitiveValue", source = "rounding")
+ @Mapping(target = "longValue", source = "rounding")
+ Target map(Source source);
+
+ default Long mapAmount(BigDecimal amount) {
+ return amount != null ? amount.movePointRight( 2 ).longValue() : null;
+ }
+
+ class Target {
+
+ private double doublePrimitiveValue;
+ private Double doubleValue;
+ private long longPrimitiveValue;
+ private Long longValue;
+
+ public double getDoublePrimitiveValue() {
+ return doublePrimitiveValue;
+ }
+
+ public void setDoublePrimitiveValue(double doublePrimitiveValue) {
+ this.doublePrimitiveValue = doublePrimitiveValue;
+ }
+
+ public Double getDoubleValue() {
+ return doubleValue;
+ }
+
+ public void setDoubleValue(Double doubleValue) {
+ this.doubleValue = doubleValue;
+ }
+
+ public long getLongPrimitiveValue() {
+ return longPrimitiveValue;
+ }
+
+ public void setLongPrimitiveValue(long longPrimitiveValue) {
+ this.longPrimitiveValue = longPrimitiveValue;
+ }
+
+ public Long getLongValue() {
+ return longValue;
+ }
+
+ public void setLongValue(Long longValue) {
+ this.longValue = longValue;
+ }
+ }
+
+ class Source {
+
+ private final BigDecimal rounding;
+
+ public Source(BigDecimal rounding) {
+ this.rounding = rounding;
+ }
+
+ public BigDecimal getRounding() {
+ return rounding;
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java
new file mode 100644
index 0000000000..7257c9ab7e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2913;
+
+import java.math.BigDecimal;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2913")
+@WithClasses({
+ Issue2913Mapper.class,
+})
+class Issue2913Test {
+
+ @ProcessorTest
+ void shouldNotWidenWithUserDefinedMethods() {
+ Issue2913Mapper.Source source = new Issue2913Mapper.Source( BigDecimal.valueOf( 10.543 ) );
+ Issue2913Mapper.Target target = Issue2913Mapper.INSTANCE.map( source );
+
+ assertThat( target.getDoubleValue() ).isEqualTo( 10.543 );
+ assertThat( target.getDoublePrimitiveValue() ).isEqualTo( 10.543 );
+ assertThat( target.getLongValue() ).isEqualTo( 1054 );
+ assertThat( target.getLongPrimitiveValue() ).isEqualTo( 1054 );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java
new file mode 100644
index 0000000000..d686fcbc16
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2921;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue2921Mapper {
+
+ Issue2921Mapper INSTANCE = Mappers.getMapper( Issue2921Mapper.class );
+
+ Target map(Source source);
+
+ default Short toShort(Integer value) {
+ throw new UnsupportedOperationException( "toShort method should not be used" );
+ }
+
+ class Source {
+ private final Integer value;
+
+ public Source(Integer value) {
+ this.value = value;
+ }
+
+ public Integer getValue() {
+ return value;
+ }
+ }
+
+ class Target {
+ private final int value;
+
+ public Target(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java
new file mode 100644
index 0000000000..5b8dd0386c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2921;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2921")
+@WithClasses({
+ Issue2921Mapper.class,
+})
+class Issue2921Test {
+
+ @ProcessorTest
+ void shouldNotUseIntegerToShortForMappingIntegerToInt() {
+ Issue2921Mapper.Target target = Issue2921Mapper.INSTANCE.map( new Issue2921Mapper.Source( 10 ) );
+ assertThat( target.getValue() ).isEqualTo( 10 );
+ }
+}
From e979f506fac6aeabfa3a1993b3b986449fcb7b3a Mon Sep 17 00:00:00 2001
From: Orange Add <48479242+chenzijia12300@users.noreply.github.com>
Date: Thu, 29 Sep 2022 04:17:59 +0800
Subject: [PATCH 035/320] #2773 Copy `@Deprecated` annotation from method or
mapper to implementation
---
...eatureCompilationExclusionCliEnhancer.java | 1 +
.../test/resources/fullFeatureTest/pom.xml | 2 +
.../ap/internal/gem/GemGenerator.java | 1 +
.../model/AdditionalAnnotationsBuilder.java | 42 ++++++++++++-
.../test/annotatewith/AnnotateWithTest.java | 1 -
.../deprecated/DeprecatedMapperWithClass.java | 13 ++++
.../DeprecatedMapperWithMethod.java | 44 +++++++++++++
.../deprecated/DeprecatedTest.java | 56 +++++++++++++++++
.../deprecated/RepeatDeprecatedMapper.java | 15 +++++
.../jdk11/DeprecatedMapperWithClass.java | 13 ++++
.../jdk11/DeprecatedMapperWithMethod.java | 44 +++++++++++++
.../deprecated/jdk11/DeprecatedTest.java | 63 +++++++++++++++++++
.../RepeatDeprecatedMapperWithParams.java | 21 +++++++
13 files changed, 314 insertions(+), 2 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.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 0f261d6568..e017d1b003 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
@@ -31,6 +31,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces
case JAVA_8:
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" );
break;
case JAVA_9:
// TODO find out why this fails:
diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
index ac69114bd6..8a62f48588 100644
--- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml
+++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
@@ -25,6 +25,7 @@
x
x
x
+ x
@@ -45,6 +46,7 @@
${additionalExclude2}
${additionalExclude3}
${additionalExclude4}
+ ${additionalExclude5}
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 cbb59f4e57..5caea8a008 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
@@ -45,6 +45,7 @@
*
* @author Gunnar Morling
*/
+@GemDefinition(Deprecated.class)
@GemDefinition(AnnotateWith.class)
@GemDefinition(AnnotateWith.Element.class)
@GemDefinition(AnnotateWiths.class)
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 f28cdfef9c..3f0b2123ae 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
@@ -10,6 +10,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -31,6 +32,7 @@
import org.mapstruct.ap.internal.gem.AnnotateWithGem;
import org.mapstruct.ap.internal.gem.AnnotateWithsGem;
+import org.mapstruct.ap.internal.gem.DeprecatedGem;
import org.mapstruct.ap.internal.gem.ElementGem;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement;
import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType;
@@ -55,7 +57,6 @@ public class AdditionalAnnotationsBuilder
extends RepeatableAnnotations {
private static final String ANNOTATE_WITH_FQN = "org.mapstruct.AnnotateWith";
private static final String ANNOTATE_WITHS_FQN = "org.mapstruct.AnnotateWiths";
-
private TypeFactory typeFactory;
private FormattingMessager messager;
@@ -90,6 +91,45 @@ protected void addInstances(AnnotateWithsGem gem, Element source, Set getProcessedAnnotations(Element source) {
+ Set processedAnnotations = super.getProcessedAnnotations( source );
+ return addDeprecatedAnnotation( source, processedAnnotations );
+ }
+
+ private Set addDeprecatedAnnotation(Element source, Set annotations) {
+ DeprecatedGem deprecatedGem = DeprecatedGem.instanceOn( source );
+ if ( deprecatedGem == null ) {
+ return annotations;
+ }
+ Type deprecatedType = typeFactory.getType( Deprecated.class );
+ if ( annotations.stream().anyMatch( annotation -> annotation.getType().equals( deprecatedType ) ) ) {
+ messager.printMessage(
+ source,
+ deprecatedGem.mirror(),
+ Message.ANNOTATE_WITH_DUPLICATE,
+ deprecatedType.describe() );
+ return annotations;
+ }
+ List annotationElements = new ArrayList<>();
+ if ( deprecatedGem.since() != null && deprecatedGem.since().hasValue() ) {
+ annotationElements.add( new AnnotationElement(
+ AnnotationElementType.STRING,
+ "since",
+ Collections.singletonList( deprecatedGem.since().getValue() )
+ ) );
+ }
+ if ( deprecatedGem.forRemoval() != null && deprecatedGem.forRemoval().hasValue() ) {
+ annotationElements.add( new AnnotationElement(
+ AnnotationElementType.BOOLEAN,
+ "forRemoval",
+ Collections.singletonList( deprecatedGem.forRemoval().getValue() )
+ ) );
+ }
+ annotations.add( new Annotation(deprecatedType, annotationElements ) );
+ return annotations;
+ }
+
private void addAndValidateMapping(Set mappings, Element source, AnnotateWithGem gem, Annotation anno) {
if ( anno.getType().getTypeElement().getAnnotation( Repeatable.class ) == null ) {
if ( mappings.stream().anyMatch( existing -> existing.getType().equals( anno.getType() ) ) ) {
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 e3b20f547e..3687fe2bc0 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
@@ -593,5 +593,4 @@ public void valueMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodE
Method method = mapper.getClass().getMethod( "map", String.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
-
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java
new file mode 100644
index 0000000000..d0164e8be0
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated;
+
+import org.mapstruct.Mapper;
+
+@Mapper
+@Deprecated
+public class DeprecatedMapperWithClass {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java
new file mode 100644
index 0000000000..b6c2c8eb62
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+
+@Mapper
+public interface DeprecatedMapperWithMethod {
+
+ @AnnotateWith(CustomMethodOnlyAnnotation.class)
+ @Deprecated
+ Target map(Source source);
+
+ class Source {
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ class Target {
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java
new file mode 100644
index 0000000000..5abca7171e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated;
+
+import java.lang.reflect.Method;
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author orange add
+ */
+public class DeprecatedTest {
+
+ @ProcessorTest
+ @WithClasses( { DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class} )
+ public void deprecatedWithMethodCorrectCopy() throws NoSuchMethodException {
+ DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class );
+ Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class );
+ Deprecated annotation = method.getAnnotation( Deprecated.class );
+ assertThat( annotation ).isNotNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(DeprecatedMapperWithClass.class)
+ public void deprecatedWithClassCorrectCopy() {
+ DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class );
+ Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class );
+ assertThat( annotation ).isNotNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(RepeatDeprecatedMapper.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.SUCCEEDED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.WARNING,
+ type = RepeatDeprecatedMapper.class,
+ message = "Annotation \"Deprecated\" is already present with the " +
+ "same elements configuration."
+ )
+ }
+ )
+ public void deprecatedWithRepeat() {
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java
new file mode 100644
index 0000000000..dd085f101c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+
+@Mapper
+@Deprecated
+@AnnotateWith(Deprecated.class)
+public class RepeatDeprecatedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java
new file mode 100644
index 0000000000..2e0507f5ab
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated.jdk11;
+
+import org.mapstruct.Mapper;
+
+@Mapper
+@Deprecated(since = "11")
+public class DeprecatedMapperWithClass {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java
new file mode 100644
index 0000000000..bf898e2043
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated.jdk11;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+
+@Mapper
+public interface DeprecatedMapperWithMethod {
+
+ @AnnotateWith(CustomMethodOnlyAnnotation.class)
+ @Deprecated(since = "18", forRemoval = false)
+ Target map(Source source);
+
+ class Source {
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ class Target {
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java
new file mode 100644
index 0000000000..4e10b6ded6
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated.jdk11;
+
+import java.lang.reflect.Method;
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author orange add
+ */
+public class DeprecatedTest {
+ @ProcessorTest
+ @WithClasses({ DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class})
+ public void deprecatedWithMethodCorrectCopyForJdk11() throws NoSuchMethodException {
+ DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class );
+ Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class );
+ Deprecated annotation = method.getAnnotation( Deprecated.class );
+ assertThat( annotation ).isNotNull();
+ assertThat( annotation.since() ).isEqualTo( "18" );
+ assertThat( annotation.forRemoval() ).isEqualTo( false );
+ }
+
+ @ProcessorTest
+ @WithClasses(DeprecatedMapperWithClass.class)
+ public void deprecatedWithClassCorrectCopyForJdk11() {
+ DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class );
+ Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class );
+ assertThat( annotation ).isNotNull();
+ assertThat( annotation.since() ).isEqualTo( "11" );
+ }
+
+ @ProcessorTest
+ @WithClasses( { RepeatDeprecatedMapperWithParams.class})
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.SUCCEEDED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.WARNING,
+ type = RepeatDeprecatedMapperWithParams.class,
+ message = "Annotation \"Deprecated\" is already present with the " +
+ "same elements configuration."
+ )
+ }
+ )
+ public void bothExistPriorityAnnotateWithForJdk11() {
+ RepeatDeprecatedMapperWithParams mapper = Mappers.getMapper( RepeatDeprecatedMapperWithParams.class );
+ Deprecated deprecated = mapper.getClass().getAnnotation( Deprecated.class );
+ assertThat( deprecated ).isNotNull();
+ assertThat( deprecated.since() ).isEqualTo( "1.5" );
+ assertThat( deprecated.forRemoval() ).isEqualTo( false );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java
new file mode 100644
index 0000000000..971791bfd2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.annotatewith.deprecated.jdk11;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+
+@Mapper
+@Deprecated( since = "1.8" )
+@AnnotateWith(
+ value = Deprecated.class,
+ elements = {
+ @AnnotateWith.Element( name = "forRemoval", booleans = false),
+ @AnnotateWith.Element( name = "since", strings = "1.5")
+ }
+)
+public class RepeatDeprecatedMapperWithParams {
+}
From 608d476ed26ce4ac3096e2d9a2ee2a8226e42a00 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Thu, 29 Sep 2022 21:35:51 +0200
Subject: [PATCH 036/320] #3018: Use MappingControl with SubclassMapping
---
.../ap/internal/model/BeanMappingMethod.java | 2 +-
.../test/subclassmapping/DeepCloneMapper.java | 23 ++++++++++++++
.../DeepCloneMethodMapper.java | 26 ++++++++++++++++
.../subclassmapping/SubclassMappingTest.java | 30 +++++++++++++++++++
.../test/subclassmapping/mappables/Car.java | 1 +
5 files changed, 81 insertions(+), 1 deletion(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.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 4f2dc4e5f1..24e160b9de 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
@@ -405,7 +405,7 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
Collections.emptyList(),
subclassMappingOptions.getTarget(),
ctx.getTypeUtils() ).withSourceRHS( rightHandSide ),
- null,
+ subclassMappingOptions.getMappingControl( ctx.getElementUtils() ),
null,
false );
Assignment assignment = ctx
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java
new file mode 100644
index 0000000000..24d0244729
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.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.subclassmapping;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
+import org.mapstruct.ap.test.subclassmapping.mappables.Car;
+import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
+import org.mapstruct.control.DeepClone;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(mappingControl = DeepClone.class)
+public interface DeepCloneMapper {
+ DeepCloneMapper INSTANCE = Mappers.getMapper( DeepCloneMapper.class );
+
+ @SubclassMapping( source = Car.class, target = Car.class )
+ @SubclassMapping( source = Bike.class, target = Bike.class )
+ Vehicle map(Vehicle vehicle);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java
new file mode 100644
index 0000000000..3f276b24cd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.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.subclassmapping;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.NullValueMappingStrategy;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
+import org.mapstruct.ap.test.subclassmapping.mappables.Car;
+import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
+import org.mapstruct.control.DeepClone;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface DeepCloneMethodMapper {
+ DeepCloneMethodMapper INSTANCE = Mappers.getMapper( DeepCloneMethodMapper.class );
+
+ @SubclassMapping( source = Car.class, target = Car.class )
+ @SubclassMapping( source = Bike.class, target = Bike.class )
+ @BeanMapping( mappingControl = DeepClone.class, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL )
+ Vehicle map(Vehicle vehicle);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
index 7df555ecc2..6f9b6e160c 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
@@ -52,6 +52,36 @@ void mappingIsDoneUsingSubclassMapping() {
.containsExactly( CarDto.class, BikeDto.class );
}
+ @ProcessorTest
+ @WithClasses( DeepCloneMapper.class )
+ void deepCloneMappingClonesObjects() {
+ Car car = new Car();
+ car.setManual( true );
+ car.setName( "namedCar" );
+ car.setVehicleManufacturingCompany( "veMac" );
+
+ Vehicle result = DeepCloneMapper.INSTANCE.map( car );
+
+ assertThat( result ).isInstanceOf( Car.class );
+ assertThat( result ).isNotSameAs( car );
+ assertThat( result ).usingRecursiveComparison().isEqualTo( car );
+ }
+
+ @ProcessorTest
+ @WithClasses( DeepCloneMethodMapper.class )
+ void deepCloneMappingOnMethodClonesObjects() {
+ Car car = new Car();
+ car.setManual( true );
+ car.setName( "namedCar" );
+ car.setVehicleManufacturingCompany( "veMac" );
+
+ Vehicle result = DeepCloneMethodMapper.INSTANCE.map( car );
+
+ assertThat( result ).isInstanceOf( Car.class );
+ assertThat( result ).isNotSameAs( car );
+ assertThat( result ).usingRecursiveComparison().isEqualTo( car );
+ }
+
@ProcessorTest
@WithClasses( SimpleSubclassMapper.class )
void inverseMappingIsDoneUsingSubclassMapping() {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java
index 71b2446d34..6f33a6cb61 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java
@@ -15,4 +15,5 @@ public boolean isManual() {
public void setManual(boolean manual) {
this.manual = manual;
}
+
}
From af1eab0ece084aa04798e2d86fc20bd65bfa784e Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Thu, 29 Sep 2022 22:10:27 +0200
Subject: [PATCH 037/320] #2743 BeanMappingOptions should not be inherited for
forged methods
---
.../model/source/BeanMappingOptions.java | 7 +-
.../model/source/MappingMethodOptions.java | 2 +-
.../ap/test/bugs/_2743/Issue2743Mapper.java | 75 +++++++++++++++++++
.../ap/test/bugs/_2743/Issue2743Test.java | 24 ++++++
4 files changed, 105 insertions(+), 3 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java
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 60aac32e54..b73b4084fd 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
@@ -54,13 +54,16 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping)
return options;
}
+ public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) {
+ return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions );
+ }
+
public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, MapperOptions mapperOptions,
ExecutableElement method, FormattingMessager messager,
TypeUtils typeUtils, TypeFactory typeFactory
) {
if ( beanMapping == null || !isConsistent( beanMapping, method, messager ) ) {
- BeanMappingOptions options = new BeanMappingOptions( null, Collections.emptyList(), null, mapperOptions );
- return options;
+ return empty( mapperOptions );
}
Objects.requireNonNull( method );
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 4a140c4fb9..a1a64872a9 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
@@ -365,7 +365,7 @@ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethod
options.mappings,
options.iterableMapping,
options.mapMapping,
- options.beanMapping,
+ BeanMappingOptions.empty( options.beanMapping.next() ),
options.enumMappingOptions,
options.valueMappings,
Collections.emptySet(),
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java
new file mode 100644
index 0000000000..db3c0e6d49
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2743;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
+public interface Issue2743Mapper {
+
+ @BeanMapping(ignoreUnmappedSourceProperties = { "number" })
+ Target map(Source source);
+
+ class Source {
+
+ private final int number = 10;
+ private final NestedSource nested;
+
+ public Source(NestedSource nested) {
+ this.nested = nested;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public NestedSource getNested() {
+ return nested;
+ }
+ }
+
+ class NestedSource {
+ private final String value;
+
+ public NestedSource(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ class Target {
+
+ private final NestedTarget nested;
+
+ public Target(NestedTarget nested) {
+ this.nested = nested;
+ }
+
+ public NestedTarget getNested() {
+ return nested;
+ }
+ }
+
+ class NestedTarget {
+ private final String value;
+
+ public NestedTarget(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java
new file mode 100644
index 0000000000..5c2bb61279
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2743;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2743")
+@WithClasses({
+ Issue2743Mapper.class
+})
+class Issue2743Test {
+
+ @ProcessorTest
+ void shouldCompile() {
+ }
+}
From 90a487ac0641c39ebec6680567cc97eaede49365 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?=
Date: Sat, 1 Oct 2022 13:47:49 +0200
Subject: [PATCH 038/320] #1427 Add support for custom name in Spring
stereotype annotations
---
.../processor/SpringComponentProcessor.java | 67 +++++++++++++-
.../spring/annotateWith/CustomStereotype.java | 21 +++++
...ustomerSpringComponentQualifiedMapper.java | 20 ++++
...stomerSpringControllerQualifiedMapper.java | 19 ++++
...SpringCustomStereotypeQualifiedMapper.java | 21 +++++
...stomerSpringRepositoryQualifiedMapper.java | 19 ++++
.../CustomerSpringServiceQualifiedMapper.java | 19 ++++
.../SpringAnnotateWithMapperTest.java | 91 +++++++++++++++++++
8 files changed, 276 insertions(+), 1 deletion(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java
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 4efc537c3d..84a672bcc9 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java
@@ -8,7 +8,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.internal.model.Annotation;
@@ -16,6 +18,13 @@
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;
+
/**
* A {@link ModelElementProcessor} which converts the given {@link Mapper}
* object into a Spring bean in case Spring is configured as the
@@ -25,6 +34,7 @@
* @author Andreas Gudian
*/
public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor {
+
@Override
protected String getComponentModelIdentifier() {
return MappingConstantsGem.ComponentModelGem.SPRING;
@@ -33,7 +43,9 @@ protected String getComponentModelIdentifier() {
@Override
protected List getTypeAnnotations(Mapper mapper) {
List typeAnnotations = new ArrayList<>();
- typeAnnotations.add( component() );
+ if ( !isAlreadyAnnotatedAsSpringStereotype( mapper ) ) {
+ typeAnnotations.add( component() );
+ }
if ( mapper.getDecorator() != null ) {
typeAnnotations.add( qualifierDelegate() );
@@ -91,4 +103,57 @@ private Annotation primary() {
private Annotation component() {
return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) );
}
+
+ private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) {
+ Set handledElements = new HashSet<>();
+ return mapper.getAnnotations()
+ .stream()
+ .anyMatch(
+ annotation -> isOrIncludesComponentAnnotation( annotation, handledElements )
+ );
+ }
+
+ private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set handledElements) {
+ return isOrIncludesComponentAnnotation(
+ annotation.getType().getTypeElement(), handledElements
+ );
+ }
+
+ private boolean isOrIncludesComponentAnnotation(Element element, Set handledElements) {
+ if ( "org.springframework.stereotype.Component".equals(
+ ( (TypeElement) element ).getQualifiedName().toString()
+ )) {
+ return true;
+ }
+
+ for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
+ Element annotationMirrorElement = annotationMirror.getAnnotationType().asElement();
+ // Bypass java lang annotations to improve performance avoiding unnecessary checks
+ if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) &&
+ !handledElements.contains( annotationMirrorElement ) ) {
+ handledElements.add( annotationMirrorElement );
+ boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation(
+ annotationMirrorElement, handledElements
+ );
+
+ if ( isOrIncludesComponentAnnotation ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private PackageElement getPackageOf( Element element ) {
+ while ( element.getKind() != PACKAGE ) {
+ element = element.getEnclosingElement();
+ }
+
+ return (PackageElement) element;
+ }
+
+ private boolean isAnnotationInPackage(Element element, String packageFQN) {
+ return packageFQN.equals( getPackageOf( element ).getQualifiedName().toString() );
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java
new file mode 100644
index 0000000000..ccc196c5d3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.springframework.stereotype.Component;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Component
+public @interface CustomStereotype {
+ String value() default "";
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java
new file mode 100644
index 0000000000..be08ff0b1c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.springframework.stereotype.Component;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+
+/**
+ * @author Ben Zegveld
+ */
+@AnnotateWith( value = Component.class, elements = @AnnotateWith.Element( strings = "AnnotateWithComponent" ) )
+@Mapper( componentModel = MappingConstants.ComponentModel.SPRING )
+public interface CustomerSpringComponentQualifiedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java
new file mode 100644
index 0000000000..7203fad7a9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+import org.springframework.stereotype.Controller;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@AnnotateWith( value = Controller.class, elements = @AnnotateWith.Element( strings = "AnnotateWithController" ) )
+@Mapper( componentModel = MappingConstants.ComponentModel.SPRING )
+public interface CustomerSpringControllerQualifiedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java
new file mode 100644
index 0000000000..18a062497d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@AnnotateWith(
+ value = CustomStereotype.class,
+ elements = @AnnotateWith.Element( strings = "AnnotateWithCustomStereotype" )
+)
+@Mapper( componentModel = MappingConstants.ComponentModel.SPRING )
+public interface CustomerSpringCustomStereotypeQualifiedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java
new file mode 100644
index 0000000000..7bbefbee2b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@AnnotateWith( value = Repository.class, elements = @AnnotateWith.Element( strings = "AnnotateWithRepository" ) )
+@Mapper( componentModel = MappingConstants.ComponentModel.SPRING )
+public interface CustomerSpringRepositoryQualifiedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java
new file mode 100644
index 0000000000..52dff8ef8f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@AnnotateWith( value = Service.class, elements = @AnnotateWith.Element( strings = "AnnotateWithService" ) )
+@Mapper( componentModel = MappingConstants.ComponentModel.SPRING )
+public interface CustomerSpringServiceQualifiedMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java
new file mode 100644
index 0000000000..cdc741814c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.injectionstrategy.spring.annotateWith;
+
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.WithSpring;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+/**
+ * Test field injection for component model spring.
+ *
+ * @author Filip Hrisafov
+ * @author Jose Carlos Campanero Ortiz
+ */
+@WithClasses({
+ CustomerSpringComponentQualifiedMapper.class,
+ CustomerSpringControllerQualifiedMapper.class,
+ CustomerSpringServiceQualifiedMapper.class,
+ CustomerSpringRepositoryQualifiedMapper.class,
+ CustomStereotype.class,
+ CustomerSpringCustomStereotypeQualifiedMapper.class
+})
+@IssueKey( "1427" )
+@WithSpring
+public class SpringAnnotateWithMapperTest {
+
+ @RegisterExtension
+ final GeneratedSource generatedSource = new GeneratedSource();
+
+ @ProcessorTest
+ public void shouldHaveComponentAnnotatedQualifiedMapper() {
+
+ // then
+ generatedSource.forMapper( CustomerSpringComponentQualifiedMapper.class )
+ .content()
+ .contains( "@Component(value = \"AnnotateWithComponent\")" )
+ .doesNotContain( "@Component" + System.lineSeparator() );
+
+ }
+
+ @ProcessorTest
+ public void shouldHaveControllerAnnotatedQualifiedMapper() {
+
+ // then
+ generatedSource.forMapper( CustomerSpringControllerQualifiedMapper.class )
+ .content()
+ .contains( "@Controller(value = \"AnnotateWithController\")" )
+ .doesNotContain( "@Component" );
+
+ }
+
+ @ProcessorTest
+ public void shouldHaveServiceAnnotatedQualifiedMapper() {
+
+ // then
+ generatedSource.forMapper( CustomerSpringServiceQualifiedMapper.class )
+ .content()
+ .contains( "@Service(value = \"AnnotateWithService\")" )
+ .doesNotContain( "@Component" );
+
+ }
+
+ @ProcessorTest
+ public void shouldHaveRepositoryAnnotatedQualifiedMapper() {
+
+ // then
+ generatedSource.forMapper( CustomerSpringRepositoryQualifiedMapper.class )
+ .content()
+ .contains( "@Repository(value = \"AnnotateWithRepository\")" )
+ .doesNotContain( "@Component" );
+
+ }
+
+ @ProcessorTest
+ public void shouldHaveCustomStereotypeAnnotatedQualifiedMapper() {
+
+ // then
+ generatedSource.forMapper( CustomerSpringCustomStereotypeQualifiedMapper.class )
+ .content()
+ .contains( "@CustomStereotype(value = \"AnnotateWithCustomStereotype\")" )
+ .doesNotContain( "@Component" );
+
+ }
+
+}
From 411cc24dac06892dc7e5582aff9ce409302916b8 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Sun, 2 Oct 2022 09:34:03 +0200
Subject: [PATCH 039/320] #2955 Fix `@AfterMapping` with return type not called
for update mappings
---
.../model/LifecycleMethodResolver.java | 2 +-
.../CallbacksWithReturnValuesTest.java | 46 +++++++++++++++----
2 files changed, 38 insertions(+), 10 deletions(-)
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 87527e87f8..8b44dee254 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
@@ -141,7 +141,7 @@ private static List collectLifecycleCallbackMe
callbackMethods,
Collections.emptyList(),
targetType,
- method.getReturnType(),
+ method.getResultType(),
SelectionCriteria.forLifecycleMethods( selectionParameters ) );
return toLifecycleCallbackMethodRefs(
diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java
index 8c391bbfeb..7d6eaacb79 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java
@@ -8,6 +8,7 @@
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.AfterEach;
import org.mapstruct.ap.test.callbacks.returning.NodeMapperContext.ContextListener;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
@@ -25,23 +26,29 @@
@WithClasses( { Attribute.class, AttributeDto.class, Node.class, NodeDto.class, NodeMapperDefault.class,
NodeMapperWithContext.class, NodeMapperContext.class, Number.class, NumberMapperDefault.class,
NumberMapperContext.class, NumberMapperWithContext.class } )
-public class CallbacksWithReturnValuesTest {
+class CallbacksWithReturnValuesTest {
+ @AfterEach
+ void cleanup() {
+ NumberMapperContext.clearCache();
+ NumberMapperContext.clearVisited();
+ }
+
@ProcessorTest
- public void mappingWithDefaultHandlingRaisesStackOverflowError() {
+ void mappingWithDefaultHandlingRaisesStackOverflowError() {
Node root = buildNodes();
assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root ) )
.isInstanceOf( StackOverflowError.class );
}
@ProcessorTest
- public void updatingWithDefaultHandlingRaisesStackOverflowError() {
+ void updatingWithDefaultHandlingRaisesStackOverflowError() {
Node root = buildNodes();
assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ) )
.isInstanceOf( StackOverflowError.class );
}
@ProcessorTest
- public void mappingWithContextCorrectlyResolvesCycles() {
+ void mappingWithContextCorrectlyResolvesCycles() {
final AtomicReference contextLevel = new AtomicReference<>( null );
ContextListener contextListener = new ContextListener() {
@Override
@@ -75,28 +82,49 @@ private static Node buildNodes() {
}
@ProcessorTest
- public void numberMappingWithoutContextDoesNotUseCache() {
+ void numberMappingWithoutContextDoesNotUseCache() {
Number n1 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 );
Number n2 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 );
+
assertThat( n1 ).isEqualTo( n2 );
assertThat( n1 ).isNotSameAs( n2 );
}
@ProcessorTest
- public void numberMappingWithContextUsesCache() {
+ void numberMappingWithContextUsesCache() {
NumberMapperContext.putCache( new Number( 2342 ) );
Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 );
Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 );
+
assertThat( n1 ).isEqualTo( n2 );
assertThat( n1 ).isSameAs( n2 );
- NumberMapperContext.clearCache();
}
@ProcessorTest
- public void numberMappingWithContextCallsVisitNumber() {
+ void numberMappingWithContextCallsVisitNumber() {
Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 1234 );
Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 5678 );
+
assertThat( NumberMapperContext.getVisited() ).isEqualTo( Arrays.asList( n1, n2 ) );
- NumberMapperContext.clearVisited();
+ }
+
+ @ProcessorTest
+ @IssueKey( "2955" )
+ void numberUpdateMappingWithContextUsesCacheAndThereforeDoesNotVisitNumber() {
+ Number target = new Number();
+ Number expectedReturn = new Number( 2342 );
+ NumberMapperContext.putCache( expectedReturn );
+ NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target );
+
+ assertThat( NumberMapperContext.getVisited() ).isEmpty();
+ }
+
+ @ProcessorTest
+ @IssueKey( "2955" )
+ void numberUpdateMappingWithContextCallsVisitNumber() {
+ Number target = new Number();
+ NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target );
+
+ assertThat( NumberMapperContext.getVisited() ).contains( target );
}
}
From 266c5fa41ce6a07c4fb11b745b461c4ca19a8301 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 2 Oct 2022 19:20:13 +0200
Subject: [PATCH 040/320] #1216 Pick candidate method with most specific return
type
When there are multiple candidate methods returning different types.
We should be able to use the method with the most specific return type (if such a method exists)
---
.../source/selector/MethodSelectors.java | 4 +-
.../MostSpecificResultTypeSelector.java | 45 +++++++++++++
.../source/selector/SelectionCriteria.java | 8 +++
...MostSpecificResultTypeSelectingMapper.java | 31 +++++++++
.../selection/resulttype/FruitFamily.java | 22 ++++++
.../resulttype/InheritanceSelectionTest.java | 67 +++++++++++++++++++
...MostSpecificResultTypeSelectingMapper.java | 30 +++++++++
...ecificResultTypeSelectingUpdateMapper.java | 65 ++++++++++++++++++
8 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java
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 519e1c3d6d..74fa1a33a3 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
@@ -37,7 +37,9 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFacto
new InheritanceSelector(),
new CreateOrUpdateSelector(),
new SourceRhsSelector(),
- new FactoryParameterSelector() );
+ new FactoryParameterSelector(),
+ new MostSpecificResultTypeSelector()
+ );
}
/**
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java
new file mode 100644
index 0000000000..6e86a30bc7
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.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.selector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mapstruct.ap.internal.model.common.Type;
+import org.mapstruct.ap.internal.model.source.Method;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class MostSpecificResultTypeSelector implements MethodSelector {
+
+ @Override
+ public List> getMatchingMethods(Method mappingMethod,
+ List> candidates,
+ List sourceTypes, Type mappingTargetType,
+ Type returnType, SelectionCriteria criteria) {
+ if ( candidates.size() < 2 || !criteria.isForMapping() || criteria.getQualifyingResultType() != null) {
+ return candidates;
+ }
+
+ List> result = new ArrayList<>();
+
+ for ( SelectedMethod candidate : candidates ) {
+ if ( candidate.getMethod()
+ .getResultType()
+ .getBoxedEquivalent()
+ .equals( mappingTargetType.getBoxedEquivalent() ) ) {
+ // If the result type is the same as the target type
+ // then this candidate has the most specific match and should be used
+ result.add( candidate );
+ }
+ }
+
+
+ // If not most specific types were found then return the current candidates
+ return result.isEmpty() ? candidates : result;
+ }
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java
index da8bae365a..7e12bbd188 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
@@ -68,6 +68,14 @@ public SelectionCriteria(SelectionParameters selectionParameters, MappingControl
this.type = type;
}
+ /**
+ *
+ * @return {@code true} if only mapping methods should be selected
+ */
+ public boolean isForMapping() {
+ return type == null || type == Type.PREFER_UPDATE_MAPPING;
+ }
+
/**
* @return true if factory methods should be selected, false otherwise.
*/
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java
new file mode 100644
index 0000000000..28a925c32b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.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.selection.resulttype;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousAmbiguousMostSpecificResultTypeSelectingMapper {
+
+ @Mapping( target = "apple", source = "fruit")
+ AppleFamily map(FruitFamily fruitFamily);
+
+ default GoldenDelicious toGolden(IsFruit fruit) {
+ return fruit != null ? new GoldenDelicious( fruit.getType() ) : null;
+ }
+
+ default Apple toApple1(IsFruit fruit) {
+ return fruit != null ? new Apple( fruit.getType() ) : null;
+ }
+
+ default Apple toApple2(IsFruit fruit) {
+ return fruit != null ? new Apple( fruit.getType() ) : null;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java
new file mode 100644
index 0000000000..347e5a0be0
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.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.selection.resulttype;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FruitFamily {
+
+ private IsFruit fruit;
+
+ public IsFruit getFruit() {
+ return fruit;
+ }
+
+ public void setFruit(IsFruit fruit) {
+ this.fruit = fruit;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java
index a6a4f1ad05..05245bafe0 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java
@@ -240,4 +240,71 @@ public void testShouldUseConstructorFromResultType() {
.hasMessage( "Not allowed to change citrus type" );
assertThat( citrus.getType() ).isEqualTo( "lemon" );
}
+
+ @ProcessorTest
+ @IssueKey("1216")
+ @WithClasses({
+ Citrus.class,
+ GoldenDelicious.class,
+ FruitFamily.class,
+ AppleFamily.class,
+ MostSpecificResultTypeSelectingMapper.class,
+ Citrus.class
+ })
+ public void testShouldUseMethodWithMostSpecificReturnType() {
+ FruitFamily fruitFamily = new FruitFamily();
+ fruitFamily.setFruit( new Citrus( "citrus" ) );
+ AppleFamily appleFamily = MostSpecificResultTypeSelectingMapper.INSTANCE.map( fruitFamily );
+
+ assertThat( appleFamily.getApple() ).isExactlyInstanceOf( Apple.class );
+ assertThat( appleFamily.getApple().getType() ).isEqualTo( "citrus" );
+ }
+
+ @ProcessorTest
+ @IssueKey("1216")
+ @WithClasses({
+ Citrus.class,
+ FruitFamily.class,
+ GoldenDelicious.class,
+ MostSpecificResultTypeSelectingUpdateMapper.class,
+ Citrus.class
+ })
+ public void testShouldUseMethodWithMostSpecificReturnTypeForUpdateMappings() {
+ FruitFamily fruitFamily = new FruitFamily();
+ fruitFamily.setFruit( new Citrus( "citrus" ) );
+ MostSpecificResultTypeSelectingUpdateMapper.Target target =
+ new MostSpecificResultTypeSelectingUpdateMapper.Target(
+ new Apple( "from_test" ),
+ new GoldenDelicious( "from_test" )
+ );
+ MostSpecificResultTypeSelectingUpdateMapper.INSTANCE.update( target, fruitFamily );
+
+ assertThat( target.getApple() ).isExactlyInstanceOf( Apple.class );
+ assertThat( target.getApple().getType() ).isEqualTo( "apple updated citrus" );
+ assertThat( target.getGoldenApple() ).isExactlyInstanceOf( GoldenDelicious.class );
+ assertThat( target.getGoldenApple().getType() ).isEqualTo( "golden updated citrus" );
+ }
+
+ @ProcessorTest
+ @IssueKey("1216")
+ @WithClasses({
+ GoldenDelicious.class,
+ FruitFamily.class,
+ AppleFamily.class,
+ ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.class,
+ kind = Kind.ERROR,
+ line = 17,
+ message = "Ambiguous mapping methods found for mapping property \"IsFruit fruit\" to Apple: " +
+ "Apple toApple1(IsFruit fruit), Apple toApple2(IsFruit fruit). " +
+ "See https://mapstruct.org/faq/#ambiguous for more info."
+ )
+ }
+ )
+ public void testAmbiguousMostSpecificResultTypeErroneous() {
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java
new file mode 100644
index 0000000000..16d9750ec2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.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.selection.resulttype;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface MostSpecificResultTypeSelectingMapper {
+
+ MostSpecificResultTypeSelectingMapper INSTANCE = Mappers.getMapper( MostSpecificResultTypeSelectingMapper.class );
+
+ @Mapping( target = "apple", source = "fruit")
+ AppleFamily map(FruitFamily fruitFamily);
+
+ default GoldenDelicious toGolden(IsFruit fruit) {
+ return fruit != null ? new GoldenDelicious( fruit.getType() ) : null;
+ }
+
+ default Apple toApple(IsFruit fruit) {
+ return fruit != null ? new Apple( fruit.getType() ) : null;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java
new file mode 100644
index 0000000000..5f270116d4
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.selection.resulttype;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.ObjectFactory;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface MostSpecificResultTypeSelectingUpdateMapper {
+
+ MostSpecificResultTypeSelectingUpdateMapper INSTANCE = Mappers.getMapper(
+ MostSpecificResultTypeSelectingUpdateMapper.class );
+
+ @Mapping(target = "apple", source = "fruit")
+ @Mapping(target = "goldenApple", source = "fruit")
+ void update(@MappingTarget Target target, FruitFamily fruitFamily);
+
+ default void updateGolden(@MappingTarget GoldenDelicious target, IsFruit fruit) {
+ target.setType( "golden updated " + fruit.getType() );
+ }
+
+ default void updateApple(@MappingTarget Apple target, IsFruit fruit) {
+ target.setType( "apple updated " + fruit.getType() );
+ }
+
+ @ObjectFactory
+ default GoldenDelicious createGolden() {
+ return new GoldenDelicious( "from_object_factory" );
+ }
+
+ class Target {
+ protected Apple apple;
+ protected GoldenDelicious goldenApple;
+
+ public Target(Apple apple, GoldenDelicious goldenApple) {
+ this.apple = apple;
+ this.goldenApple = goldenApple;
+ }
+
+ public Apple getApple() {
+ return apple;
+ }
+
+ public void setApple(Apple apple) {
+ this.apple = apple;
+ }
+
+ public GoldenDelicious getGoldenApple() {
+ return goldenApple;
+ }
+
+ public void setGoldenApple(GoldenDelicious goldenApple) {
+ this.goldenApple = goldenApple;
+ }
+ }
+}
From 3a325ea66bdd07741d1bda43d3c77c2709464ea7 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Mon, 3 Oct 2022 21:12:19 +0200
Subject: [PATCH 041/320] #3036 Fix compile errors when intersection types are
used in lifecycle methods
---
...eatureCompilationExclusionCliEnhancer.java | 4 +
.../test/resources/fullFeatureTest/pom.xml | 1 +
.../ap/internal/model/common/Type.java | 29 ++++++
.../internal/model/source/MethodMatcher.java | 3 +-
.../LifecycleIntersectionMapper.java | 96 +++++++++++++++++++
.../wildcards/WildCardTest.java | 20 ++++
6 files changed, 151 insertions(+), 2 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.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 e017d1b003..e64b207990 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
@@ -32,6 +32,10 @@ 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" );
+ if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) {
+ additionalExcludes.add(
+ "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" );
+ }
break;
case JAVA_9:
// TODO find out why this fails:
diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
index 8a62f48588..9c17f6c7d5 100644
--- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml
+++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
@@ -26,6 +26,7 @@
x
x
x
+ x
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 fd7f3d6904..6d86aad452 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
@@ -28,6 +28,7 @@
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -114,6 +115,7 @@ public class Type extends ModelElement implements Comparable {
private List alternativeTargetAccessors = null;
private Type boundingBase = null;
+ private List boundTypes = null;
private Type boxedEquivalent = null;
@@ -354,6 +356,10 @@ public boolean isTypeVar() {
return (typeMirror.getKind() == TypeKind.TYPEVAR);
}
+ public boolean isIntersection() {
+ return typeMirror.getKind() == TypeKind.INTERSECTION;
+ }
+
public boolean isJavaLangType() {
return packageName != null && packageName.startsWith( "java." );
}
@@ -1264,6 +1270,29 @@ public Type getTypeBound() {
return boundingBase;
}
+ public List getTypeBounds() {
+ if ( this.boundTypes != null ) {
+ return boundTypes;
+ }
+ Type bound = getTypeBound();
+ if ( bound == null ) {
+ this.boundTypes = Collections.emptyList();
+ }
+ else if ( !bound.isIntersection() ) {
+ this.boundTypes = Collections.singletonList( bound );
+ }
+ else {
+ List extends TypeMirror> bounds = ( (IntersectionType) bound.typeMirror ).getBounds();
+ this.boundTypes = new ArrayList<>( bounds.size() );
+ for ( TypeMirror mirror : bounds ) {
+ boundTypes.add( typeFactory.getType( mirror ) );
+ }
+ }
+
+ return this.boundTypes;
+
+ }
+
public boolean hasAccessibleConstructor() {
if ( hasAccessibleConstructor == null ) {
hasAccessibleConstructor = false;
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 42c26f09a0..3d8bfea135 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java
@@ -325,8 +325,7 @@ else if ( resolved.getParameter().isWildCardBoundByTypeVar()
*/
private boolean candidatesWithinBounds(Map methodParCandidates ) {
for ( Map.Entry entry : methodParCandidates.entrySet() ) {
- Type bound = entry.getKey().getTypeBound();
- if ( bound != null ) {
+ for ( Type bound : entry.getKey().getTypeBounds() ) {
for ( Type.ResolvedPair pair : entry.getValue().pairs ) {
if ( entry.getKey().hasUpperBound() ) {
if ( !pair.getMatch().asRawType().isAssignableTo( bound.asRawType() ) ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java
new file mode 100644
index 0000000000..c466ee0d91
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.selection.methodgenerics.wildcards;
+
+import org.mapstruct.AfterMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface LifecycleIntersectionMapper {
+
+ LifecycleIntersectionMapper INSTANCE = Mappers.getMapper( LifecycleIntersectionMapper.class );
+
+ @Mapping(target = "realm", ignore = true)
+ RealmTarget mapRealm(String source);
+
+ @Mapping(target = "uniqueRealm", ignore = true)
+ UniqueRealmTarget mapUniqueRealm(String source);
+
+ @Mapping(target = "realm", ignore = true)
+ @Mapping(target = "uniqueRealm", ignore = true)
+ BothRealmsTarget mapBothRealms(String source);
+
+ @AfterMapping
+ default void afterMapping(String source, @MappingTarget T target) {
+ target.setRealm( "realm_" + source );
+ target.setUniqueRealm( "uniqueRealm_" + source );
+ }
+
+ interface RealmObject {
+ void setRealm(String realm);
+ }
+
+ interface UniqueRealmObject {
+ void setUniqueRealm(String realm);
+ }
+
+ class RealmTarget implements RealmObject {
+
+ protected String realm;
+
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+ }
+
+ class UniqueRealmTarget implements UniqueRealmObject {
+ protected String uniqueRealm;
+
+ @Override
+ public void setUniqueRealm(String uniqueRealm) {
+ this.uniqueRealm = uniqueRealm;
+ }
+
+ public String getUniqueRealm() {
+ return uniqueRealm;
+ }
+ }
+
+ class BothRealmsTarget implements RealmObject, UniqueRealmObject {
+
+ protected String realm;
+ protected String uniqueRealm;
+
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public String getUniqueRealm() {
+ return uniqueRealm;
+ }
+
+ @Override
+ public void setUniqueRealm(String uniqueRealm) {
+ this.uniqueRealm = uniqueRealm;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java
index a228304c72..9e27129635 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java
@@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.test.selection.methodgenerics.wildcards;
+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;
@@ -54,4 +55,23 @@ public void testIntersectionRelation() {
assertThat( target ).isNotNull();
assertThat( target.getProp() ).isEqualTo( typeC );
}
+
+ // Eclipse does not handle intersection types correctly (TODO: worthwhile to investigate?)
+ @ProcessorTest(Compiler.JDK)
+ @WithClasses(LifecycleIntersectionMapper.class)
+ @IssueKey("3036")
+ public void testLifecycleIntersection() {
+
+ LifecycleIntersectionMapper.RealmTarget realmTarget = LifecycleIntersectionMapper.INSTANCE.mapRealm( "test" );
+ assertThat( realmTarget.getRealm() ).isNull();
+
+ LifecycleIntersectionMapper.UniqueRealmTarget uniqueRealmTarget =
+ LifecycleIntersectionMapper.INSTANCE.mapUniqueRealm( "test" );
+ assertThat( uniqueRealmTarget.getUniqueRealm() ).isNull();
+
+ LifecycleIntersectionMapper.BothRealmsTarget bothRealmsTarget =
+ LifecycleIntersectionMapper.INSTANCE.mapBothRealms( "test" );
+ assertThat( bothRealmsTarget.getRealm() ).isEqualTo( "realm_test" );
+ assertThat( bothRealmsTarget.getUniqueRealm() ).isEqualTo( "uniqueRealm_test" );
+ }
}
From 481ab36ca35f53a6160bac179c495d6a1774388e Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Mon, 3 Oct 2022 21:16:25 +0200
Subject: [PATCH 042/320] #3036 Add missing exclude to full feature test
---
integrationtest/src/test/resources/fullFeatureTest/pom.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
index 9c17f6c7d5..1a31b28221 100644
--- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml
+++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
@@ -48,6 +48,7 @@
${additionalExclude3}
${additionalExclude4}
${additionalExclude5}
+ ${additionalExclude6}
From a5d3542c24419ff2a66132f3ce2bae12182e3ac0 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Fri, 7 Oct 2022 20:43:15 +0200
Subject: [PATCH 043/320] Update latest release version to 1.5.3.Final
---
readme.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/readme.md b/readme.md
index e1ee8f9deb..58d1c515bb 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,6 @@
# MapStruct - Java bean mappings, the easy way!
-[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final)
+[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final)
[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct)
[](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt)
@@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map
```xml
...
- 1.5.2.Final
+ 1.5.3.Final
...
@@ -114,10 +114,10 @@ plugins {
dependencies {
...
- implementation 'org.mapstruct:mapstruct:1.5.2.Final'
+ implementation 'org.mapstruct:mapstruct:1.5.3.Final'
- annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
- testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' // if you are using mapstruct in test code
+ annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
+ testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // if you are using mapstruct in test code
}
...
```
From 81b2f70dac328d96d788d7746a57fbeb3978e543 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 2 Oct 2022 19:36:20 +0200
Subject: [PATCH 044/320] #3039 Upgrade FreeMarker to 2.3.31
---
distribution/pom.xml | 2 +-
distribution/src/main/assembly/dist.xml | 6 +-----
parent/pom.xml | 2 +-
3 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 11fb7c9f37..be82f52b3c 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -66,7 +66,7 @@
org.freemarker
freemarker
${project.build.directory}/freemarker-unpacked
- META-INF/LICENSE.txt,META-INF/NOTICE.txt
+ META-INF/LICENSE
diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml
index f519e5fc9c..f0c727f8da 100644
--- a/distribution/src/main/assembly/dist.xml
+++ b/distribution/src/main/assembly/dist.xml
@@ -42,11 +42,7 @@
/
- target/freemarker-unpacked/META-INF/NOTICE.txt
- /
-
-
- target/freemarker-unpacked/META-INF/LICENSE.txt
+ target/freemarker-unpacked/META-INF/LICENSE
etc/freemarker
diff --git a/parent/pom.xml b/parent/pom.xml
index 3f536034c7..e87db14551 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -109,7 +109,7 @@
org.freemarker
freemarker
- 2.3.21
+ 2.3.31
org.assertj
From bb099a55ee3c1b9ae7fc2fbad6c6f9c8fe3ae911 Mon Sep 17 00:00:00 2001
From: Orange Add <48479242+chenzijia12300@users.noreply.github.com>
Date: Fri, 4 Nov 2022 05:02:58 +0800
Subject: [PATCH 045/320] #3040: Allow using only `BeanMapping#mappingControl`
---
.../mapstruct/ap/internal/model/source/BeanMappingOptions.java | 1 +
.../ap/test/subclassmapping/DeepCloneMethodMapper.java | 3 +--
2 files changed, 2 insertions(+), 2 deletions(-)
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 b73b4084fd..90ad0aabb4 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
@@ -92,6 +92,7 @@ public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, Mappe
private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method,
FormattingMessager messager) {
if ( !gem.resultType().hasValue()
+ && !gem.mappingControl().hasValue()
&& !gem.qualifiedBy().hasValue()
&& !gem.qualifiedByName().hasValue()
&& !gem.ignoreUnmappedSourceProperties().hasValue()
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java
index 3f276b24cd..036b98a318 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java
@@ -7,7 +7,6 @@
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
-import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.SubclassMapping;
import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
import org.mapstruct.ap.test.subclassmapping.mappables.Car;
@@ -21,6 +20,6 @@ public interface DeepCloneMethodMapper {
@SubclassMapping( source = Car.class, target = Car.class )
@SubclassMapping( source = Bike.class, target = Bike.class )
- @BeanMapping( mappingControl = DeepClone.class, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL )
+ @BeanMapping( mappingControl = DeepClone.class )
Vehicle map(Vehicle vehicle);
}
From 6a394ad466b5bda8b17e2406b56be34be3312e21 Mon Sep 17 00:00:00 2001
From: Orange Add <48479242+chenzijia12300@users.noreply.github.com>
Date: Fri, 4 Nov 2022 06:21:43 +0800
Subject: [PATCH 046/320] #3037 Support `@ValueMapping` in meta annotations
---
.../main/java/org/mapstruct/ValueMapping.java | 2 +-
.../java/org/mapstruct/ValueMappings.java | 2 +-
.../chapter-8-mapping-values.asciidoc | 39 +++++++++++++++
.../model/source/ValueMappingOptions.java | 4 +-
.../processor/MethodRetrievalProcessor.java | 39 ++++++++++-----
.../composition/CustomValueAnnotation.java | 21 ++++++++
.../composition/ValueCompositionTest.java | 48 +++++++++++++++++++
.../ValueMappingCompositionMapper.java | 25 ++++++++++
8 files changed, 164 insertions(+), 16 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java
diff --git a/core/src/main/java/org/mapstruct/ValueMapping.java b/core/src/main/java/org/mapstruct/ValueMapping.java
index 21ea5f4a83..7ad3726e48 100644
--- a/core/src/main/java/org/mapstruct/ValueMapping.java
+++ b/core/src/main/java/org/mapstruct/ValueMapping.java
@@ -84,7 +84,7 @@
*/
@Repeatable(ValueMappings.class)
@Retention(RetentionPolicy.CLASS)
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface ValueMapping {
/**
* The source value constant to use for this mapping.
diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java
index faefbf7105..bb593f53d1 100644
--- a/core/src/main/java/org/mapstruct/ValueMappings.java
+++ b/core/src/main/java/org/mapstruct/ValueMappings.java
@@ -40,7 +40,7 @@
*
* @author Sjaak Derksen
*/
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ValueMappings {
diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
index 24c7324f1d..ce16b33ecd 100644
--- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
@@ -266,3 +266,42 @@ MapStruct provides the following out of the box enum name transformation strateg
It is also possible to register custom strategies.
For more information on how to do that have a look at <>
+
+[[value-mapping-composition]]
+=== ValueMapping Composition
+
+The `@ValueMapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`.
+This allows `@ValueMapping` to be used on other (user defined) annotations for re-use purposes.
+For example:
+
+.Custom value mapping annotations
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Retention( RetentionPolicy.CLASS )
+@ValueMapping(source = "EXTRA", target = "SPECIAL")
+@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
+public @interface CustomValueAnnotation {
+}
+----
+====
+It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example:
+
+.Using custom combination annotations
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface ValueMappingCompositionMapper {
+
+ @CustomValueAnnotation
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+
+ @CustomValueAnnotation
+ @ValueMapping(source = "STANDARD", target = "SPECIAL")
+ ExternalOrderType duplicateAnnotation(OrderType orderType);
+}
+----
+====
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java
index 963683b5c5..9c412733c4 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java
@@ -5,8 +5,8 @@
*/
package org.mapstruct.ap.internal.model.source;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
@@ -34,7 +34,7 @@ public class ValueMappingOptions {
private final AnnotationValue targetAnnotationValue;
public static void fromMappingsGem(ValueMappingsGem mappingsGem, ExecutableElement method,
- FormattingMessager messager, List mappings) {
+ FormattingMessager messager, Set mappings) {
boolean anyFound = false;
for ( ValueMappingGem mappingGem : mappingsGem.value().get() ) {
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 bda33b6a92..4ad531d334 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
@@ -69,7 +69,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getValueMappings(ExecutableElement method) {
- List valueMappings = new ArrayList<>();
+ Set processedAnnotations = new RepeatValueMappings().getProcessedAnnotations( method );
+ return new ArrayList<>(processedAnnotations);
+ }
- ValueMappingGem mappingAnnotation = ValueMappingGem.instanceOn( method );
- ValueMappingsGem mappingsAnnotation = ValueMappingsGem.instanceOn( method );
+ private class RepeatValueMappings
+ extends RepeatableAnnotations {
- if ( mappingAnnotation != null ) {
- ValueMappingOptions valueMapping = ValueMappingOptions.fromMappingGem( mappingAnnotation );
- if ( valueMapping != null ) {
- valueMappings.add( valueMapping );
- }
+ protected RepeatValueMappings() {
+ super( elementUtils, VALUE_MAPPING_FQN, VALUE_MAPPINGS_FQN );
+ }
+
+ @Override
+ protected ValueMappingGem singularInstanceOn(Element element) {
+ return ValueMappingGem.instanceOn( element );
}
- if ( mappingsAnnotation != null ) {
- ValueMappingOptions.fromMappingsGem( mappingsAnnotation, method, messager, valueMappings );
+ @Override
+ protected ValueMappingsGem multipleInstanceOn(Element element) {
+ return ValueMappingsGem.instanceOn( element );
+ }
+
+ @Override
+ protected void addInstance(ValueMappingGem gem, Element source, Set mappings) {
+ ValueMappingOptions valueMappingOptions = ValueMappingOptions.fromMappingGem( gem );
+ mappings.add( valueMappingOptions );
}
- return valueMappings;
+ @Override
+ protected void addInstances(ValueMappingsGem gems, Element source, Set mappings) {
+ ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings );
+ }
}
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java
new file mode 100644
index 0000000000..f9a7500a35
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.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.value.composition;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.mapstruct.MappingConstants;
+import org.mapstruct.ValueMapping;
+
+/**
+ * @author orange add
+ */
+@Retention( RetentionPolicy.CLASS )
+@ValueMapping(source = "EXTRA", target = "SPECIAL")
+@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
+public @interface CustomValueAnnotation {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java
new file mode 100644
index 0000000000..d6d8ce0bed
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.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.value.composition;
+
+import org.mapstruct.ap.test.value.ExternalOrderType;
+import org.mapstruct.ap.test.value.OrderType;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+/**
+ * @author orange add
+ */
+@IssueKey("3037")
+@WithClasses({
+ ValueMappingCompositionMapper.class,
+ ExternalOrderType.class,
+ OrderType.class,
+ CustomValueAnnotation.class
+})
+public class ValueCompositionTest {
+
+ @ProcessorTest
+ public void shouldValueCompositionSuccess() {
+ ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class );
+ assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.EXTRA ) )
+ .isEqualTo( ExternalOrderType.SPECIAL );
+ assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.NORMAL ) )
+ .isEqualTo( ExternalOrderType.DEFAULT );
+ }
+
+ @ProcessorTest
+ public void duplicateValueMappingAnnotation() {
+ ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class );
+ assertThat( compositionMapper.duplicateAnnotation( OrderType.EXTRA ) )
+ .isEqualTo( ExternalOrderType.SPECIAL );
+ assertThat( compositionMapper.duplicateAnnotation( OrderType.STANDARD ) )
+ .isEqualTo( ExternalOrderType.SPECIAL );
+ assertThat( compositionMapper.duplicateAnnotation( OrderType.NORMAL ) )
+ .isEqualTo( ExternalOrderType.DEFAULT );
+
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java
new file mode 100644
index 0000000000..c3e0e64d69
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.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.value.composition;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ValueMapping;
+import org.mapstruct.ap.test.value.ExternalOrderType;
+import org.mapstruct.ap.test.value.OrderType;
+
+/**
+ * @author orange add
+ */
+@Mapper
+public interface ValueMappingCompositionMapper {
+
+ @CustomValueAnnotation
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+
+ @CustomValueAnnotation
+ @ValueMapping(source = "STANDARD", target = "SPECIAL")
+ ExternalOrderType duplicateAnnotation(OrderType orderType);
+}
From 93f7c3b8eadef4ad37b35ae1bb4e6ac41b09a781 Mon Sep 17 00:00:00 2001
From: Orange Add <48479242+chenzijia12300@users.noreply.github.com>
Date: Fri, 4 Nov 2022 06:22:35 +0800
Subject: [PATCH 047/320] #3015 Fix annotations to the forged methods
---
.../model/AbstractMappingMethodBuilder.java | 4 +
.../ap/internal/model/ValueMappingMethod.java | 20 ++-
.../ap/test/bugs/_3015/Issue3015Mapper.java | 153 ++++++++++++++++++
.../ap/test/bugs/_3015/Issue3015Test.java | 38 +++++
4 files changed, 209 insertions(+), 6 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.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 6efb67ba9c..c23dd50430 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
@@ -14,6 +14,7 @@
import org.mapstruct.ap.internal.util.Strings;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -121,6 +122,9 @@ public ForgedMethodHistory getDescription() {
}
public List getMethodAnnotations() {
+ if ( method instanceof ForgedMethod ) {
+ return Collections.emptyList();
+ }
AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
ctx.getElementUtils(),
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 f61e80c2d4..6b33faa464 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
@@ -6,6 +6,7 @@
package org.mapstruct.ap.internal.model;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -117,13 +118,20 @@ else if ( sourceType.isString() && targetType.isEnumType() ) {
LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List afterMappingMethods =
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
- AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
+ List annotations;
+ if ( method instanceof ForgedMethod ) {
+ annotations = Collections.emptyList();
+ }
+ else {
+ annotations = new ArrayList<>();
+ AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
- ctx.getElementUtils(),
- ctx.getTypeFactory(),
- ctx.getMessager() );
- List annotations = new ArrayList<>();
- annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
+ ctx.getElementUtils(),
+ ctx.getTypeFactory(),
+ ctx.getMessager() );
+
+ annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
+ }
// finally return a mapping
return new ValueMappingMethod( method,
annotations,
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java
new file mode 100644
index 0000000000..1683db8f39
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3015;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.mapstruct.AnnotateWith;
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+
+/**
+ * @author orange add
+ */
+@Mapper
+public interface Issue3015Mapper {
+
+ @AnnotateWith( CustomMethodOnlyAnnotation.class )
+ Target map(Source source);
+
+ class Source {
+
+ private NestedSource nested;
+ private List list;
+ private Stream stream;
+ private AnnotateSourceEnum annotateWithEnum;
+ private Map map;
+
+ public NestedSource getNested() {
+ return nested;
+ }
+
+ public void setNested(NestedSource nested) {
+ this.nested = nested;
+ }
+
+ public List getList() {
+ return list;
+ }
+
+ public void setList(List list) {
+ this.list = list;
+ }
+
+ public Stream getStream() {
+ return stream;
+ }
+
+ public void setStream(Stream stream) {
+ this.stream = stream;
+ }
+
+ public AnnotateSourceEnum getAnnotateWithEnum() {
+ return annotateWithEnum;
+ }
+
+ public void setAnnotateWithEnum(AnnotateSourceEnum annotateWithEnum) {
+ this.annotateWithEnum = annotateWithEnum;
+ }
+
+ public Map getMap() {
+ return map;
+ }
+
+ public void setMap(Map map) {
+ this.map = map;
+ }
+ }
+
+ class Target {
+ private NestedTarget nested;
+ private List list;
+ private Stream stream;
+ private AnnotateTargetEnum annotateWithEnum;
+ private Map map;
+
+ public NestedTarget getNested() {
+ return nested;
+ }
+
+ public void setNested(NestedTarget nested) {
+ this.nested = nested;
+ }
+
+ public List getList() {
+ return list;
+ }
+
+ public void setList(List list) {
+ this.list = list;
+ }
+
+ public Stream getStream() {
+ return stream;
+ }
+
+ public void setStream(Stream stream) {
+ this.stream = stream;
+ }
+
+ public AnnotateTargetEnum getAnnotateWithEnum() {
+ return annotateWithEnum;
+ }
+
+ public void setAnnotateWithEnum(AnnotateTargetEnum annotateWithEnum) {
+ this.annotateWithEnum = annotateWithEnum;
+ }
+
+ public Map getMap() {
+ return map;
+ }
+
+ public void setMap(Map map) {
+ this.map = map;
+ }
+ }
+
+ enum AnnotateSourceEnum {
+ EXISTING;
+ }
+
+ enum AnnotateTargetEnum {
+ EXISTING;
+ }
+
+ class NestedSource {
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ class NestedTarget {
+ private Integer value;
+
+ public Integer getValue() {
+ return value;
+ }
+
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java
new file mode 100644
index 0000000000..96004b8c2d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3015;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author orange add
+ */
+@WithClasses({
+ Issue3015Mapper.class,
+ CustomMethodOnlyAnnotation.class
+})
+class Issue3015Test {
+
+ @ProcessorTest
+ void noNeedPassAnnotationToForgeMethod() {
+ Issue3015Mapper mapper = Mappers.getMapper( Issue3015Mapper.class );
+ Method[] declaredMethods = mapper.getClass().getDeclaredMethods();
+ List annotationMethods = Arrays.stream( declaredMethods )
+ .filter( method -> method.getAnnotation( CustomMethodOnlyAnnotation.class ) != null )
+ .collect( Collectors.toList() );
+ assertThat( annotationMethods ).hasSize( 1 );
+ }
+}
From 16e3ceadec76ad4f3657e8af2f99172241b8dfd3 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Thu, 3 Nov 2022 23:29:47 +0100
Subject: [PATCH 048/320] #2952 Do not treat a getter as an alternative write
accessor when using `CollectionMappingStrategy#TARGET_IMMUTABLE`
---
.../mapstruct/CollectionMappingStrategy.java | 48 ++++++++++++++
.../ap/internal/model/common/Type.java | 7 +++
.../ap/test/bugs/_2952/Issue2952Mapper.java | 62 +++++++++++++++++++
.../ap/test/bugs/_2952/Issue2952Test.java | 29 +++++++++
.../CupboardEntityOnlyGetter.java | 3 +-
...apper.java => CupboardNoSetterMapper.java} | 4 +-
.../immutabletarget/ImmutableProductTest.java | 23 +++----
7 files changed, 159 insertions(+), 17 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java
rename processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/{ErroneousCupboardMapper.java => CupboardNoSetterMapper.java} (80%)
diff --git a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
index afaba97370..0ea3ee7df5 100644
--- a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
+++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
@@ -7,6 +7,54 @@
/**
* Strategy for propagating the value of collection-typed properties from source to target.
+ *
+ * In the table below, the dash {@code -} indicates a property name.
+ * Next, the trailing {@code s} indicates the plural form.
+ * The table explains the options and how they are applied to the presence / absence of a
+ * {@code set-s}, {@code add-} and / or {@code get-s} method on the target object.
+ *
+ * Collection mapping strategy options
+ *
+ * Option
+ * Only target set-s Available
+ * Only target add- Available
+ * Both set-s/add- Available
+ * No set-s/add- Available
+ * Existing Target ({@code @TargetType})
+ *
+ *
+ * {@link #ACCESSOR_ONLY}
+ * set-s
+ * get-s
+ * set-s
+ * get-s
+ * get-s
+ *
+ *
+ * {@link #SETTER_PREFERRED}
+ * set-s
+ * add-
+ * set-s
+ * get-s
+ * get-s
+ *
+ *
+ * {@link #ADDER_PREFERRED}
+ * set-s
+ * add-
+ * add-
+ * get-s
+ * get-s
+ *
+ *
+ * {@link #TARGET_IMMUTABLE}
+ * set-s
+ * exception
+ * set-s
+ * exception
+ * set-s
+ *
+ *
*
* @author Sjaak Derksen
*/
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 6d86aad452..6894906266 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
@@ -788,6 +788,13 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER ) {
// an adder has been found (according strategy) so overrule current choice.
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 ) ) ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java
new file mode 100644
index 0000000000..885d74448f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2952;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
+public interface Issue2952Mapper {
+
+ Issue2952Mapper INSTANCE = Mappers.getMapper( Issue2952Mapper.class );
+
+ Target map(Source source);
+
+ class Source {
+ private final String value;
+
+ public Source(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ class Target {
+ private final Map attributes = new HashMap<>();
+ private final List values = new ArrayList<>();
+ private String value;
+
+ public Map getAttributes() {
+ return Collections.unmodifiableMap( attributes );
+ }
+
+ public List getValues() {
+ return Collections.unmodifiableList( values );
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java
new file mode 100644
index 0000000000..7a517ec979
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2952;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2952")
+@WithClasses({
+ Issue2952Mapper.class
+})
+class Issue2952Test {
+
+ @ProcessorTest
+ void shouldCorrectIgnoreImmutableIterable() {
+ Issue2952Mapper.Target target = Issue2952Mapper.INSTANCE.map( new Issue2952Mapper.Source( "test" ) );
+
+ assertThat( target.getValue() ).isEqualTo( "test" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java
index bbf3a6d24e..1f2f2f00b7 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java
@@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.test.collection.immutabletarget;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -13,7 +14,7 @@
*/
public class CupboardEntityOnlyGetter {
- private List content;
+ private List content = new ArrayList<>();
public List getContent() {
return content;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java
similarity index 80%
rename from processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java
rename to processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java
index 028ac4d765..a6674e629f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java
@@ -15,9 +15,9 @@
* @author Sjaak Derksen
*/
@Mapper( collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE )
-public interface ErroneousCupboardMapper {
+public interface CupboardNoSetterMapper {
- ErroneousCupboardMapper INSTANCE = Mappers.getMapper( ErroneousCupboardMapper.class );
+ CupboardNoSetterMapper INSTANCE = Mappers.getMapper( CupboardNoSetterMapper.class );
void map( CupboardDto in, @MappingTarget CupboardEntityOnlyGetter out );
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java
index b3d2096bc3..de22436296 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,9 +11,6 @@
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,19 +38,17 @@ public void shouldHandleImmutableTarget() {
@ProcessorTest
@WithClasses({
- ErroneousCupboardMapper.class,
+ CupboardNoSetterMapper.class,
CupboardEntityOnlyGetter.class
})
- @ExpectedCompilationOutcome(
- value = CompilationResult.FAILED,
- diagnostics = {
- @Diagnostic(type = ErroneousCupboardMapper.class,
- kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 22,
- message = "No write accessor found for property \"content\" in target type.")
- }
- )
- public void testShouldFailOnPropertyMappingNoPropertySetterOnlyGetter() {
+ public void shouldIgnoreImmutableTarget() {
+ CupboardDto in = new CupboardDto();
+ in.setContent( Arrays.asList( "flour", "peas" ) );
+ CupboardEntityOnlyGetter out = new CupboardEntityOnlyGetter();
+ out.getContent().add( "bread" );
+ CupboardNoSetterMapper.INSTANCE.map( in, out );
+
+ assertThat( out.getContent() ).containsExactly( "bread" );
}
}
From 8894cd5935f238ae32338f49cd4595e4562f87f2 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Fri, 4 Nov 2022 14:21:05 +0100
Subject: [PATCH 049/320] #3057: limit do not allow self to subclassmappings.
(#3063)
* #3057: limit do not allow self to subclassmappings.
* #3057: determine method candidates after all other fields are set in the constructor.
Co-authored-by: Ben Zegveld
Co-authored-by: Filip Hrisafov
---
.../ap/internal/model/BeanMappingMethod.java | 6 +-
.../source/selector/SelectionCriteria.java | 10 ++++
.../creation/MappingResolverImpl.java | 8 ++-
.../ap/test/bugs/_3057/Issue3057Mapper.java | 55 +++++++++++++++++++
.../test/bugs/_3057/Issue3057MapperTest.java | 34 ++++++++++++
5 files changed, 107 insertions(+), 6 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.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 24e160b9de..0ea4ecd197 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
@@ -399,15 +399,13 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
"SubclassMapping for " + sourceType.getFullyQualifiedName() );
SelectionCriteria criteria =
SelectionCriteria
- .forMappingMethods(
+ .forSubclassMappingMethods(
new SelectionParameters(
Collections.emptyList(),
Collections.emptyList(),
subclassMappingOptions.getTarget(),
ctx.getTypeUtils() ).withSourceRHS( rightHandSide ),
- subclassMappingOptions.getMappingControl( ctx.getElementUtils() ),
- null,
- false );
+ subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) );
Assignment assignment = ctx
.getMappingResolver()
.getTargetAssignment(
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 7e12bbd188..2d288dd56e 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
@@ -149,6 +149,10 @@ public boolean isAllow2Steps() {
return allow2Steps;
}
+ public boolean isSelfAllowed() {
+ return type != Type.SELF_NOT_ALLOWED;
+ }
+
public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters,
MappingControl mappingControl,
String targetPropertyName, boolean preferUpdateMapping) {
@@ -173,10 +177,16 @@ public static SelectionCriteria forPresenceCheckMethods(SelectionParameters sele
return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK );
}
+ public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters,
+ MappingControl mappingControl) {
+ return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED );
+ }
+
public enum Type {
PREFER_UPDATE_MAPPING,
OBJECT_FACTORY,
LIFECYCLE_CALLBACK,
PRESENCE_CHECK,
+ SELF_NOT_ALLOWED,
}
}
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 5a2af176f6..12b345d238 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
@@ -196,7 +196,6 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM
this.mappingMethod = mappingMethod;
this.description = description;
- this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod );
this.formattingParameters =
formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters;
this.sourceRHS = sourceRHS;
@@ -207,13 +206,14 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM
this.builtIns = builtIns;
this.messager = messager;
this.reportingLimitAmbiguous = verboseLogging ? Integer.MAX_VALUE : LIMIT_REPORTING_AMBIGUOUS;
+ this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod );
}
// CHECKSTYLE:ON
private List filterPossibleCandidateMethods(List candidateMethods, T mappingMethod) {
List result = new ArrayList<>( candidateMethods.size() );
for ( T candidate : candidateMethods ) {
- if ( isCandidateForMapping( candidate ) && !candidate.equals( mappingMethod )) {
+ if ( isCandidateForMapping( candidate ) && isNotSelfOrSelfAllowed( mappingMethod, candidate )) {
result.add( candidate );
}
}
@@ -221,6 +221,10 @@ private List filterPossibleCandidateMethods(List candid
return result;
}
+ private boolean isNotSelfOrSelfAllowed(T mappingMethod, T candidate) {
+ return selectionCriteria == null || selectionCriteria.isSelfAllowed() || !candidate.equals( mappingMethod );
+ }
+
private Assignment getTargetAssignment(Type sourceType, Type targetType) {
Assignment assignment;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java
new file mode 100644
index 0000000000..d8c444d8c5
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3057;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Ben Zegveld
+ */
+@Mapper
+public interface Issue3057Mapper {
+
+ Issue3057Mapper INSTANCE = Mappers.getMapper( Issue3057Mapper.class );
+
+ class Source {
+ private Source self;
+
+ public Source getSelf() {
+ return self;
+ }
+
+ public void setSelf(Source self) {
+ this.self = self;
+ }
+ }
+
+ class Target {
+ private Target self;
+ private String value;
+
+ public Target getSelf() {
+ return self;
+ }
+
+ public void setSelf(Target self) {
+ this.self = self;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ @Mapping( target = "value", constant = "constantValue" )
+ Target map(Source source);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java
new file mode 100644
index 0000000000..7e64f529f3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3057;
+
+import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Source;
+import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Target;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Ben Zegveld
+ */
+@WithClasses(Issue3057Mapper.class)
+@IssueKey("3057")
+class Issue3057MapperTest {
+
+ @ProcessorTest
+ void mapsSelf() {
+ Source sourceOuter = new Issue3057Mapper.Source();
+ Source sourceInner = new Issue3057Mapper.Source();
+ sourceOuter.setSelf( sourceInner );
+
+ Target targetOuter = Issue3057Mapper.INSTANCE.map( sourceOuter );
+
+ assertThat( targetOuter.getValue() ).isEqualTo( "constantValue" );
+ assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "constantValue" );
+ }
+}
From 82b19b0d8a7addcb137b1d53e9e753fde0957304 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 12 Nov 2022 10:04:03 +0100
Subject: [PATCH 050/320] #3077 Add test case
---
.../ap/test/bugs/_3077/Issue3077Mapper.java | 65 +++++++++++++++++++
.../test/bugs/_3077/Issue3077MapperTest.java | 31 +++++++++
2 files changed, 96 insertions(+)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java
new file mode 100644
index 0000000000..f1a630a444
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3077;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Named;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3077Mapper {
+
+ Issue3077Mapper INSTANCE = Mappers.getMapper( Issue3077Mapper.class );
+
+ class Source {
+ private final String source;
+ private final Source self;
+
+ public Source(String source, Source self) {
+ this.source = source;
+ this.self = self;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public Source getSelf() {
+ return self;
+ }
+ }
+
+ class Target {
+ private String value;
+ private Target self;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public Target getSelf() {
+ return self;
+ }
+
+ public void setSelf(Target self) {
+ this.self = self;
+ }
+
+ }
+
+ @Named("self")
+ @Mapping(target = "value", source = "source")
+ @Mapping(target = "self", qualifiedByName = "self")
+ Target map(Source source);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java
new file mode 100644
index 0000000000..bfba7988aa
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3077;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses(Issue3077Mapper.class)
+@IssueKey("3057")
+class Issue3077MapperTest {
+
+ @ProcessorTest
+ void mapsSelf() {
+ Issue3077Mapper.Source sourceInner = new Issue3077Mapper.Source( "inner", null );
+ Issue3077Mapper.Source sourceOuter = new Issue3077Mapper.Source( "outer", sourceInner );
+
+ Issue3077Mapper.Target targetOuter = Issue3077Mapper.INSTANCE.map( sourceOuter );
+
+ assertThat( targetOuter.getValue() ).isEqualTo( "outer" );
+ assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "inner" );
+ }
+}
From fd27380185fe83feb6d26308c7d358025783ff8f Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 13 Nov 2022 14:22:25 +0100
Subject: [PATCH 051/320] #2953 Add support for globally defining
nullValueMapMappingStrategy and nullValueIterableMappingStrategy
---
.../main/asciidoc/chapter-2-set-up.asciidoc | 24 ++++++++++
.../org/mapstruct/ap/MappingProcessor.java | 18 +++++++-
.../internal/model/source/DefaultOptions.java | 8 ++++
.../mapstruct/ap/internal/option/Options.java | 20 ++++++++-
.../test/nullvaluemapping/CarListMapper.java | 30 +++++++++++++
.../CarListMapperSettingOnMapper.java | 31 +++++++++++++
.../test/nullvaluemapping/CarMapMapper.java | 30 +++++++++++++
.../CarMapMapperSettingOnMapper.java | 31 +++++++++++++
.../NullValueIterableMappingStrategyTest.java | 45 +++++++++++++++++++
.../NullValueMapMappingStrategyTest.java | 45 +++++++++++++++++++
10 files changed, 279 insertions(+), 3 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java
diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
index 601bc42a99..12c399c1f5 100644
--- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
@@ -266,6 +266,30 @@ If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappe
disableBuilders`
|If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers.
|`false`
+
+|`mapstruct.nullValueIterableMappingStrategy`
+|The strategy to be applied when `null` is passed as a source value to an iterable mapping.
+
+Supported values are:
+
+* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
+* `RETURN_DEFAULT`: if `null` is passed then a default value (empty collection) will be returned
+
+If a strategy is given for a specific mapper via `@Mapper#nullValueIterableMappingStrategy()`, the value from the annotation takes precedence.
+If a strategy is given for a specific iterable mapping via `@IterableMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueIterableMappingStrategy()` and the option.
+|`RETURN_NULL`
+
+|`mapstruct.nullValueMapMappingStrategy`
+|The strategy to be applied when `null` is passed as a source value to a map mapping.
+
+Supported values are:
+
+* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
+* `RETURN_DEFAULT`: if `null` is passed then a default value (empty map) will be returned
+
+If a strategy is given for a specific mapper via `@Mapper#nullValueMapMappingStrategy()`, the value from the annotation takes precedence.
+If a strategy is given for a specific map mapping via `@MapMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueMapMappingStrategy()` and the option.
+|`RETURN_NULL`
|===
=== Using MapStruct with the Java Module System
diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
index 2a4af558f2..8c842f007b 100644
--- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
@@ -12,6 +12,7 @@
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;
@@ -32,6 +33,7 @@
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;
+import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.gem.MapperGem;
@@ -87,7 +89,9 @@
MappingProcessor.DEFAULT_COMPONENT_MODEL,
MappingProcessor.DEFAULT_INJECTION_STRATEGY,
MappingProcessor.DISABLE_BUILDERS,
- MappingProcessor.VERBOSE
+ MappingProcessor.VERBOSE,
+ MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY,
+ MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY,
})
public class MappingProcessor extends AbstractProcessor {
@@ -106,6 +110,8 @@ public class MappingProcessor extends AbstractProcessor {
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";
private Options options;
@@ -139,6 +145,9 @@ 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 );
return new Options(
Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
@@ -149,7 +158,12 @@ private Options createOptions() {
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 ) )
+ Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ),
+ nullValueIterableMappingStrategy != null ?
+ NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) :
+ null,
+ nullValueMapMappingStrategy != null ?
+ NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null
);
}
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 5c76e7face..c754d3c395 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
@@ -132,10 +132,18 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() {
}
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
+ NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy();
+ if ( nullValueIterableMappingStrategy != null ) {
+ return nullValueIterableMappingStrategy;
+ }
return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() );
}
public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
+ NullValueMappingStrategyGem nullValueMapMappingStrategy = options.getNullValueMapMappingStrategy();
+ if ( nullValueMapMappingStrategy != null ) {
+ return nullValueMapMappingStrategy;
+ }
return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() );
}
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 65089cc139..a544374c0e 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,7 @@
*/
package org.mapstruct.ap.internal.option;
+import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
/**
@@ -23,14 +24,21 @@ public class Options {
private final String defaultInjectionStrategy;
private final boolean disableBuilders;
private final boolean verbose;
+ private final NullValueMappingStrategyGem nullValueIterableMappingStrategy;
+ private final NullValueMappingStrategyGem nullValueMapMappingStrategy;
+ //CHECKSTYLE:OFF
public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
ReportingPolicyGem unmappedTargetPolicy,
ReportingPolicyGem unmappedSourcePolicy,
String defaultComponentModel, String defaultInjectionStrategy,
boolean alwaysGenerateSpi,
boolean disableBuilders,
- boolean verbose) {
+ boolean verbose,
+ NullValueMappingStrategyGem nullValueIterableMappingStrategy,
+ NullValueMappingStrategyGem nullValueMapMappingStrategy
+ ) {
+ //CHECKSTYLE:ON
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.suppressGeneratorVersionComment = suppressGeneratorVersionComment;
this.unmappedTargetPolicy = unmappedTargetPolicy;
@@ -40,6 +48,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers
this.alwaysGenerateSpi = alwaysGenerateSpi;
this.disableBuilders = disableBuilders;
this.verbose = verbose;
+ this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy;
+ this.nullValueMapMappingStrategy = nullValueMapMappingStrategy;
}
public boolean isSuppressGeneratorTimestamp() {
@@ -77,4 +87,12 @@ public boolean isDisableBuilders() {
public boolean isVerbose() {
return verbose;
}
+
+ public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
+ return nullValueIterableMappingStrategy;
+ }
+
+ public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
+ return nullValueMapMappingStrategy;
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java
new file mode 100644
index 0000000000..d239f9ace7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import java.util.List;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface CarListMapper {
+
+ CarListMapper INSTANCE = Mappers.getMapper( CarListMapper.class );
+
+ @Mapping(target = "seatCount", ignore = true)
+ @Mapping(target = "model", ignore = true)
+ @Mapping(target = "catalogId", ignore = true)
+ CarDto map(Car car);
+
+ List carsToCarDtoList(List cars);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java
new file mode 100644
index 0000000000..860b17654a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import java.util.List;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValueMappingStrategy;
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL)
+public interface CarListMapperSettingOnMapper {
+
+ CarListMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarListMapperSettingOnMapper.class );
+
+ @Mapping(target = "seatCount", ignore = true)
+ @Mapping(target = "model", ignore = true)
+ @Mapping(target = "catalogId", ignore = true)
+ CarDto map(Car car);
+
+ List carsToCarDtoList(List cars);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java
new file mode 100644
index 0000000000..0227949f4d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import java.util.Map;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface CarMapMapper {
+
+ CarMapMapper INSTANCE = Mappers.getMapper( CarMapMapper.class );
+
+ @Mapping(target = "seatCount", ignore = true)
+ @Mapping(target = "model", ignore = true)
+ @Mapping(target = "catalogId", ignore = true)
+ CarDto map(Car car);
+
+ Map carsToCarDtoMap(Map cars);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java
new file mode 100644
index 0000000000..adb99887bd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import java.util.Map;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValueMappingStrategy;
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL)
+public interface CarMapMapperSettingOnMapper {
+
+ CarMapMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapMapperSettingOnMapper.class );
+
+ @Mapping(target = "seatCount", ignore = true)
+ @Mapping(target = "model", ignore = true)
+ @Mapping(target = "catalogId", ignore = true)
+ CarDto map(Car car);
+
+ Map carsToCarDtoMap(Map cars);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java
new file mode 100644
index 0000000000..c4acecd64f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ CarDto.class,
+ Car.class
+})
+@IssueKey("2953")
+public class NullValueIterableMappingStrategyTest {
+
+ @ProcessorTest
+ @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default")
+ @WithClasses({
+ CarListMapper.class
+ })
+ void globalNullIterableMappingStrategy() {
+ assertThat( CarListMapper.INSTANCE.carsToCarDtoList( null ) ).isEmpty();
+ }
+
+ @ProcessorTest
+ @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default")
+ @WithClasses({
+ CarListMapperSettingOnMapper.class
+ })
+ void globalNullMapMappingStrategyWithOverrideInMapper() {
+ // Explicit definition in @Mapper should override global
+ assertThat( CarListMapperSettingOnMapper.INSTANCE.carsToCarDtoList( null ) ).isNull();
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java
new file mode 100644
index 0000000000..33b5942f85
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.nullvaluemapping;
+
+import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
+import org.mapstruct.ap.test.nullvaluemapping.source.Car;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ CarDto.class,
+ Car.class
+})
+@IssueKey("2953")
+public class NullValueMapMappingStrategyTest {
+
+ @ProcessorTest
+ @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default")
+ @WithClasses({
+ CarMapMapper.class
+ })
+ void globalNullMapMappingStrategy() {
+ assertThat( CarMapMapper.INSTANCE.carsToCarDtoMap( null ) ).isEmpty();
+ }
+
+ @ProcessorTest
+ @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default")
+ @WithClasses({
+ CarMapMapperSettingOnMapper.class
+ })
+ void globalNullMapMappingStrategyWithOverrideInMapper() {
+ // Explicit definition in @Mapper should override global
+ assertThat( CarMapMapperSettingOnMapper.INSTANCE.carsToCarDtoMap( null ) ).isNull();
+ }
+}
From a7ba12676d237957be0753076f436d7974c9f7f4 Mon Sep 17 00:00:00 2001
From: Claudio Nave
Date: Sun, 5 Feb 2023 12:17:02 +0100
Subject: [PATCH 052/320] #3110 Fix throws declaration for ValueMapping
annotated methods (#3122)
#3110 Fix throws declaration for ValueMapping annotated methods
---
.../ap/internal/model/ValueMappingMethod.ftl | 10 ++++++-
.../ap/test/bugs/_3110/Issue3110Mapper.java | 29 +++++++++++++++++++
.../test/bugs/_3110/Issue3110MapperTest.java | 27 +++++++++++++++++
3 files changed, 65 insertions(+), 1 deletion(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
index 2c3e2b6410..016cfcd182 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
@@ -10,7 +10,7 @@
<#nt><@includeModel object=annotation/>
#list>
<#if overridden>@Override#if>
-<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, #if>#list>) {
+<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, #if>#list>)<@throws/> {
<#list beforeMappingReferencesWithoutMappingTarget as callback>
<@includeModel object=callback targetBeanName=resultName targetType=resultType/>
<#if !callback_has_next>
@@ -69,3 +69,11 @@
#if>
@compress>
#macro>
+<#macro throws>
+ <#if (thrownTypes?size > 0)><#lt> throws #if><@compress single_line=true>
+ <#list thrownTypes as exceptionType>
+ <@includeModel object=exceptionType/>
+ <#if exceptionType_has_next>, #if><#t>
+ #list>
+ @compress>
+#macro>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java
new file mode 100644
index 0000000000..12dd823760
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3110;
+
+import org.mapstruct.EnumMapping;
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface Issue3110Mapper {
+ enum SourceEnum {
+ FOO, BAR
+ }
+
+ enum TargetEnum {
+ FOO, BAR
+ }
+
+ class CustomCheckedException extends Exception {
+ public CustomCheckedException(String message) {
+ super( message );
+ }
+ }
+
+ @EnumMapping(unexpectedValueMappingException = CustomCheckedException.class)
+ TargetEnum map(SourceEnum sourceEnum) throws CustomCheckedException;
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java
new file mode 100644
index 0000000000..3b256ba100
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3110;
+
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+@WithClasses({
+ Issue3110Mapper.class
+})
+@IssueKey("3110")
+class Issue3110MapperTest {
+ @RegisterExtension
+ final GeneratedSource generatedSource = new GeneratedSource();
+
+ @ProcessorTest
+ void throwsException() {
+ generatedSource.forMapper( Issue3110Mapper.class ).content()
+ .contains( "throws CustomCheckedException" );
+ }
+}
From 89db26a1af35fb759a2fa74b8bc7586cb743c454 Mon Sep 17 00:00:00 2001
From: Claudio Nave
Date: Fri, 17 Mar 2023 09:06:13 +0100
Subject: [PATCH 053/320] #3119 Add qualifiedBy and qualifiedByName to
SubclassMapping annotation
---
.../main/java/org/mapstruct/Qualifier.java | 1 +
.../java/org/mapstruct/SubclassMapping.java | 26 ++
...apter-10-advanced-mapping-options.asciidoc | 2 +
.../ap/internal/model/BeanMappingMethod.java | 20 +-
.../model/source/SubclassMappingOptions.java | 33 +-
.../subclassmapping/qualifier/Composer.java | 18 ++
.../qualifier/ComposerDto.java | 18 ++
.../ErroneousSubclassQualifiedByMapper.java | 16 +
...rroneousSubclassQualifiedByNameMapper.java | 16 +
.../test/subclassmapping/qualifier/Light.java | 19 ++
.../qualifier/NonExistent.java | 19 ++
.../subclassmapping/qualifier/Rossini.java | 20 ++
.../subclassmapping/qualifier/RossiniDto.java | 20 ++
.../qualifier/RossiniMapper.java | 50 +++
.../qualifier/SubclassQualifiedByMapper.java | 37 +++
.../SubclassQualifiedByNameMapper.java | 37 +++
.../SubclassQualifierMapperTest.java | 297 ++++++++++++++++++
.../subclassmapping/qualifier/Unused.java | 19 ++
.../subclassmapping/qualifier/Vivaldi.java | 20 ++
.../subclassmapping/qualifier/VivaldiDto.java | 20 ++
.../qualifier/VivaldiMapper.java | 50 +++
21 files changed, 740 insertions(+), 18 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java
diff --git a/core/src/main/java/org/mapstruct/Qualifier.java b/core/src/main/java/org/mapstruct/Qualifier.java
index be51f49e04..9e18c3e420 100644
--- a/core/src/main/java/org/mapstruct/Qualifier.java
+++ b/core/src/main/java/org/mapstruct/Qualifier.java
@@ -21,6 +21,7 @@
* {@link IterableMapping#qualifiedBy() }
* {@link MapMapping#keyQualifiedBy() }
* {@link MapMapping#valueQualifiedBy() }
+ * {@link SubclassMapping#qualifiedBy() }
*
* Example:
*
diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java
index bfd4f9bec5..ccf8d4d472 100644
--- a/core/src/main/java/org/mapstruct/SubclassMapping.java
+++ b/core/src/main/java/org/mapstruct/SubclassMapping.java
@@ -5,6 +5,7 @@
*/
package org.mapstruct;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
@@ -81,4 +82,29 @@
* @return the target subclass to map the source to.
*/
Class> target();
+
+ /**
+ * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
+ * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
+ * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
+ *
+ * @return the qualifiers
+ * @see Qualifier
+ */
+ Class extends Annotation>[] qualifiedBy() default {};
+
+ /**
+ * String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
+ * only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
+ * for each of the specified qualifier names.
+ *
+ * Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
+ * are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
+ * number of qualifiers as no custom annotation types are needed.
+ *
+ * @return One or more qualifier name(s)
+ * @see #qualifiedBy()
+ * @see Named
+ */
+ String[] qualifiedByName() default {};
}
diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
index a6e42b6d48..87dffce31c 100644
--- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
@@ -141,6 +141,8 @@ In the case that the `Fruit` is an abstract class or an interface, you would get
To allow mappings for abstract classes or interfaces you need to set the `subclassExhaustiveStrategy` to `RUNTIME_EXCEPTION`, you can do this at the `@MapperConfig`, `@Mapper` or `@BeanMapping` annotations. If you then pass a `GrapeDto` an `IllegalArgumentException` will be thrown because it is unknown how to map a `GrapeDto`.
Adding the missing (`@SubclassMapping`) for it will fix that.
+<> can be used to further control which methods may be chosen to map a specific subclass. For that, you will need to use one of `SubclassMapping#qualifiedByName` or `SubclassMapping#qualifiedBy`.
+
[TIP]
====
If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings.
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 0ea4ecd197..29d27c8d68 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
@@ -399,13 +399,10 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
"SubclassMapping for " + sourceType.getFullyQualifiedName() );
SelectionCriteria criteria =
SelectionCriteria
- .forSubclassMappingMethods(
- new SelectionParameters(
- Collections.emptyList(),
- Collections.emptyList(),
- subclassMappingOptions.getTarget(),
- ctx.getTypeUtils() ).withSourceRHS( rightHandSide ),
- subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) );
+ .forSubclassMappingMethods(
+ subclassMappingOptions.getSelectionParameters().withSourceRHS( rightHandSide ),
+ subclassMappingOptions.getMappingControl( ctx.getElementUtils() )
+ );
Assignment assignment = ctx
.getMappingResolver()
.getTargetAssignment(
@@ -424,10 +421,13 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
String sourceArgument = null;
for ( Parameter parameter : method.getSourceParameters() ) {
if ( ctx
- .getTypeUtils()
- .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) {
+ .getTypeUtils()
+ .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) {
sourceArgument = parameter.getName();
- assignment.setSourceLocalVarName( "(" + sourceType.createReferenceName() + ") " + sourceArgument );
+ if ( assignment != null ) {
+ assignment.setSourceLocalVarName(
+ "(" + sourceType.createReferenceName() + ") " + sourceArgument );
+ }
}
}
return new SubclassMapping( sourceType, sourceArgument, targetType, assignment );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
index 08df8083bc..6ed1117ebd 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
@@ -33,12 +33,15 @@ public class SubclassMappingOptions extends DelegatingOptions {
private final TypeMirror source;
private final TypeMirror target;
private final TypeUtils typeUtils;
+ private final SelectionParameters selectionParameters;
- public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next) {
+ public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next,
+ SelectionParameters selectionParameters) {
super( next );
this.source = source;
this.target = target;
this.typeUtils = typeUtils;
+ this.selectionParameters = selectionParameters;
}
@Override
@@ -117,6 +120,10 @@ public TypeMirror getTarget() {
return target;
}
+ public SelectionParameters getSelectionParameters() {
+ return selectionParameters;
+ }
+
public static void addInstances(SubclassMappingsGem gem, ExecutableElement method,
BeanMappingOptions beanMappingOptions, FormattingMessager messager,
TypeUtils typeUtils, Set mappings,
@@ -154,6 +161,12 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle
TypeMirror sourceSubclass = subclassMapping.source().getValue();
TypeMirror targetSubclass = subclassMapping.target().getValue();
+ SelectionParameters selectionParameters = new SelectionParameters(
+ subclassMapping.qualifiedBy().get(),
+ subclassMapping.qualifiedByName().get(),
+ targetSubclass,
+ typeUtils
+ );
mappings
.add(
@@ -161,20 +174,24 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle
sourceSubclass,
targetSubclass,
typeUtils,
- beanMappingOptions ) );
+ beanMappingOptions,
+ selectionParameters
+ ) );
}
public static List copyForInverseInheritance(Set subclassMappings,
- BeanMappingOptions beanMappingOptions) {
+ BeanMappingOptions beanMappingOptions) {
// we are not interested in keeping it unique at this point.
List mappings = new ArrayList<>();
for ( SubclassMappingOptions subclassMapping : subclassMappings ) {
mappings.add(
- new SubclassMappingOptions(
- subclassMapping.target,
- subclassMapping.source,
- subclassMapping.typeUtils,
- beanMappingOptions ) );
+ new SubclassMappingOptions(
+ subclassMapping.target,
+ subclassMapping.source,
+ subclassMapping.typeUtils,
+ beanMappingOptions,
+ subclassMapping.selectionParameters
+ ) );
}
return mappings;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java
new file mode 100644
index 0000000000..116304ae5d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.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.subclassmapping.qualifier;
+
+public class Composer {
+ 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/subclassmapping/qualifier/ComposerDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java
new file mode 100644
index 0000000000..1f9d27681f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.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.subclassmapping.qualifier;
+
+public class ComposerDto {
+ 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/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java
new file mode 100644
index 0000000000..26cdd4715e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+
+@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class })
+public interface ErroneousSubclassQualifiedByMapper {
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = NonExistent.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class)
+ ComposerDto toDto(Composer composer);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java
new file mode 100644
index 0000000000..23887baf0f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+
+@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class })
+public interface ErroneousSubclassQualifiedByNameMapper {
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedByName = "non-existent")
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class)
+ ComposerDto toDto(Composer composer);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java
new file mode 100644
index 0000000000..0a24c561c9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.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.subclassmapping.qualifier;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.Qualifier;
+
+@Qualifier
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Light {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java
new file mode 100644
index 0000000000..d1d74f76f7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.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.subclassmapping.qualifier;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.Qualifier;
+
+@Qualifier
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface NonExistent {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java
new file mode 100644
index 0000000000..cfd41847d0
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.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.subclassmapping.qualifier;
+
+import java.util.List;
+
+public class Rossini extends Composer {
+ private List crescendo;
+
+ public List getCrescendo() {
+ return crescendo;
+ }
+
+ public void setCrescendo(List crescendo) {
+ this.crescendo = crescendo;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java
new file mode 100644
index 0000000000..355de1876e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.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.subclassmapping.qualifier;
+
+import java.util.List;
+
+public class RossiniDto extends ComposerDto {
+ private List crescendo;
+
+ public List getCrescendo() {
+ return crescendo;
+ }
+
+ public void setCrescendo(List crescendo) {
+ this.crescendo = crescendo;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java
new file mode 100644
index 0000000000..c4bb5e2497
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Named;
+
+@Mapper
+public interface RossiniMapper {
+ RossiniDto toDto(Rossini rossini);
+
+ @Light
+ @Mapping(target = "crescendo", ignore = true)
+ RossiniDto toDtoLight(Rossini source);
+
+ @Named("light")
+ @Mapping(target = "crescendo", ignore = true)
+ RossiniDto toDtoLightNamed(Rossini source);
+
+ @Unused
+ @BeanMapping(ignoreByDefault = true)
+ RossiniDto toDtoUnused(Rossini source);
+
+ @Named("unused")
+ @BeanMapping(ignoreByDefault = true)
+ RossiniDto toDtoUnusedNamed(Rossini source);
+
+ Rossini fromDto(RossiniDto rossini);
+
+ @Light
+ @Mapping(target = "crescendo", ignore = true)
+ Rossini fromDtoLight(RossiniDto source);
+
+ @Named("light")
+ @Mapping(target = "crescendo", ignore = true)
+ Rossini fromDtoLightNamed(RossiniDto source);
+
+ @Unused
+ @BeanMapping(ignoreByDefault = true)
+ Rossini fromDtoUnused(RossiniDto source);
+
+ @Named("unused")
+ @BeanMapping(ignoreByDefault = true)
+ Rossini fromDtoUnusedNamed(RossiniDto source);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java
new file mode 100644
index 0000000000..7f486804fd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class })
+public interface SubclassQualifiedByMapper {
+ SubclassQualifiedByMapper INSTANCE = Mappers.getMapper( SubclassQualifiedByMapper.class );
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class)
+ ComposerDto toDto(Composer composer);
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = Light.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedBy = Light.class)
+ ComposerDto toDtoLight(Composer composer);
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedBy = Light.class)
+ ComposerDto toDtoLightJustVivaldi(Composer composer);
+
+ @InheritInverseConfiguration(name = "toDto")
+ Composer fromDto(ComposerDto composer);
+
+ @InheritInverseConfiguration(name = "toDtoLight")
+ Composer fromDtoLight(ComposerDto composer);
+
+ @InheritInverseConfiguration(name = "toDtoLightJustVivaldi")
+ Composer fromDtoLightJustVivaldi(ComposerDto composer);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java
new file mode 100644
index 0000000000..e4816ef732
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class })
+public interface SubclassQualifiedByNameMapper {
+ SubclassQualifiedByNameMapper INSTANCE = Mappers.getMapper( SubclassQualifiedByNameMapper.class );
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class)
+ ComposerDto toDto(Composer composer);
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedByName = "light")
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedByName = "light")
+ ComposerDto toDtoLight(Composer composer);
+
+ @SubclassMapping(source = Rossini.class, target = RossiniDto.class)
+ @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedByName = "light")
+ ComposerDto toDtoLightJustVivaldi(Composer composer);
+
+ @InheritInverseConfiguration(name = "toDto")
+ Composer fromDto(ComposerDto composer);
+
+ @InheritInverseConfiguration(name = "toDtoLight")
+ Composer fromDtoLight(ComposerDto composer);
+
+ @InheritInverseConfiguration(name = "toDtoLightJustVivaldi")
+ Composer fromDtoLightJustVivaldi(ComposerDto composer);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
new file mode 100644
index 0000000000..df67c27013
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright MapStruct 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.qualifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@WithClasses({
+ Composer.class,
+ ComposerDto.class,
+ Rossini.class,
+ RossiniDto.class,
+ Vivaldi.class,
+ VivaldiDto.class,
+ VivaldiDto.class,
+ VivaldiDto.class,
+ Light.class,
+ Unused.class,
+ NonExistent.class,
+ RossiniMapper.class,
+ VivaldiMapper.class
+})
+@IssueKey("3119")
+public class SubclassQualifierMapperTest {
+ @RegisterExtension
+ final GeneratedSource generatedSource = new GeneratedSource();
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByMapper.class)
+ void subclassQualifiedBy() {
+ Rossini rossini = buildRossini();
+
+ Vivaldi vivaldi = buildVivaldi();
+
+ RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDto( rossini );
+ RossiniDto rossiniDtoLight = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDtoLight( rossini );
+
+ VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDto( vivaldi );
+ VivaldiDto vivaldiDtoLight = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDtoLight( vivaldi );
+
+ assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+ assertThat( rossiniDtoLight.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDtoLight.getCrescendo() ).isNull();
+
+ assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDto.getSeasons() ).containsExactly( "spring", "winter" );
+ assertThat( vivaldiDtoLight.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDtoLight.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByMapper.class)
+ void subclassQualifiedByOnlyOne() {
+ Rossini rossini = buildRossini();
+
+ Vivaldi vivaldi = buildVivaldi();
+
+ RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDtoLightJustVivaldi( rossini );
+
+ VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDtoLightJustVivaldi( vivaldi );
+
+ assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+
+ assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDto.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByNameMapper.class)
+ void subclassQualifiedByName() {
+ Rossini rossini = buildRossini();
+
+ Vivaldi vivaldi = buildVivaldi();
+
+ RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDto( rossini );
+ RossiniDto rossiniDtoLight = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLight( rossini );
+
+ VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDto( vivaldi );
+ VivaldiDto vivaldiDtoLight = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLight( vivaldi );
+
+ assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+ assertThat( rossiniDtoLight.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDtoLight.getCrescendo() ).isNull();
+
+ assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDto.getSeasons() ).containsExactly( "spring", "winter" );
+ assertThat( vivaldiDtoLight.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDtoLight.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByNameMapper.class)
+ void subclassQualifiedByNameOnlyOne() {
+ Rossini rossini = buildRossini();
+
+ Vivaldi vivaldi = buildVivaldi();
+
+ RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLightJustVivaldi( rossini );
+
+ VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLightJustVivaldi( vivaldi );
+
+ assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+
+ assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiDto.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByMapper.class)
+ void subclassQualifiedByInverse() {
+ RossiniDto rossiniDto = buildRossiniDto();
+
+ VivaldiDto vivaldiDto = buildVivaldiDto();
+
+ Rossini rossini = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDto( rossiniDto );
+ Rossini rossiniLight = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDtoLight( rossiniDto );
+
+ Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDto( vivaldiDto );
+ Vivaldi vivaldiLight = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDtoLight( vivaldiDto );
+
+ assertThat( rossini.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+ assertThat( rossiniLight.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniLight.getCrescendo() ).isNull();
+
+ assertThat( vivaldi.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldi.getSeasons() ).containsExactly( "spring", "winter" );
+ assertThat( vivaldiLight.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiLight.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByMapper.class)
+ void subclassQualifiedByOnlyOneInverse() {
+ RossiniDto rossiniDto = buildRossiniDto();
+
+ VivaldiDto vivaldiDto = buildVivaldiDto();
+
+ Rossini rossini = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDtoLightJustVivaldi( rossiniDto );
+
+ Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDtoLightJustVivaldi( vivaldiDto );
+
+ assertThat( rossini.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+
+ assertThat( vivaldi.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldi.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByNameMapper.class)
+ void subclassQualifiedByNameInverse() {
+ RossiniDto rossiniDto = buildRossiniDto();
+
+ VivaldiDto vivaldiDto = buildVivaldiDto();
+
+ Rossini rossini = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDto( rossiniDto );
+ Rossini rossiniLight = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLight( rossiniDto );
+
+ Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDto( vivaldiDto );
+ Vivaldi vivaldiLight = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLight( vivaldiDto );
+
+ assertThat( rossini.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+ assertThat( rossiniLight.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossiniLight.getCrescendo() ).isNull();
+
+ assertThat( vivaldi.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldi.getSeasons() ).containsExactly( "spring", "winter" );
+ assertThat( vivaldiLight.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldiLight.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByNameMapper.class)
+ void subclassQualifiedByNameOnlyOneInverse() {
+ RossiniDto rossiniDto = buildRossiniDto();
+
+ VivaldiDto vivaldiDto = buildVivaldiDto();
+
+ Rossini rossini = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLightJustVivaldi( rossiniDto );
+
+ Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLightJustVivaldi( vivaldiDto );
+
+ assertThat( rossini.getName() ).isEqualTo( "gioacchino" );
+ assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" );
+
+ assertThat( vivaldi.getName() ).isEqualTo( "antonio" );
+ assertThat( vivaldi.getSeasons() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByMapper.class)
+ void subclassQualifiedByDoesNotContainUnused() {
+ generatedSource.forMapper( SubclassQualifiedByMapper.class )
+ .content()
+ .doesNotContain( "DtoUnused" );
+ }
+
+ @ProcessorTest
+ @WithClasses(SubclassQualifiedByNameMapper.class)
+ void subclassQualifiedByNameDoesNotContainUnused() {
+ generatedSource.forMapper( SubclassQualifiedByNameMapper.class )
+ .content()
+ .doesNotContain( "DtoUnused" );
+ }
+
+ @ProcessorTest
+ @WithClasses(ErroneousSubclassQualifiedByMapper.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = @Diagnostic(
+ type = ErroneousSubclassQualifiedByMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 15,
+ message = "Qualifier error. No method found annotated with: [ @NonExistent ]. " +
+ "See https://mapstruct.org/faq/#qualifier for more info."
+ )
+ )
+ void subclassQualifiedByErroneous() {
+ }
+
+ @ProcessorTest
+ @WithClasses(ErroneousSubclassQualifiedByNameMapper.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = @Diagnostic(
+ type = ErroneousSubclassQualifiedByNameMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 15,
+ message = "Qualifier error. No method found annotated with @Named#value: [ non-existent ]. " +
+ "See https://mapstruct.org/faq/#qualifier for more info."
+ )
+ )
+ void subclassQualifiedByNameErroneous() {
+ }
+
+ private Rossini buildRossini() {
+ Rossini rossini = new Rossini();
+ rossini.setName( "gioacchino" );
+ List crescendo = new ArrayList<>();
+ crescendo.add( "andante" );
+ crescendo.add( "allegro" );
+ crescendo.add( "vivace" );
+ rossini.setCrescendo( crescendo );
+ return rossini;
+ }
+
+ private Vivaldi buildVivaldi() {
+ Vivaldi vivaldi = new Vivaldi();
+ vivaldi.setName( "antonio" );
+ List season = new ArrayList<>();
+ season.add( "spring" );
+ season.add( "winter" );
+ vivaldi.setSeasons( season );
+ return vivaldi;
+ }
+
+ private RossiniDto buildRossiniDto() {
+ RossiniDto rossiniDto = new RossiniDto();
+ rossiniDto.setName( "gioacchino" );
+ List crescendo = new ArrayList<>();
+ crescendo.add( "andante" );
+ crescendo.add( "allegro" );
+ crescendo.add( "vivace" );
+ rossiniDto.setCrescendo( crescendo );
+ return rossiniDto;
+ }
+
+ private VivaldiDto buildVivaldiDto() {
+ VivaldiDto vivaldiDto = new VivaldiDto();
+ vivaldiDto.setName( "antonio" );
+ List season = new ArrayList<>();
+ season.add( "spring" );
+ season.add( "winter" );
+ vivaldiDto.setSeasons( season );
+ return vivaldiDto;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java
new file mode 100644
index 0000000000..10da0434e2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.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.subclassmapping.qualifier;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.Qualifier;
+
+@Qualifier
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Unused {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java
new file mode 100644
index 0000000000..75df3d66f7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.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.subclassmapping.qualifier;
+
+import java.util.List;
+
+public class Vivaldi extends Composer {
+ private List seasons;
+
+ public List getSeasons() {
+ return seasons;
+ }
+
+ public void setSeasons(List seasons) {
+ this.seasons = seasons;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java
new file mode 100644
index 0000000000..79cd5fef33
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.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.subclassmapping.qualifier;
+
+import java.util.List;
+
+public class VivaldiDto extends ComposerDto {
+ private List seasons;
+
+ public List getSeasons() {
+ return seasons;
+ }
+
+ public void setSeasons(List seasons) {
+ this.seasons = seasons;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java
new file mode 100644
index 0000000000..2b3b1fa3e7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.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.subclassmapping.qualifier;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Named;
+
+@Mapper
+public interface VivaldiMapper {
+ VivaldiDto toDto(Vivaldi vivaldi);
+
+ @Light
+ @Mapping(target = "seasons", ignore = true)
+ VivaldiDto toDtoLight(Vivaldi vivaldi);
+
+ @Named("light")
+ @Mapping(target = "seasons", ignore = true)
+ VivaldiDto toDtoLightNamed(Vivaldi vivaldi);
+
+ @Unused
+ @BeanMapping(ignoreByDefault = true)
+ VivaldiDto toDtoUnused(Vivaldi vivaldi);
+
+ @Named("unused")
+ @BeanMapping(ignoreByDefault = true)
+ VivaldiDto toDtoUnusedNamed(Vivaldi vivaldi);
+
+ Vivaldi fromDto(VivaldiDto vivaldi);
+
+ @Light
+ @Mapping(target = "seasons", ignore = true)
+ Vivaldi fromDtoLight(VivaldiDto vivaldi);
+
+ @Named("light")
+ @Mapping(target = "seasons", ignore = true)
+ Vivaldi fromDtoLightNamed(VivaldiDto vivaldi);
+
+ @Unused
+ @BeanMapping(ignoreByDefault = true)
+ Vivaldi fromDtoUnused(VivaldiDto vivaldi);
+
+ @Named("unused")
+ @BeanMapping(ignoreByDefault = true)
+ Vivaldi fromDtoUnusedNamed(VivaldiDto vivaldi);
+}
From 62c7ce1cdf0ccc5528dca84decaf07cbecadaed4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?=
Date: Thu, 13 Apr 2023 21:48:13 +0200
Subject: [PATCH 054/320] #3112 Document in the reference
guide
---
.../chapter-8-mapping-values.asciidoc | 63 +++++++++++++++++--
1 file changed, 59 insertions(+), 4 deletions(-)
diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
index ce16b33ecd..b9cb308d4f 100644
--- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
@@ -65,7 +65,7 @@ public class OrderMapperImpl implements OrderMapper {
----
====
By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated
-mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs.
+mapping method will throw an `IllegalStateException` if for some reason an unrecognized source value occurs.
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings and only applies to the source. It comes in two flavors: `` and ``. They cannot be used at the same time.
@@ -75,14 +75,18 @@ MapStruct will *not* attempt such name based mapping for `` and di
MapStruct is able to handle `null` sources and `null` targets by means of the `` keyword.
+In addition, the constant value `` can be used for throwing an exception for particular value mappings. This value is only applicable to `ValueMapping#target()` and not `ValueMapping#source()` since MapStruct can't map from exceptions.
+
[TIP]
====
-Constants for ``, `` and `` are available in the `MappingConstants` class.
+Constants for ``, ``, `` and `` are available in the `MappingConstants` class.
====
Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `` and `` will be ignored in that case.
-.Enum mapping method, and
+The following code snippets exemplify the use of the aforementioned constants.
+
+.Enum mapping method, `` and ``
====
[source, java, linenums]
[subs="verbatim,attributes"]
@@ -102,7 +106,7 @@ public interface SpecialOrderMapper {
----
====
-.Enum mapping method result, and
+.Enum mapping method result, `` and ``
====
[source, java, linenums]
[subs="verbatim,attributes"]
@@ -137,6 +141,55 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper {
*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``.
+.Enum mapping method with ``
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface SpecialOrderMapper {
+
+ SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
+
+ @ValueMappings({
+ @ValueMapping( source = "STANDARD", target = "DEFAULT" ),
+ @ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
+ })
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+}
+----
+====
+
+.Enum mapping method with `` result
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class SpecialOrderMapperImpl implements SpecialOrderMapper {
+
+ @Override
+ public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
+ if ( orderType == null ) {
+ return null;
+ }
+
+ ExternalOrderType externalOrderType;
+
+ switch ( orderType ) {
+ case STANDARD: externalOrderType = ExternalOrderType.DEFAULT;
+ break;
+ case C2C: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
+ default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
+ }
+
+ return externalOrderType;
+ }
+}
+----
+====
+
+
[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.
@@ -152,6 +205,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Difference: `` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.
4. Difference: Given 1. and 3. there will never be unmapped values.
+5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for particular enum values.
*`String` to enum*
@@ -159,6 +213,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Similarity: `` will create a mapping for each target enum constant and proceed to the switch/default clause value.
4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `` or ` will result in a warning.
+5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for any arbitrary `String` value.
=== Custom name transformation
From 2be1f306a1179b437e850f8ac99ceb492373dced Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Fri, 17 Mar 2023 11:23:53 +0100
Subject: [PATCH 055/320] #3135 BeanMapping#mappingControl should be inherited
by forged methods
---
.../model/source/BeanMappingOptions.java | 11 ++++++++++
.../model/source/MappingMethodOptions.java | 2 +-
.../model/source/SelectionParameters.java | 4 ++++
.../CloningBeanMappingMapper.java | 21 +++++++++++++++++++
.../mappingcontrol/MappingControlTest.java | 15 +++++++++++++
5 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java
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 90ad0aabb4..24811768e7 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
@@ -54,6 +54,17 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping)
return options;
}
+ public static BeanMappingOptions forForgedMethods(BeanMappingOptions beanMapping) {
+ BeanMappingOptions options = new BeanMappingOptions(
+ beanMapping.selectionParameters != null ?
+ SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : null,
+ Collections.emptyList(),
+ beanMapping.beanMapping,
+ beanMapping
+ );
+ return options;
+ }
+
public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) {
return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions );
}
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 a1a64872a9..9f5da7b8de 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
@@ -365,7 +365,7 @@ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethod
options.mappings,
options.iterableMapping,
options.mapMapping,
- BeanMappingOptions.empty( options.beanMapping.next() ),
+ BeanMappingOptions.forForgedMethods( options.beanMapping ),
options.enumMappingOptions,
options.valueMappings,
Collections.emptySet(),
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 fb76c13629..7237668e2c 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
@@ -39,6 +39,10 @@ public class SelectionParameters {
* @return the selection parameters based on the given ones
*/
public static SelectionParameters forInheritance(SelectionParameters selectionParameters) {
+ return withoutResultType( selectionParameters );
+ }
+
+ public static SelectionParameters withoutResultType(SelectionParameters selectionParameters) {
return new SelectionParameters(
selectionParameters.qualifiers,
selectionParameters.qualifyingNames,
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java
new file mode 100644
index 0000000000..a5c05d974d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.control.DeepClone;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface CloningBeanMappingMapper {
+
+ CloningBeanMappingMapper INSTANCE = Mappers.getMapper( CloningBeanMappingMapper.class );
+
+ @BeanMapping(mappingControl = DeepClone.class)
+ FridgeDTO clone(FridgeDTO in);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
index d542801799..c9c2e095f5 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
@@ -69,6 +69,21 @@ public void testDeepCloning() {
assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" );
}
+ @ProcessorTest
+ @IssueKey("3135")
+ @WithClasses(CloningBeanMappingMapper.class)
+ public void testDeepCloningViaBeanMapping() {
+
+ FridgeDTO in = createFridgeDTO();
+ FridgeDTO out = CloningBeanMappingMapper.INSTANCE.clone( in );
+
+ assertThat( out ).isNotNull();
+ assertThat( out.getShelve() ).isNotNull();
+ assertThat( out.getShelve() ).isNotSameAs( in.getShelve() );
+ assertThat( out.getShelve().getCoolBeer() ).isNotSameAs( in.getShelve().getCoolBeer() );
+ assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" );
+ }
+
/**
* Test the deep cloning annotation with lists
*/
From 6e9fa87ba99bb8637ae7f525636610fc77ad6e22 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Thu, 13 Apr 2023 21:52:15 +0200
Subject: [PATCH 056/320] #3142 Nested forged methods should declare throws
from lifecycle methods
---
.../ap/internal/model/BeanMappingMethod.java | 8 ++++
.../test/bugs/_3142/Issue3142Exception.java | 16 +++++++
.../ap/test/bugs/_3142/Issue3142Test.java | 48 +++++++++++++++++++
...ue3142WithAfterMappingExceptionMapper.java | 32 +++++++++++++
...e3142WithBeforeMappingExceptionMapper.java | 32 +++++++++++++
.../mapstruct/ap/test/bugs/_3142/Source.java | 33 +++++++++++++
.../mapstruct/ap/test/bugs/_3142/Target.java | 42 ++++++++++++++++
7 files changed, 211 insertions(+)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.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 29d27c8d68..8942c62789 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
@@ -340,6 +340,14 @@ else if ( !method.isUpdateMethod() ) {
if ( factoryMethod != null ) {
forgedMethod.addThrownTypes( factoryMethod.getThrownTypes() );
}
+ for ( LifecycleCallbackMethodReference beforeMappingMethod : beforeMappingMethods ) {
+ forgedMethod.addThrownTypes( beforeMappingMethod.getThrownTypes() );
+ }
+
+ for ( LifecycleCallbackMethodReference afterMappingMethod : afterMappingMethods ) {
+ forgedMethod.addThrownTypes( afterMappingMethod.getThrownTypes() );
+ }
+
for ( PropertyMapping propertyMapping : propertyMappings ) {
if ( propertyMapping.getAssignment() != null ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java
new file mode 100644
index 0000000000..54ed903ee7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Issue3142Exception extends Exception {
+
+ public Issue3142Exception(String message) {
+ super( message );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java
new file mode 100644
index 0000000000..a8b44874bb
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ Issue3142Exception.class,
+ Source.class,
+ Target.class,
+})
+@IssueKey("3142")
+class Issue3142Test {
+
+ @ProcessorTest
+ @WithClasses({
+ Issue3142WithBeforeMappingExceptionMapper.class
+ })
+ void exceptionThrownInBeforeMapping() {
+ assertThatThrownBy( () -> Issue3142WithBeforeMappingExceptionMapper.INSTANCE.map(
+ new Source( new Source.Nested( "throwException" ) ), "test" )
+ )
+ .isInstanceOf( Issue3142Exception.class )
+ .hasMessage( "Source nested exception" );
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ Issue3142WithAfterMappingExceptionMapper.class
+ })
+ void exceptionThrownInAfterMapping() {
+ assertThatThrownBy( () -> Issue3142WithAfterMappingExceptionMapper.INSTANCE.map(
+ new Source( new Source.Nested( "throwException" ) ), "test" )
+ )
+ .isInstanceOf( Issue3142Exception.class )
+ .hasMessage( "Source nested exception" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java
new file mode 100644
index 0000000000..e188626424
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+import org.mapstruct.AfterMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3142WithAfterMappingExceptionMapper {
+
+ Issue3142WithAfterMappingExceptionMapper INSTANCE =
+ Mappers.getMapper( Issue3142WithAfterMappingExceptionMapper.class );
+
+ Target map(Source source, String id) throws Issue3142Exception;
+
+ @AfterMapping
+ default void afterMappingValidation(Object source) throws Issue3142Exception {
+ if ( source instanceof Source.Nested ) {
+ Source.Nested nested = (Source.Nested) source;
+ if ( "throwException".equals( nested.getValue() ) ) {
+ throw new Issue3142Exception( "Source nested exception" );
+ }
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java
new file mode 100644
index 0000000000..ee27d87b7f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+import org.mapstruct.BeforeMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3142WithBeforeMappingExceptionMapper {
+
+ Issue3142WithBeforeMappingExceptionMapper INSTANCE =
+ Mappers.getMapper( Issue3142WithBeforeMappingExceptionMapper.class );
+
+ Target map(Source source, String id) throws Issue3142Exception;
+
+ @BeforeMapping
+ default void preMappingValidation(Object source) throws Issue3142Exception {
+ if ( source instanceof Source.Nested ) {
+ Source.Nested nested = (Source.Nested) source;
+ if ( "throwException".equals( nested.getValue() ) ) {
+ throw new Issue3142Exception( "Source nested exception" );
+ }
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java
new file mode 100644
index 0000000000..2b78ffa022
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Source {
+ private final Nested nested;
+
+ public Source(Nested nested) {
+ this.nested = nested;
+ }
+
+ public Nested getNested() {
+ return nested;
+ }
+
+ public static class Nested {
+ private final String value;
+
+ public Nested(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java
new file mode 100644
index 0000000000..6f832b6ecf
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3142;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Target {
+ private String id;
+ private Nested nested;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Nested getNested() {
+ return nested;
+ }
+
+ public void setNested(Nested nested) {
+ this.nested = nested;
+ }
+
+ public static class Nested {
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+}
From 03563d8ffea0ca5ff4eadb297795f2407001c5dd Mon Sep 17 00:00:00 2001
From: Ben Zegveld
Date: Sat, 25 Feb 2023 21:29:23 +0100
Subject: [PATCH 057/320] #3174 Also allow multiple SubclassMapping annotations
on an annotation @interface.
---
.../java/org/mapstruct/SubclassMappings.java | 2 +-
.../CompositeSubclassMappingAnnotation.java | 19 ++++++++
.../SubclassCompositeMapper.java | 29 ++++++++++++
.../subclassmapping/SubclassMappingTest.java | 45 +++++++++++++++++++
4 files changed, 94 insertions(+), 1 deletion(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java
diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java
index 938b836f77..2ddafaf516 100644
--- a/core/src/main/java/org/mapstruct/SubclassMappings.java
+++ b/core/src/main/java/org/mapstruct/SubclassMappings.java
@@ -45,7 +45,7 @@
* @author Ben Zegveld
* @since 1.5
*/
-@Target(ElementType.METHOD)
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
@Experimental
public @interface SubclassMappings {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java
new file mode 100644
index 0000000000..a121c08799
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.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.subclassmapping;
+
+import org.mapstruct.Mapping;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
+import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto;
+import org.mapstruct.ap.test.subclassmapping.mappables.Car;
+import org.mapstruct.ap.test.subclassmapping.mappables.CarDto;
+
+@SubclassMapping( source = Car.class, target = CarDto.class )
+@SubclassMapping( source = Bike.class, target = BikeDto.class )
+@Mapping( source = "vehicleManufacturingCompany", target = "maker")
+public @interface CompositeSubclassMappingAnnotation {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java
new file mode 100644
index 0000000000..c660cd2390
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.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;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
+import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection;
+import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto;
+import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SubclassCompositeMapper {
+ SubclassCompositeMapper INSTANCE = Mappers.getMapper( SubclassCompositeMapper.class );
+
+ VehicleCollectionDto map(VehicleCollection vehicles);
+
+ @CompositeSubclassMappingAnnotation
+ 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/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
index 6f9b6e160c..5a5ce1ef99 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
@@ -143,6 +143,51 @@ void subclassMappingOverridesInverseInheritsMapping() {
.containsExactly( Car.class );
}
+ @ProcessorTest
+ @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
+ void mappingIsDoneUsingSubclassMappingWithCompositeMapping() {
+ VehicleCollection vehicles = new VehicleCollection();
+ vehicles.getVehicles().add( new Car() );
+ vehicles.getVehicles().add( new Bike() );
+
+ VehicleCollectionDto result = SubclassCompositeMapper.INSTANCE.map( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( CarDto.class, BikeDto.class );
+ }
+
+ @ProcessorTest
+ @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
+ void inverseMappingIsDoneUsingSubclassMappingWithCompositeMapping() {
+ VehicleCollectionDto vehicles = new VehicleCollectionDto();
+ vehicles.getVehicles().add( new CarDto() );
+ vehicles.getVehicles().add( new BikeDto() );
+
+ VehicleCollection result = SubclassCompositeMapper.INSTANCE.mapInverse( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( Car.class, Bike.class );
+ }
+
+ @ProcessorTest
+ @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
+ void subclassMappingInheritsInverseMappingWithCompositeMapping() {
+ VehicleCollectionDto vehiclesDto = new VehicleCollectionDto();
+ CarDto carDto = new CarDto();
+ carDto.setMaker( "BenZ" );
+ vehiclesDto.getVehicles().add( carDto );
+
+ VehicleCollection result = SubclassCompositeMapper.INSTANCE.mapInverse( vehiclesDto );
+
+ assertThat( result.getVehicles() )
+ .extracting( Vehicle::getVehicleManufacturingCompany )
+ .containsExactly( "BenZ" );
+ }
+
@ProcessorTest
@WithClasses({
HatchBack.class,
From 162fdb44f402e47d549778d57f369db144ce5e0d Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 16 Apr 2023 10:17:32 +0200
Subject: [PATCH 058/320] #3195 Update the location for asking questions
---
readme.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index 58d1c515bb..4807b5fba0 100644
--- a/readme.md
+++ b/readme.md
@@ -126,7 +126,7 @@ If you don't work with a dependency management tool, you can obtain a distributi
## Documentation and getting help
-To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help, come and join the [mapstruct-users](https://groups.google.com/forum/?hl=en#!forum/mapstruct-users) group.
+To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions).
## Building from Source
From 3c81d36810caf052f1d8bc73e30d9e3a8c8622f4 Mon Sep 17 00:00:00 2001
From: Johnny Lim
Date: Mon, 17 Apr 2023 04:51:33 +0900
Subject: [PATCH 059/320] Polish links in docs (#3214)
---
core/src/main/java/org/mapstruct/Mapper.java | 2 +-
core/src/main/java/org/mapstruct/MapperConfig.java | 2 +-
core/src/main/java/org/mapstruct/package-info.java | 2 +-
distribution/pom.xml | 2 +-
parent/pom.xml | 2 +-
readme.md | 14 +++++++-------
6 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java
index 0436c0cf69..60725cc441 100644
--- a/core/src/main/java/org/mapstruct/Mapper.java
+++ b/core/src/main/java/org/mapstruct/Mapper.java
@@ -298,7 +298,7 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
* Can be configured by the {@link MapperConfig#disableSubMappingMethodsGeneration()} as well.
*
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
- * mapstruct.org or
+ * mapstruct.org or
* github.com/mapstruct/mapstruct to share what problem you
* are facing with the automatic sub-mapping generation.
*
diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java
index 893801cdaa..915f3dd120 100644
--- a/core/src/main/java/org/mapstruct/MapperConfig.java
+++ b/core/src/main/java/org/mapstruct/MapperConfig.java
@@ -269,7 +269,7 @@ MappingInheritanceStrategy mappingInheritanceStrategy()
* Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()}
*
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
- * mapstruct.org or
+ * mapstruct.org or
* github.com/mapstruct/mapstruct to share what problem you
* are facing with the automatic sub-mapping generation.
*
diff --git a/core/src/main/java/org/mapstruct/package-info.java b/core/src/main/java/org/mapstruct/package-info.java
index 02f2b31a42..5a53b5a6d3 100644
--- a/core/src/main/java/org/mapstruct/package-info.java
+++ b/core/src/main/java/org/mapstruct/package-info.java
@@ -13,6 +13,6 @@
* This package contains several annotations which allow to configure how mapper interfaces are generated.
*
*
- * @see MapStruct reference documentation
+ * @see MapStruct reference documentation
*/
package org.mapstruct;
diff --git a/distribution/pom.xml b/distribution/pom.xml
index be82f52b3c..bcfa1f3db0 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -100,7 +100,7 @@
MapStruct ${project.version}
MapStruct ${project.version}
- MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0 .]]>
+ MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0 .]]>
diff --git a/parent/pom.xml b/parent/pom.xml
index e87db14551..b306446b65 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -16,7 +16,7 @@
MapStruct Parent
An annotation processor for generating type-safe bean mappers
- http://mapstruct.org/
+ https://mapstruct.org/
2012
diff --git a/readme.md b/readme.md
index 4807b5fba0..4e0c2f6ad4 100644
--- a/readme.md
+++ b/readme.md
@@ -1,7 +1,7 @@
# MapStruct - Java bean mappings, the easy way!
-[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final)
-[](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct)
+[](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final)
+[](https://search.maven.org/search?q=g:org.mapstruct)
[](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt)
[](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amaster+workflow%3ACI)
@@ -22,7 +22,7 @@
## What is MapStruct?
-MapStruct is a Java [annotation processor](http://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/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.
Compared to mapping frameworks working at runtime, MapStruct offers the following advantages:
@@ -126,7 +126,7 @@ If you don't work with a dependency management tool, you can obtain a distributi
## Documentation and getting help
-To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions).
+To learn more about MapStruct, refer to the [project homepage](https://mapstruct.org). The [reference documentation](https://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions).
## Building from Source
@@ -154,13 +154,13 @@ Make sure that you have the [m2e_apt](https://marketplace.eclipse.org/content/m2
## Links
-* [Homepage](http://mapstruct.org)
+* [Homepage](https://mapstruct.org)
* [Source code](https://github.com/mapstruct/mapstruct/)
-* [Downloads](https://sourceforge.net/projects/mapstruct/files/)
+* [Downloads](https://github.com/mapstruct/mapstruct/releases)
* [Issue tracker](https://github.com/mapstruct/mapstruct/issues)
* [User group](https://groups.google.com/forum/?hl=en#!forum/mapstruct-users)
* [CI build](https://github.com/mapstruct/mapstruct/actions/)
## Licensing
-MapStruct is licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
+MapStruct is licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0.
From 00f891be5834da6d81db330cde7ddaab6abe6bf8 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 22 Apr 2023 17:14:46 +0200
Subject: [PATCH 060/320] #3112 Add missing brackets
---
.../src/main/asciidoc/chapter-8-mapping-values.asciidoc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
index b9cb308d4f..965479541e 100644
--- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
@@ -205,7 +205,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Difference: `` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.
4. Difference: Given 1. and 3. there will never be unmapped values.
-5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for particular enum values.
+5. Similarity: `` can be used for throwing an exception for particular enum values.
*`String` to enum*
@@ -213,7 +213,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Similarity: `` will create a mapping for each target enum constant and proceed to the switch/default clause value.
4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `` or ` will result in a warning.
-5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for any arbitrary `String` value.
+5. Similarity: `` can be used for throwing an exception for any arbitrary `String` value.
=== Custom name transformation
From 9adcb06c3486ea53ab37020b3080f776efe9fb48 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 22 Apr 2023 17:22:26 +0200
Subject: [PATCH 061/320] #3236 Add missing jakarta-cdi to the documentation
---
documentation/src/main/asciidoc/chapter-2-set-up.asciidoc | 3 ++-
1 file changed, 2 insertions(+), 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 12c399c1f5..666954c4b1 100644
--- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
@@ -215,10 +215,11 @@ suppressGeneratorVersionInfoComment`
Supported values are:
* `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)`
-* `cdi`: the generated mapper is an application-scoped CDI bean and can be retrieved via `@Inject`
+* `cdi`: the generated mapper is an application-scoped (from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority) CDI bean and can be retrieved via `@Inject`
* `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired`
* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring
* `jakarta`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from jakarta.inject), e.g. using Spring
+* `jakarta-cdi`: the generated mapper is an application-scoped (from jakarta.enterprise.context) CDI bean and can be retrieved via `@Inject`
If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence.
|`default`
From b1034e67035fea9329b13e288aa4503533f6912c Mon Sep 17 00:00:00 2001
From: Claudio Nave
Date: Sat, 18 Mar 2023 13:01:49 +0100
Subject: [PATCH 062/320] #3202 Improve line location report for invalid
qualifier for SubclassMapping
---
.../ap/internal/model/BeanMappingMethod.java | 2 +-
.../model/source/SubclassMappingOptions.java | 40 +++++++++++--------
.../ErroneousSubclassQualifiedByMapper.java | 1 -
.../SubclassQualifierMapperTest.java | 5 ++-
4 files changed, 27 insertions(+), 21 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 8942c62789..c8fbe0205b 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
@@ -420,7 +420,7 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
FormattingParameters.EMPTY,
criteria,
rightHandSide,
- null,
+ subclassMappingOptions.getMirror(),
() -> forgeSubclassMapping(
rightHandSide,
sourceType,
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
index 6ed1117ebd..11b4b283c2 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
@@ -7,7 +7,10 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
+import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
@@ -34,14 +37,16 @@ public class SubclassMappingOptions extends DelegatingOptions {
private final TypeMirror target;
private final TypeUtils typeUtils;
private final SelectionParameters selectionParameters;
+ private final SubclassMappingGem subclassMapping;
public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next,
- SelectionParameters selectionParameters) {
+ SelectionParameters selectionParameters, SubclassMappingGem subclassMapping) {
super( next );
this.source = source;
this.target = target;
this.typeUtils = typeUtils;
this.selectionParameters = selectionParameters;
+ this.subclassMapping = subclassMapping;
}
@Override
@@ -124,14 +129,18 @@ public SelectionParameters getSelectionParameters() {
return selectionParameters;
}
+ public AnnotationMirror getMirror() {
+ return Optional.ofNullable( subclassMapping ).map( SubclassMappingGem::mirror ).orElse( null );
+ }
+
public static void addInstances(SubclassMappingsGem gem, ExecutableElement method,
BeanMappingOptions beanMappingOptions, FormattingMessager messager,
TypeUtils typeUtils, Set mappings,
List sourceParameters, Type resultType,
SubclassValidator subclassValidator) {
- for ( SubclassMappingGem subclassMappingGem : gem.value().get() ) {
+ for ( SubclassMappingGem subclassMapping : gem.value().get() ) {
addInstance(
- subclassMappingGem,
+ subclassMapping,
method,
beanMappingOptions,
messager,
@@ -175,25 +184,22 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle
targetSubclass,
typeUtils,
beanMappingOptions,
- selectionParameters
+ selectionParameters,
+ subclassMapping
) );
}
- public static List copyForInverseInheritance(Set subclassMappings,
+ public static List copyForInverseInheritance(Set mappings,
BeanMappingOptions beanMappingOptions) {
// we are not interested in keeping it unique at this point.
- List mappings = new ArrayList<>();
- for ( SubclassMappingOptions subclassMapping : subclassMappings ) {
- mappings.add(
- new SubclassMappingOptions(
- subclassMapping.target,
- subclassMapping.source,
- subclassMapping.typeUtils,
- beanMappingOptions,
- subclassMapping.selectionParameters
- ) );
- }
- return mappings;
+ return mappings.stream().map( mapping -> new SubclassMappingOptions(
+ mapping.target,
+ mapping.source,
+ mapping.typeUtils,
+ beanMappingOptions,
+ mapping.selectionParameters,
+ mapping.subclassMapping
+ ) ).collect( Collectors.toCollection( ArrayList::new ) );
}
@Override
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java
index 26cdd4715e..fb5dcca11c 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java
@@ -11,6 +11,5 @@
@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class })
public interface ErroneousSubclassQualifiedByMapper {
@SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = NonExistent.class)
- @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class)
ComposerDto toDto(Composer composer);
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
index df67c27013..2b301d8981 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java
@@ -230,7 +230,7 @@ void subclassQualifiedByNameDoesNotContainUnused() {
diagnostics = @Diagnostic(
type = ErroneousSubclassQualifiedByMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 15,
+ line = 13,
message = "Qualifier error. No method found annotated with: [ @NonExistent ]. " +
"See https://mapstruct.org/faq/#qualifier for more info."
)
@@ -245,7 +245,8 @@ void subclassQualifiedByErroneous() {
diagnostics = @Diagnostic(
type = ErroneousSubclassQualifiedByNameMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 15,
+ line = 13,
+ alternativeLine = 15,
message = "Qualifier error. No method found annotated with @Named#value: [ non-existent ]. " +
"See https://mapstruct.org/faq/#qualifier for more info."
)
From d10d48ccff8e3715b44ddfd3ad3f5a54e1238479 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 22 Apr 2023 18:36:42 +0200
Subject: [PATCH 063/320] #3248 BeanMapping#ignoreUnmappedSourceProperties
should be inherited for `@InheritConfiguration`
---
.../model/source/BeanMappingOptions.java | 5 +-
.../model/source/MappingMethodOptions.java | 2 +-
.../ap/test/bugs/_3248/Issue3248Mapper.java | 52 +++++++++++++++++++
.../ap/test/bugs/_3248/Issue3248Test.java | 25 +++++++++
4 files changed, 81 insertions(+), 3 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java
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 24811768e7..e8f19f91f6 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
@@ -41,13 +41,14 @@ public class BeanMappingOptions extends DelegatingOptions {
* creates a mapping for inheritance. Will set
*
* @param beanMapping the bean mapping options that should be used
+ * @param isInverse whether the inheritance is inverse
*
* @return new mapping
*/
- public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping) {
+ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping, boolean isInverse) {
BeanMappingOptions options = new BeanMappingOptions(
SelectionParameters.forInheritance( beanMapping.selectionParameters ),
- Collections.emptyList(),
+ isInverse ? Collections.emptyList() : beanMapping.ignoreUnmappedSourceProperties,
beanMapping.beanMapping,
beanMapping
);
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 9f5da7b8de..2124dd974d 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
@@ -167,7 +167,7 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa
}
if ( !getBeanMapping().hasAnnotation() && templateOptions.getBeanMapping().hasAnnotation() ) {
- setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ) ) );
+ setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ), isInverse ) );
}
if ( !getEnumMappingOptions().hasAnnotation() && templateOptions.getEnumMappingOptions().hasAnnotation() ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java
new file mode 100644
index 0000000000..e0cbba40f7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3248;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.InheritConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
+public interface Issue3248Mapper {
+
+ @BeanMapping(ignoreUnmappedSourceProperties = "otherValue")
+ Target map(Source source);
+
+ @InheritConfiguration
+ Target secondMap(Source source);
+
+ class Target {
+ private final String value;
+
+ public Target(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ class Source {
+ private final String value;
+
+ public Source(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getOtherValue() {
+ return value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java
new file mode 100644
index 0000000000..ad8cf2e499
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3248;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("3248")
+@WithClasses({
+ Issue3248Mapper.class
+})
+class Issue3248Test {
+
+ @ProcessorTest
+ void shouldCompileCode() {
+
+ }
+}
From c6ea69eaf9bba177a52f6ddebebc1378797b0eb2 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 15 Apr 2023 23:18:32 +0200
Subject: [PATCH 064/320] #3186 Do not use conversions in 2-step mapping when
they are disabled
---
.../creation/MappingResolverImpl.java | 54 ++++++-----
.../ErroneousBuiltInAndBuiltInMapper.java | 44 +++++++++
.../ErroneousBuiltInAndMethodMapper.java | 50 +++++++++++
.../ErroneousConversionAndMethodMapper.java | 48 ++++++++++
.../ErroneousMethodAndBuiltInMapper.java | 49 ++++++++++
.../ErroneousMethodAndConversionMapper.java | 48 ++++++++++
.../mappingcontrol/MappingControlTest.java | 90 +++++++++++++++++++
.../ap/test/mappingcontrol/NoConversion.java | 20 +++++
8 files changed, 380 insertions(+), 23 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.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 12b345d238..0c8e2cc4c4 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
@@ -295,20 +295,24 @@ && allowDirect( sourceType, targetType ) ) {
}
// 2 step method, then: method(conversion(source))
- assignment = ConversionMethod.getBestMatch( this, sourceType, targetType );
- if ( assignment != null ) {
- usedSupportedMappings.addAll( supportingMethodCandidates );
- return assignment;
+ if ( allowConversion() ) {
+ assignment = ConversionMethod.getBestMatch( this, sourceType, targetType );
+ if ( assignment != null ) {
+ usedSupportedMappings.addAll( supportingMethodCandidates );
+ return assignment;
+ }
}
// stop here when looking for update methods.
selectionCriteria.setPreferUpdateMapping( false );
// 2 step method, finally: conversion(method(source))
- assignment = MethodConversion.getBestMatch( this, sourceType, targetType );
- if ( assignment != null ) {
- usedSupportedMappings.addAll( supportingMethodCandidates );
- return assignment;
+ if ( allowConversion() ) {
+ assignment = MethodConversion.getBestMatch( this, sourceType, targetType );
+ if ( assignment != null ) {
+ usedSupportedMappings.addAll( supportingMethodCandidates );
+ return assignment;
+ }
}
}
@@ -763,22 +767,26 @@ static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targe
return mmAttempt.result;
}
}
- MethodMethod mbAttempt =
- new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef )
- .getBestMatch( sourceType, targetType );
- if ( mbAttempt.hasResult ) {
- return mbAttempt.result;
- }
- MethodMethod bmAttempt =
- new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef )
- .getBestMatch( sourceType, targetType );
- if ( bmAttempt.hasResult ) {
- return bmAttempt.result;
+ if ( att.allowConversion() ) {
+ MethodMethod mbAttempt =
+ new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef )
+ .getBestMatch( sourceType, targetType );
+ if ( mbAttempt.hasResult ) {
+ return mbAttempt.result;
+ }
+ MethodMethod bmAttempt =
+ new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef )
+ .getBestMatch( sourceType, targetType );
+ if ( bmAttempt.hasResult ) {
+ return bmAttempt.result;
+ }
+ MethodMethod bbAttempt =
+ new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef )
+ .getBestMatch( sourceType, targetType );
+ return bbAttempt.result;
}
- MethodMethod bbAttempt =
- new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef )
- .getBestMatch( sourceType, targetType );
- return bbAttempt.result;
+
+ return null;
}
MethodMethod(ResolvingAttempt attempt, List xMethods, List yMethods,
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java
new file mode 100644
index 0000000000..eb9f7ea9ce
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(mappingControl = NoConversion.class)
+public interface ErroneousBuiltInAndBuiltInMapper {
+
+ Target map(Source source);
+
+ class Source {
+ private final ZonedDateTime time;
+
+ public Source(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ public ZonedDateTime getTime() {
+ return time;
+ }
+ }
+
+ class Target {
+ private final Date time;
+
+ public Target(Date time) {
+ this.time = time;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java
new file mode 100644
index 0000000000..fdd7545da7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(mappingControl = NoConversion.class)
+public interface ErroneousBuiltInAndMethodMapper {
+
+ Target map(Source source);
+
+ default ZonedDateTime fromInt(int time) {
+ return ZonedDateTime.ofInstant( Instant.ofEpochMilli( time ), ZoneOffset.UTC );
+ }
+
+ class Source {
+ private final int time;
+
+ public Source(int time) {
+ this.time = time;
+ }
+
+ public int getTime() {
+ return time;
+ }
+ }
+
+ class Target {
+ private Calendar time;
+
+ public Target(Calendar time) {
+ this.time = time;
+ }
+
+ public Calendar getTime() {
+ return time;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java
new file mode 100644
index 0000000000..91a94aeca2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.time.Instant;
+import java.util.Date;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(mappingControl = NoConversion.class)
+public interface ErroneousConversionAndMethodMapper {
+
+ Target map(Source source);
+
+ default Instant fromDate(int time) {
+ return Instant.ofEpochMilli( time );
+ }
+
+ class Source {
+ private final int time;
+
+ public Source(int time) {
+ this.time = time;
+ }
+
+ public int getTime() {
+ return time;
+ }
+ }
+
+ class Target {
+ private Date time;
+
+ public Target(Date time) {
+ this.time = time;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java
new file mode 100644
index 0000000000..d7798161c1
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(mappingControl = NoConversion.class)
+public interface ErroneousMethodAndBuiltInMapper {
+
+ Target map(Source source);
+
+ default Date fromCalendar(Calendar calendar) {
+ return calendar != null ? calendar.getTime() : null;
+ }
+
+ class Source {
+ private final ZonedDateTime time;
+
+ public Source(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ public ZonedDateTime getTime() {
+ return time;
+ }
+ }
+
+ class Target {
+ private final Date time;
+
+ public Target(Date time) {
+ this.time = time;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java
new file mode 100644
index 0000000000..5a372addd9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.time.Instant;
+import java.util.Date;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(mappingControl = NoConversion.class)
+public interface ErroneousMethodAndConversionMapper {
+
+ Target map(Source source);
+
+ default long fromInstant(Instant value) {
+ return value != null ? value.getEpochSecond() : null;
+ }
+
+ class Source {
+ private final Date time;
+
+ public Source(Date time) {
+ this.time = time;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+ }
+
+ class Target {
+ private long time;
+
+ public Target(long time) {
+ this.time = time;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
index c9c2e095f5..171fe4bc6a 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java
@@ -240,6 +240,96 @@ public void complexSelectionNotAllowed() {
public void complexSelectionNotAllowedWithConfig() {
}
+ @ProcessorTest
+ @IssueKey("3186")
+ @WithClasses({
+ ErroneousMethodAndBuiltInMapper.class,
+ NoConversion.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousMethodAndBuiltInMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 20,
+ message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " +
+ "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"."
+ )
+ })
+ public void conversionSelectionNotAllowedInTwoStepMethodBuiltIdConversion() {
+ }
+
+ @ProcessorTest
+ @IssueKey("3186")
+ @WithClasses({
+ ErroneousBuiltInAndBuiltInMapper.class,
+ NoConversion.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousBuiltInAndBuiltInMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 19,
+ message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " +
+ "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"."
+ )
+ })
+ public void conversionSelectionNotAllowedInTwoStepBuiltInBuiltInConversion() {
+ }
+
+ @ProcessorTest
+ @IssueKey("3186")
+ @WithClasses({
+ ErroneousBuiltInAndMethodMapper.class,
+ NoConversion.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousBuiltInAndMethodMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 21,
+ message = "Can't map property \"int time\" to \"Calendar time\". " +
+ "Consider to declare/implement a mapping method: \"Calendar map(int value)\"."
+ )
+ })
+ public void conversionSelectionNotAllowedInTwoStepBuiltInMethodConversion() {
+ }
+
+ @ProcessorTest
+ @IssueKey("3186")
+ @WithClasses({
+ ErroneousMethodAndConversionMapper.class,
+ NoConversion.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousMethodAndConversionMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 19,
+ message = "Can't map property \"Date time\" to \"long time\". " +
+ "Consider to declare/implement a mapping method: \"long map(Date value)\"."
+ )
+ })
+ public void conversionSelectionNotAllowedInTwoStepMethodConversionConversion() {
+ }
+
+ @ProcessorTest
+ @IssueKey("3186")
+ @WithClasses({
+ ErroneousConversionAndMethodMapper.class,
+ NoConversion.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousConversionAndMethodMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 19,
+ message = "Can't map property \"int time\" to \"Date time\". " +
+ "Consider to declare/implement a mapping method: \"Date map(int value)\"."
+ )
+ })
+ public void conversionSelectionNotAllowedInTwoStepConversionMethodConversion() {
+ }
+
private FridgeDTO createFridgeDTO() {
FridgeDTO fridgeDTO = new FridgeDTO();
ShelveDTO shelveDTO = new ShelveDTO();
diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java
new file mode 100644
index 0000000000..6f05c16bb8
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.mappingcontrol;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.mapstruct.control.MappingControl;
+import org.mapstruct.util.Experimental;
+
+@Retention(RetentionPolicy.CLASS)
+@Experimental
+@MappingControl( MappingControl.Use.DIRECT )
+@MappingControl( MappingControl.Use.MAPPING_METHOD )
+@MappingControl( MappingControl.Use.COMPLEX_MAPPING )
+public @interface NoConversion {
+}
From e69843f46e47332ea6899717e530266b853cee8f Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 2 Apr 2023 19:47:08 +0200
Subject: [PATCH 065/320] #3158 BeanMapping#ignoreByDefault should work
properly for constructor properties
---
.../ap/internal/model/BeanMappingMethod.java | 10 ++++-
.../ap/test/bugs/_2149/Issue2149Test.java | 6 ---
.../ap/test/bugs/_3158/Issue3158Mapper.java | 43 +++++++++++++++++++
.../ap/test/bugs/_3158/Issue3158Test.java | 31 +++++++++++++
4 files changed, 83 insertions(+), 7 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.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 c8fbe0205b..58b4640c95 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
@@ -285,7 +285,11 @@ else if ( !method.isUpdateMethod() ) {
return null;
}
- if ( !mappingReferences.isRestrictToDefinedMappings() ) {
+ boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings();
+ if ( applyImplicitMappings ) {
+ applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault();
+ }
+ if ( applyImplicitMappings ) {
// apply name based mapping from a source reference
applyTargetThisMapping();
@@ -1522,6 +1526,10 @@ private ReportingPolicyGem getUnmappedTargetPolicy() {
if ( mappingReferences.isForForgedMethods() ) {
return ReportingPolicyGem.IGNORE;
}
+ // If we have ignoreByDefault = true, unprocessed target properties are not an issue.
+ if ( method.getOptions().getBeanMapping().isignoreByDefault() ) {
+ return ReportingPolicyGem.IGNORE;
+ }
if ( method.getOptions().getBeanMapping() != null ) {
return method.getOptions().getBeanMapping().unmappedTargetPolicy();
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java
index 8c19e2e090..39c415f340 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java
@@ -30,12 +30,6 @@ public class Issue2149Test {
line = 18,
message = "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not " +
"allowed. You'll need to explicitly ignore the target properties that should be ignored instead."
- ),
- @Diagnostic(
- type = Erroneous2149Mapper.class,
- kind = javax.tools.Diagnostic.Kind.WARNING,
- line = 20,
- message = "Unmapped target property: \"address\"."
)
}
)
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java
new file mode 100644
index 0000000000..25af6b3d44
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3158;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3158Mapper {
+
+ Issue3158Mapper INSTANCE = Mappers.getMapper( Issue3158Mapper.class );
+
+ @BeanMapping(ignoreByDefault = true)
+ @Mapping(target = "name")
+ Target map(Target target);
+
+ class Target {
+ private final String name;
+ private final String email;
+
+ public Target(String name, String email) {
+ this.name = name;
+ this.email = email;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java
new file mode 100644
index 0000000000..e4832edfa9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3158;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses(Issue3158Mapper.class)
+@IssueKey("3158")
+class Issue3158Test {
+
+ @ProcessorTest
+ void beanMappingIgnoreByDefaultShouldBeRespectedForConstructorProperties() {
+ Issue3158Mapper.Target target = Issue3158Mapper.INSTANCE.map( new Issue3158Mapper.Target(
+ "tester",
+ "tester@test.com"
+ ) );
+
+ assertThat( target.getName() ).isEqualTo( "tester" );
+ assertThat( target.getEmail() ).isNull();
+ }
+}
From 979b35a2f46e75358af7029f0cd1ee7bbf40d8a7 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 2 Apr 2023 19:06:45 +0200
Subject: [PATCH 066/320] #3153 Do not use compress directive to strip
whitespaces for value mapping switch method
When using the compress directive it is going to strip whitespaces from the templates as well (i.e. what the user defined in `ValueMapping#source` and / or `ValueMapping#target
---
.../ap/internal/model/ValueMappingMethod.ftl | 30 ++++++++---------
.../ap/test/bugs/_3153/Issue3153Mapper.java | 33 +++++++++++++++++++
.../ap/test/bugs/_3153/Issue3153Test.java | 30 +++++++++++++++++
3 files changed, 76 insertions(+), 17 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
index 016cfcd182..104fd98437 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl
@@ -48,26 +48,22 @@
#if>
}
<#macro writeSource source="">
- <@compress single_line=true>
- <#if sourceParameter.type.enumType>
- ${source}
- <#elseif sourceParameter.type.string>
- "${source}"
- #if>
- @compress>
+ <#if sourceParameter.type.enumType>
+ ${source}<#t>
+ <#elseif sourceParameter.type.string>
+ "${source}"<#t>
+ #if>
#macro>
<#macro writeTarget target="">
- <@compress single_line=true>
- <#if target?has_content>
- <#if returnType.enumType>
- <@includeModel object=returnType/>.${target}
- <#elseif returnType.string>
- "${target}"
- #if>
- <#else>
- null
+ <#if target?has_content>
+ <#if returnType.enumType>
+ <@includeModel object=returnType/>.${target}<#t>
+ <#elseif returnType.string>
+ "${target}"<#t>
#if>
- @compress>
+ <#else>
+ null<#t>
+ #if>
#macro>
<#macro throws>
<#if (thrownTypes?size > 0)><#lt> throws #if><@compress single_line=true>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java
new file mode 100644
index 0000000000..af54d23a8d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3153;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+import org.mapstruct.ValueMapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+interface Issue3153Mapper {
+
+ Issue3153Mapper INSTANCE = Mappers.getMapper( Issue3153Mapper.class );
+
+ @ValueMapping(source = " PR", target = "PR")
+ @ValueMapping(source = " PR", target = "PR")
+ @ValueMapping(source = " PR", target = "PR")
+ @ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
+ Target mapToEnum(String value);
+
+ @ValueMapping(source = "PR", target = " PR")
+ String mapFromEnum(Target value);
+
+ enum Target {
+ PR,
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java
new file mode 100644
index 0000000000..658a497086
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3153;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses(Issue3153Mapper.class)
+@IssueKey("3153")
+class Issue3153Test {
+
+ @ProcessorTest
+ void shouldNotTrimStringValueSource() {
+ assertThat( Issue3153Mapper.INSTANCE.mapToEnum( "PR" ) ).isEqualTo( Issue3153Mapper.Target.PR );
+ assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR );
+ assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR );
+ assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR );
+
+ assertThat( Issue3153Mapper.INSTANCE.mapFromEnum( Issue3153Mapper.Target.PR ) ).isEqualTo( " PR" );
+ }
+}
From 1bc3436c5c64077503a1656eb8ef516c69d0e3b4 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 22 Apr 2023 22:30:52 +0200
Subject: [PATCH 067/320] #3239 Mapping composition is no longer experimental
---
.../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 4 ++--
1 file changed, 2 insertions(+), 2 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 e4a77f5391..8609ac4f77 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -126,7 +126,7 @@ Collection-typed attributes with the same element type will be copied by creatin
MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.
[[mapping-composition]]
-=== Mapping Composition (experimental)
+=== Mapping Composition
MapStruct supports the use of meta annotations. The `@Mapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. This allows `@Mapping` to be used on other (user defined) annotations for re-use purposes. For example:
====
@@ -164,7 +164,7 @@ public interface StorageMapper {
Still, they do have some properties in common. The `@ToEntity` assumes both target beans `ShelveEntity` and `BoxEntity` have properties: `"id"`, `"creationDate"` and `"name"`. It furthermore assumes that the source beans `ShelveDto` and `BoxDto` always have a property `"groupName"`. This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck.
-This feature is still experimental. Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
+Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
Therefore, the user should use this feature with care, especially when uncertain when a property is always present.
A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use `@InheritConfiguration` to achieve the same result (see <>).
From 86a668661a45037adc795f003e6134f89faa3c3c Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 22 Apr 2023 23:33:59 +0200
Subject: [PATCH 068/320] #3159 Do null value check for collections with
default expression
---
.../ap/internal/model/PropertyMapping.java | 8 ++-
.../ap/test/bugs/_3159/Issue3159Mapper.java | 64 +++++++++++++++++++
.../ap/test/bugs/_3159/Issue3159Test.java | 27 ++++++++
3 files changed, 97 insertions(+), 2 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java
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 ad788de43b..7114ccf57b 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
@@ -510,7 +510,7 @@ private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs, Type targetTyp
return true;
}
- if ( defaultValue != null || defaultJavaExpression != null ) {
+ if ( hasDefaultValueOrDefaultExpression() ) {
// If there is default value defined then a check is needed
return true;
}
@@ -518,6 +518,10 @@ private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs, Type targetTyp
return false;
}
+ private boolean hasDefaultValueOrDefaultExpression() {
+ return defaultValue != null || defaultJavaExpression != null;
+ }
+
private Assignment assignToPlainViaAdder( Assignment rightHandSide) {
Assignment result = rightHandSide;
@@ -555,7 +559,7 @@ private Assignment assignToCollection(Type targetType, AccessorType targetAccess
.targetAccessorType( targetAccessorType )
.rightHandSide( rightHandSide )
.assignment( rhs )
- .nullValueCheckStrategy( nvcs )
+ .nullValueCheckStrategy( hasDefaultValueOrDefaultExpression() ? ALWAYS : nvcs )
.nullValuePropertyMappingStrategy( nvpms )
.build();
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java
new file mode 100644
index 0000000000..42c77c71dd
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3159;
+
+import java.util.Collection;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue3159Mapper {
+
+ Issue3159Mapper INSTANCE = Mappers.getMapper( Issue3159Mapper.class );
+
+ @Mapping(target = "elements", defaultExpression = "java(new ArrayList<>())")
+ Target map(Source source);
+
+ default String elementName(Element element) {
+ return element != null ? element.getName() : null;
+ }
+
+ class Target {
+ private final Collection elements;
+
+ public Target(Collection elements) {
+ this.elements = elements;
+ }
+
+ public Collection getElements() {
+ return elements;
+ }
+ }
+
+ class Source {
+ private final Collection elements;
+
+ public Source(Collection elements) {
+ this.elements = elements;
+ }
+
+ public Collection getElements() {
+ return elements;
+ }
+ }
+
+ class Element {
+ private final String name;
+
+ public Element(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java
new file mode 100644
index 0000000000..77feba151c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3159;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("3159")
+@WithClasses(Issue3159Mapper.class)
+class Issue3159Test {
+
+ @ProcessorTest
+ void shouldUseDefaultExpressionForCollection() {
+ Issue3159Mapper.Target target = Issue3159Mapper.INSTANCE.map( new Issue3159Mapper.Source( null ) );
+
+ assertThat( target.getElements() ).isEmpty();
+ }
+}
From 1ab5db6a2774a58a6c41b5e4c2bfb23b2fa1bab7 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 23 Apr 2023 22:20:05 +0200
Subject: [PATCH 069/320] Update latest release version to 1.5.5.Final
---
readme.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/readme.md b/readme.md
index 4e0c2f6ad4..8b8af9a69f 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,6 @@
# MapStruct - Java bean mappings, the easy way!
-[](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final)
+[](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final)
[](https://search.maven.org/search?q=g:org.mapstruct)
[](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt)
@@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map
```xml
...
- 1.5.3.Final
+ 1.5.5.Final
...
@@ -114,10 +114,10 @@ plugins {
dependencies {
...
- implementation 'org.mapstruct:mapstruct:1.5.3.Final'
+ implementation 'org.mapstruct:mapstruct:1.5.5.Final'
- annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
- testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // if you are using mapstruct in test code
+ 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
}
...
```
From 2f78d3f4e2f493239ed15e6fe2bb85daab4bfed8 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Sun, 30 Apr 2023 16:33:00 +0200
Subject: [PATCH 070/320] #3125: Allow subclassmapping inheritance for methods
with identical signature
Co-authored-by: Ben Zegveld
---
.../model/source/MappingMethodOptions.java | 36 ++++++++++++++++--
.../model/source/SubclassMappingOptions.java | 17 +++++++++
.../InheritedSubclassMapper.java | 38 +++++++++++++++++++
.../subclassmapping/SubclassMappingTest.java | 20 +++++++++-
4 files changed, 107 insertions(+), 4 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java
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 2124dd974d..73474a8478 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
@@ -5,12 +5,15 @@
*/
package org.mapstruct.ap.internal.model.source;
+import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy;
+
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+
import javax.lang.model.element.AnnotationMirror;
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
@@ -20,8 +23,6 @@
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.accessor.Accessor;
-import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy;
-
/**
* Encapsulates all options specifiable on a mapping method
*
@@ -205,12 +206,19 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa
}
if ( isInverse ) {
- // normal inheritence of subclass mappings will result runtime in infinite loops.
List inheritedMappings = SubclassMappingOptions.copyForInverseInheritance(
templateOptions.getSubclassMappings(),
getBeanMapping() );
addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
}
+ else if ( methodsHaveIdenticalSignature( templateMethod, sourceMethod ) ) {
+ List inheritedMappings =
+ SubclassMappingOptions
+ .copyForInheritance(
+ templateOptions.getSubclassMappings(),
+ getBeanMapping() );
+ addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
+ }
Set newMappings = new LinkedHashSet<>();
for ( MappingOptions mapping : templateOptions.getMappings() ) {
@@ -232,6 +240,28 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa
}
}
+ private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, SourceMethod sourceMethod) {
+ return parametersAreOfIdenticalTypeAndOrder( templateMethod, sourceMethod )
+ && resultTypeIsTheSame( templateMethod, sourceMethod );
+ }
+
+ private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) {
+ if (templateMethod.getParameters().size() != sourceMethod.getParameters().size()) {
+ return false;
+ }
+ for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) {
+ if (!templateMethod.getParameters().get( i ).getType().equals(
+ sourceMethod.getParameters().get( i ).getType() ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean resultTypeIsTheSame(SourceMethod templateMethod, SourceMethod sourceMethod) {
+ return templateMethod.getResultType().equals( sourceMethod.getResultType() );
+ }
+
private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror,
List inheritedMappings) {
Set redefinedSubclassMappings = new HashSet<>( subclassMappings );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
index 11b4b283c2..2b0e6dd13e 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
@@ -202,6 +202,23 @@ public static List copyForInverseInheritance(Set copyForInheritance(Set subclassMappings,
+ BeanMappingOptions beanMappingOptions) {
+ // we are not interested in keeping it unique at this point.
+ List mappings = new ArrayList<>();
+ for ( SubclassMappingOptions subclassMapping : subclassMappings ) {
+ mappings.add(
+ new SubclassMappingOptions(
+ subclassMapping.source,
+ subclassMapping.target,
+ subclassMapping.typeUtils,
+ beanMappingOptions,
+ subclassMapping.selectionParameters,
+ subclassMapping.subclassMapping ) );
+ }
+ return mappings;
+ }
+
@Override
public boolean equals(Object obj) {
if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java
new file mode 100644
index 0000000000..6e4aca98ba
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.subclassmapping;
+
+import org.mapstruct.InheritConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
+import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto;
+import org.mapstruct.ap.test.subclassmapping.mappables.Car;
+import org.mapstruct.ap.test.subclassmapping.mappables.CarDto;
+import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack;
+import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
+import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto;
+import org.mapstruct.factory.Mappers;
+
+@Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE )
+public interface InheritedSubclassMapper {
+ InheritedSubclassMapper INSTANCE = Mappers.getMapper( InheritedSubclassMapper.class );
+
+ @SubclassMapping( source = HatchBack.class, target = CarDto.class )
+ @SubclassMapping( source = Car.class, target = CarDto.class )
+ @SubclassMapping( source = Bike.class, target = BikeDto.class )
+ @Mapping( source = "vehicleManufacturingCompany", target = "maker" )
+ VehicleDto map(Vehicle vehicle);
+
+ @InheritConfiguration( name = "map" )
+ VehicleDto mapInherited(Vehicle dto);
+
+ @SubclassMapping( source = Bike.class, target = CarDto.class )
+ @InheritConfiguration( name = "map" )
+ VehicleDto mapInheritedOverride(Vehicle dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
index 5a5ce1ef99..ba64d2cc7f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
@@ -130,7 +130,7 @@ void subclassMappingInheritsInverseMapping() {
HatchBack.class,
InverseOrderSubclassMapper.class
} )
- void subclassMappingOverridesInverseInheritsMapping() {
+ void subclassMappingOverridesInverseInheritedMapping() {
VehicleCollectionDto vehicleDtos = new VehicleCollectionDto();
CarDto carDto = new CarDto();
carDto.setMaker( "BenZ" );
@@ -143,6 +143,24 @@ void subclassMappingOverridesInverseInheritsMapping() {
.containsExactly( Car.class );
}
+ @IssueKey( "3125" )
+ @ProcessorTest
+ @WithClasses( {
+ HatchBack.class,
+ InheritedSubclassMapper.class
+ } )
+ void subclassMappingOverridesInheritedMapping() {
+ Vehicle bike = new Bike();
+
+ VehicleDto result = InheritedSubclassMapper.INSTANCE.map( bike );
+ VehicleDto resultInherited = InheritedSubclassMapper.INSTANCE.mapInherited( bike );
+ VehicleDto resultOverride = InheritedSubclassMapper.INSTANCE.mapInheritedOverride( bike );
+
+ assertThat( result ).isInstanceOf( BikeDto.class );
+ assertThat( resultInherited ).isInstanceOf( BikeDto.class );
+ assertThat( resultOverride ).isInstanceOf( CarDto.class );
+ }
+
@ProcessorTest
@WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
void mappingIsDoneUsingSubclassMappingWithCompositeMapping() {
From 931591a385a352795090a8bf9db96778d9325a3b Mon Sep 17 00:00:00 2001
From: ro0sterjam
Date: Sun, 30 Apr 2023 11:02:39 -0400
Subject: [PATCH 071/320] #3071 Support defining custom processor options by
custom SPI
---
.../chapter-13-using-mapstruct-spi.asciidoc | 51 +++++++++++
.../org/mapstruct/ap/MappingProcessor.java | 84 ++++++++++++++++++-
.../util/AnnotationProcessorContext.java | 9 +-
.../mapstruct/ap/internal/util/Services.java | 4 +
.../AdditionalSupportedOptionsProvider.java | 23 +++++
.../spi/MapStructProcessingEnvironment.java | 9 ++
...dditionalSupportedOptionsProviderTest.java | 56 +++++++++++++
...tomAdditionalSupportedOptionsProvider.java | 22 +++++
.../EmptyMapper.java | 12 +++
...lidAdditionalSupportedOptionsProvider.java | 20 +++++
.../test/additionalsupportedoptions/Pet.java | 14 ++++
.../PetWithMissing.java | 15 ++++
.../UnknownEnumMappingStrategy.java | 29 +++++++
.../UnknownEnumMappingStrategyMapper.java | 17 ++++
14 files changed, 361 insertions(+), 4 deletions(-)
create mode 100644 processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java
diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
index 1095d41656..ce9b288428 100644
--- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
@@ -370,4 +370,55 @@ A nice example is to provide support for a custom transformation strategy.
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
+====
+
+[[additional-supported-options-provider]]
+=== Additional Supported Options Provider
+
+SPI name: `org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider`
+
+MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessor
+to the individual SPIs, by implementing `AdditionalSupportedOptionsProvider` via the Service Provider Interface (SPI).
+
+.Custom Additional Supported Options Provider that declares `myorg.custom.defaultNullEnumConstant` as an option to pass through
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java[tag=documentation]
+----
+====
+
+The value of this option is provided by including an `arg` to the `compilerArgs` tag when defining your custom SPI
+implementation.
+
+.Example maven configuration with additional options
+====
+[source, maven, linenums]
+[subs="verbatim,attributes"]
+----
+
+
+
+ org.myorg
+ custom-spi-impl
+ ${project.version}
+
+
+
+ -Amyorg.custom.defaultNullEnumConstant=MISSING
+
+
+----
+====
+
+Your custom SPI implementations can then access this configured value via `MapStructProcessingEnvironment#getOptions()`.
+
+.Accessing your custom options
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/UnknownEnumMappingStrategy.java[tag=documentation]
+----
====
\ No newline at end of file
diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
index 8c842f007b..407166f6fa 100644
--- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
@@ -8,6 +8,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@@ -33,17 +34,19 @@
import javax.lang.model.util.ElementKindVisitor6;
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.Options;
-import org.mapstruct.ap.internal.gem.MapperGem;
-import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext;
import org.mapstruct.ap.internal.processor.ModelElementProcessor;
import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext;
import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.AnnotationProcessorContext;
import org.mapstruct.ap.internal.util.RoundContext;
+import org.mapstruct.ap.internal.util.Services;
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import static javax.lang.model.element.ElementKind.CLASS;
@@ -113,6 +116,9 @@ public class MappingProcessor extends AbstractProcessor {
protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy";
protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy";
+ private final Set additionalSupportedOptions;
+ private final String additionalSupportedOptionsError;
+
private Options options;
private AnnotationProcessorContext annotationProcessorContext;
@@ -128,6 +134,21 @@ public class MappingProcessor extends AbstractProcessor {
*/
private Set deferredMappers = new HashSet<>();
+ public MappingProcessor() {
+ Set additionalSupportedOptions;
+ String additionalSupportedOptionsError;
+ try {
+ additionalSupportedOptions = resolveAdditionalSupportedOptions();
+ additionalSupportedOptionsError = null;
+ }
+ catch ( IllegalStateException ex ) {
+ additionalSupportedOptions = Collections.emptySet();
+ additionalSupportedOptionsError = ex.getMessage();
+ }
+ this.additionalSupportedOptions = additionalSupportedOptions;
+ this.additionalSupportedOptionsError = additionalSupportedOptionsError;
+ }
+
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init( processingEnv );
@@ -138,8 +159,13 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
processingEnv.getTypeUtils(),
processingEnv.getMessager(),
options.isDisableBuilders(),
- options.isVerbose()
+ options.isVerbose(),
+ resolveAdditionalOptions( processingEnv.getOptions() )
);
+
+ if ( additionalSupportedOptionsError != null ) {
+ processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError );
+ }
}
private Options createOptions() {
@@ -225,6 +251,17 @@ else if ( !deferredMappers.isEmpty() ) {
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
}
+ @Override
+ public Set getSupportedOptions() {
+ Set supportedOptions = super.getSupportedOptions();
+ if ( additionalSupportedOptions.isEmpty() ) {
+ return supportedOptions;
+ }
+ Set allSupportedOptions = new HashSet<>( supportedOptions );
+ allSupportedOptions.addAll( additionalSupportedOptions );
+ return allSupportedOptions;
+ }
+
/**
* Gets fresh copies of all mappers deferred from previous rounds (the originals may contain references to
* erroneous source/target type elements).
@@ -407,6 +444,35 @@ public TypeElement visitTypeAsClass(TypeElement e, Void p) {
);
}
+ /**
+ * Fetch the additional supported options provided by the SPI {@link AdditionalSupportedOptionsProvider}.
+ *
+ * @return the additional supported options
+ */
+ private static Set resolveAdditionalSupportedOptions() {
+ Set additionalSupportedOptions = null;
+ for ( AdditionalSupportedOptionsProvider optionsProvider :
+ Services.all( AdditionalSupportedOptionsProvider.class ) ) {
+ if ( additionalSupportedOptions == null ) {
+ additionalSupportedOptions = new HashSet<>();
+ }
+ Set providerOptions = optionsProvider.getAdditionalSupportedOptions();
+
+ for ( String providerOption : providerOptions ) {
+ // Ensure additional options are not in the mapstruct namespace
+ if ( providerOption.startsWith( "mapstruct" ) ) {
+ throw new IllegalStateException(
+ "Additional SPI options cannot start with \"mapstruct\". Provider " + optionsProvider +
+ " provided option " + providerOption );
+ }
+ additionalSupportedOptions.add( providerOption );
+ }
+
+ }
+
+ return additionalSupportedOptions == null ? Collections.emptySet() : additionalSupportedOptions;
+ }
+
private static class ProcessorComparator implements Comparator> {
@Override
@@ -425,4 +491,16 @@ private DeferredMapper(TypeElement deferredMapperElement, Element erroneousEleme
this.erroneousElement = erroneousElement;
}
}
+
+ /**
+ * Filters only the options belonging to the declared additional supported options.
+ *
+ * @param options all processor environment options
+ * @return filtered options
+ */
+ private Map resolveAdditionalOptions(Map options) {
+ return options.entrySet().stream()
+ .filter( entry -> additionalSupportedOptions.contains( entry.getKey() ) )
+ .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
index 7421dfde7a..81640848d0 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java
@@ -55,8 +55,10 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
private boolean disableBuilder;
private boolean verbose;
+ private Map options;
+
public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder,
- boolean verbose) {
+ boolean verbose, Map options) {
astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList(
findAstModifyingAnnotationProcessors( messager ) );
this.elementUtils = elementUtils;
@@ -64,6 +66,7 @@ public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messag
this.messager = messager;
this.disableBuilder = disableBuilder;
this.verbose = verbose;
+ this.options = java.util.Collections.unmodifiableMap( options );
}
/**
@@ -270,4 +273,8 @@ public Map getEnumTransformationStrategies()
initialize();
return enumTransformationStrategies;
}
+
+ public Map getOptions() {
+ return this.options;
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
index 2d37c46db8..292512ff80 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java
@@ -18,6 +18,10 @@ public class Services {
private Services() {
}
+ public static Iterable all(Class serviceType) {
+ return ServiceLoader.load( serviceType, Services.class.getClassLoader() );
+ }
+
public static T get(Class serviceType, T defaultValue) {
Iterator services = ServiceLoader.load( serviceType, Services.class.getClassLoader() ).iterator();
diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java
new file mode 100644
index 0000000000..3638f70320
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.spi;
+
+import java.util.Set;
+
+/**
+ * Provider for any additional supported options required for custom SPI implementations.
+ * The resolved values are retrieved from {@link MapStructProcessingEnvironment#getOptions()}.
+ */
+public interface AdditionalSupportedOptionsProvider {
+
+ /**
+ * Returns the supported options required for custom SPI implementations.
+ *
+ * @return the additional supported options.
+ */
+ Set getAdditionalSupportedOptions();
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
index 0471108a51..0cea4369eb 100644
--- a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
+++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java
@@ -7,6 +7,7 @@
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
+import java.util.Map;
/**
* MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use
@@ -36,4 +37,12 @@ public interface MapStructProcessingEnvironment {
*/
Types getTypeUtils();
+ /**
+ * Returns the resolved options specified by the impl of
+ * {@link AdditionalSupportedOptionsProvider}.
+ *
+ * @return resolved options
+ */
+ Map getOptions();
+
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java
new file mode 100644
index 0000000000..ff2c85139f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.mapstruct.ap.spi.EnumMappingStrategy;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.WithServiceImplementation;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
+import org.mapstruct.ap.testutil.runner.Compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Execution( ExecutionMode.CONCURRENT )
+public class AdditionalSupportedOptionsProviderTest {
+
+ @ProcessorTest
+ @WithClasses({
+ Pet.class,
+ PetWithMissing.class,
+ UnknownEnumMappingStrategyMapper.class
+ })
+ @WithServiceImplementation(CustomAdditionalSupportedOptionsProvider.class)
+ @WithServiceImplementation(value = UnknownEnumMappingStrategy.class, provides = EnumMappingStrategy.class)
+ @ProcessorOption(name = "myorg.custom.defaultNullEnumConstant", value = "MISSING")
+ public void shouldUseConfiguredPrefix() {
+ assertThat( UnknownEnumMappingStrategyMapper.INSTANCE.map( null ) )
+ .isEqualTo( PetWithMissing.MISSING );
+ }
+
+ @ProcessorTest(Compiler.JDK) // The eclipse compiler does not parse the error message properly
+ @WithClasses({
+ EmptyMapper.class
+ })
+ @WithServiceImplementation(InvalidAdditionalSupportedOptionsProvider.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ messageRegExp = "Additional SPI options cannot start with \"mapstruct\". Provider " +
+ "org.mapstruct.ap.test.additionalsupportedoptions.InvalidAdditionalSupportedOptionsProvider@.*" +
+ " provided option mapstruct.custom.test"
+ )
+ )
+ public void shouldFailWhenOptionsProviderUsesMapstructPrefix() {
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java
new file mode 100644
index 0000000000..87a994bb4e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+// tag::documentation[]
+import java.util.Collections;
+import java.util.Set;
+
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
+
+public class CustomAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider {
+
+ @Override
+ public Set getAdditionalSupportedOptions() {
+ return Collections.singleton( "myorg.custom.defaultNullEnumConstant" );
+ }
+
+}
+// end::documentation[]
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java
new file mode 100644
index 0000000000..13a6f0aece
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+import org.mapstruct.Mapper;
+
+@Mapper
+public interface EmptyMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java
new file mode 100644
index 0000000000..1a387abba2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
+
+public class InvalidAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider {
+
+ @Override
+ public Set getAdditionalSupportedOptions() {
+ return Collections.singleton( "mapstruct.custom.test" );
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java
new file mode 100644
index 0000000000..8fe9b39558
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+public enum Pet {
+
+ DOG,
+ CAT,
+ BEAR
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java
new file mode 100644
index 0000000000..21d5f4a649
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+public enum PetWithMissing {
+
+ DOG,
+ CAT,
+ BEAR,
+ MISSING
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java
new file mode 100644
index 0000000000..ca4403925e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+// tag::documentation[]
+import javax.lang.model.element.TypeElement;
+
+import org.mapstruct.ap.spi.DefaultEnumMappingStrategy;
+import org.mapstruct.ap.spi.MapStructProcessingEnvironment;
+
+public class UnknownEnumMappingStrategy extends DefaultEnumMappingStrategy {
+
+ private String defaultNullEnumConstant;
+
+ @Override
+ public void init(MapStructProcessingEnvironment processingEnvironment) {
+ super.init( processingEnvironment );
+ defaultNullEnumConstant = processingEnvironment.getOptions().get( "myorg.custom.defaultNullEnumConstant" );
+ }
+
+ @Override
+ public String getDefaultNullEnumConstant(TypeElement enumType) {
+ return defaultNullEnumConstant;
+ }
+}
+// end::documentation[]
diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java
new file mode 100644
index 0000000000..be8c74e227
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.additionalsupportedoptions;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface UnknownEnumMappingStrategyMapper {
+
+ UnknownEnumMappingStrategyMapper INSTANCE = Mappers.getMapper( UnknownEnumMappingStrategyMapper.class );
+
+ PetWithMissing map(Pet pet);
+}
From d3b4a168b7abef6c31fea4f3506996ffe23ad718 Mon Sep 17 00:00:00 2001
From: Bragolgirith
Date: Mon, 1 May 2023 09:11:05 +0200
Subject: [PATCH 072/320] #3199 Add support for implicit conversion between
java.time.LocalDate and java.time.LocalDateTime
---
.../chapter-5-data-type-conversions.asciidoc | 2 +
.../internal/conversion/ConversionUtils.java | 12 +++++
.../ap/internal/conversion/Conversions.java | 5 +-
...avaLocalDateTimeToLocalDateConversion.java | 46 +++++++++++++++++++
.../java8time/Java8TimeConversionTest.java | 15 ++++++
.../ap/test/conversion/java8time/Source.java | 10 ++++
.../ap/test/conversion/java8time/Target.java | 11 +++++
.../java8time/SourceTargetMapperImpl.java | 36 +++++++++++++++
8 files changed, 136 insertions(+), 1 deletion(-)
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.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 51ccb21a46..258c7085f2 100644
--- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
@@ -107,6 +107,8 @@ public interface CarMapper {
* Between `java.time.Instant` from Java 8 Date-Time package and `java.util.Date`.
+* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.time.LocalDate` from the same package.
+
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`.
* Between `java.sql.Date` and `java.util.Date`
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 e88bb93b86..496d11676f 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
@@ -12,6 +12,7 @@
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
@@ -190,6 +191,17 @@ public static String zoneId(ConversionContext conversionContext) {
return typeReferenceName( conversionContext, ZoneId.class );
}
+ /**
+ * Name for {@link java.time.LocalDate}.
+ *
+ * @param conversionContext Conversion context
+ *
+ * @return Name or fully-qualified name.
+ */
+ public static String localDate(ConversionContext conversionContext) {
+ return typeReferenceName( conversionContext, LocalDate.class );
+ }
+
/**
* Name for {@link java.time.LocalDateTime}.
*
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 e947c78571..9a4085df83 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
@@ -233,12 +233,15 @@ private void registerJava8TimeConversions() {
register( Period.class, String.class, new StaticParseToStringConversion() );
register( Duration.class, String.class, new StaticParseToStringConversion() );
- // Java 8 to Date
+ // Java 8 time to Date
register( ZonedDateTime.class, Date.class, new JavaZonedDateTimeToDateConversion() );
register( LocalDateTime.class, Date.class, new JavaLocalDateTimeToDateConversion() );
register( LocalDate.class, Date.class, new JavaLocalDateToDateConversion() );
register( Instant.class, Date.class, new JavaInstantToDateConversion() );
+ // Java 8 time
+ register( LocalDateTime.class, LocalDate.class, new JavaLocalDateTimeToLocalDateConversion() );
+
}
private void registerJavaTimeSqlConversions() {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java
new file mode 100644
index 0000000000..ae571ad6b0
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.internal.conversion;
+
+import java.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
+ * {@link LocalDate} and vice versa.
+ */
+
+public class JavaLocalDateTimeToLocalDateConversion extends SimpleConversion {
+
+ @Override
+ 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/conversion/java8time/Java8TimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java
index aa30865425..0059630532 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java
@@ -252,6 +252,21 @@ public void testInstantToDateMapping() {
assertThat( source.getForDateConversionWithInstant() ).isEqualTo( instant );
}
+ @ProcessorTest
+ public void testLocalDateTimeToLocalDateMapping() {
+ LocalDate localDate = LocalDate.of( 2014, 1, 1 );
+
+ Source source = new Source();
+ source.setForLocalDateTimeConversionWithLocalDate( localDate );
+ Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source );
+ LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate();
+ assertThat( localDateTime ).isNotNull();
+ assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) );
+
+ source = SourceTargetMapper.INSTANCE.targetToSource( target );
+ assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).isEqualTo( localDate );
+ }
+
@ProcessorTest
@DefaultTimeZone("Australia/Melbourne")
public void testLocalDateTimeToDateMapping() {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java
index 91298617cd..93479f8a86 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java
@@ -38,6 +38,8 @@ public class Source {
private Instant forDateConversionWithInstant;
+ private LocalDate forLocalDateTimeConversionWithLocalDate;
+
private Instant forInstantConversionWithString;
private Period forPeriodConversionWithString;
@@ -124,6 +126,14 @@ public void setForDateConversionWithInstant(Instant forDateConversionWithInstant
this.forDateConversionWithInstant = forDateConversionWithInstant;
}
+ public LocalDate getForLocalDateTimeConversionWithLocalDate() {
+ return forLocalDateTimeConversionWithLocalDate;
+ }
+
+ public void setForLocalDateTimeConversionWithLocalDate(LocalDate forLocalDateTimeConversionWithLocalDate) {
+ this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate;
+ }
+
public Instant getForInstantConversionWithString() {
return forInstantConversionWithString;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java
index 188a6d0e20..d2a4878731 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java
@@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.test.conversion.java8time;
+import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
@@ -33,6 +34,8 @@ public class Target {
private Date forDateConversionWithInstant;
+ private LocalDateTime forLocalDateTimeConversionWithLocalDate;
+
private String forInstantConversionWithString;
private String forPeriodConversionWithString;
@@ -119,6 +122,14 @@ public void setForDateConversionWithInstant(Date forDateConversionWithInstant) {
this.forDateConversionWithInstant = forDateConversionWithInstant;
}
+ public LocalDateTime getForLocalDateTimeConversionWithLocalDate() {
+ return forLocalDateTimeConversionWithLocalDate;
+ }
+
+ public void setForLocalDateTimeConversionWithLocalDate(LocalDateTime forLocalDateTimeConversionWithLocalDate) {
+ this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate;
+ }
+
public String getForInstantConversionWithString() {
return forInstantConversionWithString;
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java
index 58beaa19dd..3e0f5f84fe 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java
@@ -68,6 +68,9 @@ public Target sourceToTarget(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -117,6 +120,9 @@ public Target sourceToTargetDefaultMapping(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -166,6 +172,9 @@ public Target sourceToTargetDateTimeMapped(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -215,6 +224,9 @@ public Target sourceToTargetLocalDateTimeMapped(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -264,6 +276,9 @@ public Target sourceToTargetLocalDateMapped(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -313,6 +328,9 @@ public Target sourceToTargetLocalTimeMapped(Source source) {
if ( source.getForDateConversionWithInstant() != null ) {
target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) );
}
+ if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() );
+ }
if ( source.getForInstantConversionWithString() != null ) {
target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() );
}
@@ -362,6 +380,9 @@ public Source targetToSource(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
@@ -411,6 +432,9 @@ public Source targetToSourceDateTimeMapped(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
@@ -460,6 +484,9 @@ public Source targetToSourceLocalDateTimeMapped(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
@@ -509,6 +536,9 @@ public Source targetToSourceLocalDateMapped(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
@@ -558,6 +588,9 @@ public Source targetToSourceLocalTimeMapped(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
@@ -607,6 +640,9 @@ public Source targetToSourceDefaultMapping(Target target) {
if ( target.getForDateConversionWithInstant() != null ) {
source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() );
}
+ if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) {
+ source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() );
+ }
if ( target.getForInstantConversionWithString() != null ) {
source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) );
}
From 970984785d5d2714fc398255c5938ee539a0d8c0 Mon Sep 17 00:00:00 2001
From: todzhang's cloudsdocker
Date: Mon, 1 May 2023 17:12:23 +1000
Subject: [PATCH 073/320] Update one typo in JavaDoc (#2938)
---
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 92ae29eb54..42384152d0 100644
--- a/core/src/main/java/org/mapstruct/Mapping.java
+++ b/core/src/main/java/org/mapstruct/Mapping.java
@@ -287,7 +287,7 @@
/**
* Whether the property specified via {@link #target()} should be ignored by the generated mapping method or not.
- * This can be useful when certain attributes should not be propagated from source or target or when properties in
+ * This can be useful when certain attributes should not be propagated from source to target or when properties in
* the target object are populated using a decorator and thus would be reported as unmapped target property by
* default.
*
From a8df94cc2049099dce5f42ead520965da563e9b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?=
Date: Mon, 1 May 2023 09:22:59 +0200
Subject: [PATCH 074/320] #2987 Support for defining Javadoc in the generated
mapper implementation
---
core/src/main/java/org/mapstruct/Javadoc.java | 115 ++++++++++++++++++
core/src/main/java/org/mapstruct/Mapper.java | 1 +
.../chapter-3-defining-a-mapper.asciidoc | 100 +++++++++++++++
.../ap/internal/gem/GemGenerator.java | 2 +
.../ap/internal/model/GeneratedType.java | 4 +
.../mapstruct/ap/internal/model/Javadoc.java | 92 ++++++++++++++
.../mapstruct/ap/internal/model/Mapper.java | 19 ++-
.../processor/MapperCreationProcessor.java | 31 +++++
.../mapstruct/ap/internal/util/Message.java | 2 +
.../ap/internal/model/GeneratedType.ftl | 1 +
.../mapstruct/ap/internal/model/Javadoc.ftl | 25 ++++
.../test/javadoc/ErroneousJavadocMapper.java | 17 +++
.../JavadocAnnotatedWithAttributesMapper.java | 21 ++++
.../JavadocAnnotatedWithValueMapper.java | 22 ++++
.../ap/test/javadoc/JavadocTest.java | 54 ++++++++
...adocAnnotatedWithAttributesMapperImpl.java | 27 ++++
.../JavadocAnnotatedWithValueMapperImpl.java | 27 ++++
17 files changed, 558 insertions(+), 2 deletions(-)
create mode 100644 core/src/main/java/org/mapstruct/Javadoc.java
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java
create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java
create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java
create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java
diff --git a/core/src/main/java/org/mapstruct/Javadoc.java b/core/src/main/java/org/mapstruct/Javadoc.java
new file mode 100644
index 0000000000..4b5d2fb839
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/Javadoc.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Allows the definition of Javadoc comments in the MapStruct Mapper generated class.
+ *
+ * The annotation provides support for the usual Javadoc comments elements by defining analogous attributes.
+ *
+ *
+ * Please, note that at least one of these attributes must be specified.
+ *
+ *
+ * For instance, the following definition;
+ *
+ *
+ * @Javadoc(
+ * value = "This is the description",
+ * authors = { "author1", "author2" },
+ * deprecated = "Use {@link OtherMapper} instead",
+ * since = "0.1"
+ * )
+ *
+ *
+ *
+ * will generate:
+ *
+ *
+ *
+ * /**
+ * * This is the description
+ * *
+ * * @author author1
+ * * @author author2
+ * *
+ * * @deprecated Use {@link OtherMapper} instead
+ * * @since 0.1
+ * */
+ *
+ *
+ *
+ * The entire Javadoc comment block can be passed directly:
+ *
+ *
+ * @Javadoc("This is the description\n"
+ * + "\n"
+ * + "@author author1\n"
+ * + "@author author2\n"
+ * + "\n"
+ * + "@deprecated Use {@link OtherMapper} instead\n"
+ * + "@since 0.1\n"
+ * )
+ *
+ *
+ *
+ * // or using Text Blocks
+ * @Javadoc(
+ * """
+ * This is the description
+ *
+ * @author author1
+ * @author author2
+ *
+ * @deprecated Use {@link OtherMapper} instead
+ * @since 0.1
+ * """
+ * )
+ *
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Javadoc {
+ /**
+ * Main Javadoc comment text block.
+ *
+ * @return Main Javadoc comment text block.
+ */
+ String value() default "";
+
+ /**
+ * List of authors of the code that it is being documented.
+ *
+ * It will generate a list of the Javadoc tool comment element @author
+ * with the different values and in the order provided.
+ *
+ * @return array of javadoc authors.
+ */
+ String[] authors() default { };
+
+ /**
+ * Specifies that the functionality that is being documented is deprecated.
+ *
+ * Corresponds to the @deprecated Javadoc tool comment element.
+ *
+ * @return Deprecation message about the documented functionality
+ */
+ String deprecated() default "";
+
+ /**
+ * Specifies the version since the functionality that is being documented is available.
+ *
+ * Corresponds to the @since Javadoc tool comment element.
+ *
+ * @return Version since the functionality is available
+ */
+ String since() default "";
+}
diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java
index 60725cc441..8a6f48dad9 100644
--- a/core/src/main/java/org/mapstruct/Mapper.java
+++ b/core/src/main/java/org/mapstruct/Mapper.java
@@ -74,6 +74,7 @@
*
*
* @author Gunnar Morling
+ * @see Javadoc
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
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 8609ac4f77..eb3d5a80ec 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -761,3 +761,103 @@ public class MyConverterImpl implements MyConverter {
}
----
====
+
+
+[[javadoc]]
+=== Adding Javadoc comments
+
+MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the
+`org.mapstruct.Javadoc` annotation.
+
+This functionality could be relevant especially in situations where certain Javadoc standards need to be met or
+to deal with Javadoc validation constraints.
+
+The `@Javadoc` annotation defines attributes for the different Javadoc elements.
+
+Consider the following example:
+
+.Javadoc annotation example
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ value = "This is the description",
+ authors = { "author1", "author2" },
+ deprecated = "Use {@link OtherMapper} instead",
+ since = "0.1"
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+.Javadoc annotated generated mapper
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+/**
+* This is the description
+*
+* @author author1
+* @author author2
+*
+* @deprecated Use {@link OtherMapper} instead
+* @since 0.1
+*/
+public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+The entire Javadoc comment block can be provided directly as well.
+
+.Javadoc annotation example with the entire Javadoc comment block provided directly
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ "This is the description\n"
+ + "\n"
+ + "@author author1\n"
+ + "@author author2\n"
+ + "\n"
+ + "@deprecated Use {@link OtherMapper} instead\n"
+ + "@since 0.1\n"
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+Or using Text blocks:
+
+.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ """
+ This is the description
+
+ @author author1
+ @author author2
+
+ @deprecated Use {@link OtherMapper} instead
+ @since 0.1
+ """
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
\ No newline at end of file
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java
index 5caea8a008..9ac13184cd 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
@@ -21,6 +21,7 @@
import org.mapstruct.InheritConfiguration;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.IterableMapping;
+import org.mapstruct.Javadoc;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MapperConfig;
@@ -75,6 +76,7 @@
@GemDefinition(Context.class)
@GemDefinition(Builder.class)
@GemDefinition(Condition.class)
+@GemDefinition(Javadoc.class)
@GemDefinition(MappingControl.class)
@GemDefinition(MappingControls.class)
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 df6ed9b2b1..8348ecd84b 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
@@ -253,6 +253,10 @@ public void removeConstructor() {
constructor = null;
}
+ public Javadoc getJavadoc() {
+ return null;
+ }
+
protected void addIfImportRequired(Collection collection, Type typeToAdd) {
if ( typeToAdd == null ) {
return;
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java
new file mode 100644
index 0000000000..a1efc8c022
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.internal.model;
+
+import org.mapstruct.ap.internal.model.common.ModelElement;
+import org.mapstruct.ap.internal.model.common.Type;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents the javadoc information that should be generated for a {@link Mapper}.
+ *
+ * @author Jose Carlos Campanero Ortiz
+ */
+public class Javadoc extends ModelElement {
+
+ public static class Builder {
+
+ private String value;
+ private List authors;
+ private String deprecated;
+ private String since;
+
+ public Builder value(String value) {
+ this.value = value;
+ return this;
+ }
+
+ public Builder authors(List authors) {
+ this.authors = authors;
+ return this;
+ }
+
+ public Builder deprecated(String deprecated) {
+ this.deprecated = deprecated;
+ return this;
+ }
+
+ public Builder since(String since) {
+ this.since = since;
+ return this;
+ }
+
+ public Javadoc build() {
+ return new Javadoc(
+ value,
+ authors,
+ deprecated,
+ since
+ );
+ }
+ }
+
+ private final String value;
+ private final List authors;
+ private final String deprecated;
+ private final String since;
+
+ private Javadoc(String value, List authors, String deprecated, String since) {
+ this.value = value;
+ this.authors = authors != null ? Collections.unmodifiableList( authors ) : Collections.emptyList();
+ this.deprecated = deprecated;
+ this.since = since;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public List getAuthors() {
+ return authors;
+ }
+
+ public String getDeprecated() {
+ return deprecated;
+ }
+
+ public String getSince() {
+ return since;
+ }
+
+ @Override
+ public Set getImportTypes() {
+ return Collections.emptySet();
+ }
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
index 9b7729e8fa..cd092ca4f4 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java
@@ -43,6 +43,7 @@ public static class Builder extends GeneratedTypeBuilder {
private boolean customPackage;
private boolean suppressGeneratorTimestamp;
private Set customAnnotations;
+ private Javadoc javadoc;
public Builder() {
super( Builder.class );
@@ -90,6 +91,11 @@ public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) {
return this;
}
+ public Builder javadoc(Javadoc javadoc) {
+ this.javadoc = javadoc;
+ return this;
+ }
+
public Mapper build() {
String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) +
( decorator == null ? "" : "_" );
@@ -119,7 +125,8 @@ public Mapper build() {
fields,
constructor,
decorator,
- extraImportedTypes
+ extraImportedTypes,
+ javadoc
);
}
@@ -128,6 +135,7 @@ public Mapper build() {
private final boolean customPackage;
private final boolean customImplName;
private Decorator decorator;
+ private final Javadoc javadoc;
@SuppressWarnings( "checkstyle:parameternumber" )
private Mapper(TypeFactory typeFactory, String packageName, String name,
@@ -136,7 +144,7 @@ private Mapper(TypeFactory typeFactory, String packageName, String name,
List methods, Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp,
Accessibility accessibility, List fields, Constructor constructor,
- Decorator decorator, SortedSet extraImportedTypes ) {
+ Decorator decorator, SortedSet extraImportedTypes, Javadoc javadoc ) {
super(
typeFactory,
@@ -157,6 +165,8 @@ private Mapper(TypeFactory typeFactory, String packageName, String name,
customAnnotations.forEach( this::addAnnotation );
this.decorator = decorator;
+
+ this.javadoc = javadoc;
}
public Decorator getDecorator() {
@@ -171,6 +181,11 @@ public boolean hasCustomImplementation() {
return customImplName || customPackage;
}
+ @Override
+ public Javadoc getJavadoc() {
+ return javadoc;
+ }
+
@Override
protected String getTemplateName() {
return getTemplateNameForClass( GeneratedType.class );
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 b69ba388b0..24dd528f6b 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
@@ -28,6 +28,7 @@
import org.mapstruct.ap.internal.gem.DecoratedWithGem;
import org.mapstruct.ap.internal.gem.InheritConfigurationGem;
import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem;
+import org.mapstruct.ap.internal.gem.JavadocGem;
import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
@@ -40,6 +41,7 @@
import org.mapstruct.ap.internal.model.DelegatingMethod;
import org.mapstruct.ap.internal.model.Field;
import org.mapstruct.ap.internal.model.IterableMappingMethod;
+import org.mapstruct.ap.internal.model.Javadoc;
import org.mapstruct.ap.internal.model.MapMappingMethod;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.MapperReference;
@@ -212,6 +214,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List<
.implPackage( mapperOptions.implementationPackage() )
.suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() )
.additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) )
+ .javadoc( getJavadoc( element ) )
.build();
if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) {
@@ -441,6 +444,23 @@ else if ( method.isStreamMapping() ) {
return mappingMethods;
}
+ private Javadoc getJavadoc(TypeElement element) {
+ JavadocGem javadocGem = JavadocGem.instanceOn( element );
+
+ if ( javadocGem == null || !isConsistent( javadocGem, element, messager ) ) {
+ return null;
+ }
+
+ Javadoc javadoc = new Javadoc.Builder()
+ .value( javadocGem.value().getValue() )
+ .authors( javadocGem.authors().getValue() )
+ .deprecated( javadocGem.deprecated().getValue() )
+ .since( javadocGem.since().getValue() )
+ .build();
+
+ return javadoc;
+ }
+
private Type getUserDesiredReturnType(SourceMethod method) {
SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters();
if ( selectionParameters != null && selectionParameters.getResultType() != null ) {
@@ -810,4 +830,15 @@ private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMe
onlyCandidate.getName()
);
}
+
+ private boolean isConsistent( JavadocGem gem, TypeElement element, FormattingMessager messager ) {
+ if ( !gem.value().hasValue()
+ && !gem.authors().hasValue()
+ && !gem.deprecated().hasValue()
+ && !gem.since().hasValue() ) {
+ messager.printMessage( element, gem.mirror(), Message.JAVADOC_NO_ELEMENTS );
+ return false;
+ }
+ return true;
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
index 29600489fb..6f72f720f1 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
@@ -130,6 +130,8 @@ public enum Message {
DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ),
DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ),
+ JAVADOC_NO_ELEMENTS( "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, define at least one of them." ),
+
GENERAL_CANNOT_IMPLEMENT_PRIVATE_MAPPER("Cannot create an implementation for mapper %s, because it is a private %s."),
GENERAL_NO_IMPLEMENTATION( "No implementation type is registered for return type %s." ),
GENERAL_ABSTRACT_RETURN_TYPE( "The return type %s is an abstract class or interface. Provide a non abstract / non interface result type or a factory method." ),
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl
index 4981882403..c65b3e5f85 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl
@@ -14,6 +14,7 @@ package ${packageName};
import ${importedType};
#list>
+<#if javadoc??><#nt><@includeModel object=javadoc/>#if>
<#if !generatedTypeAvailable>/*#if>
@Generated(
value = "org.mapstruct.ap.MappingProcessor"<#if suppressGeneratorTimestamp == false>,
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl
new file mode 100644
index 0000000000..89ac57b9ef
--- /dev/null
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl
@@ -0,0 +1,25 @@
+<#--
+
+ Copyright MapStruct Authors.
+
+ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+
+-->
+<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Javadoc" -->
+/**
+<#list value?split("\n") as line><#nt>*<#if line?has_content> #if>${line?trim}
+#list>
+<#if !authors.isEmpty()>
+*
+<#list authors as author> <#nt>* @author ${author?trim}
+#list>
+#if>
+<#if deprecated?has_content>
+*
+<#nt>* @deprecated ${deprecated?trim}
+#if>
+<#if since?has_content>
+*
+<#nt>* @since ${since?trim}
+#if>
+<#nt> */
\ No newline at end of file
diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java
new file mode 100644
index 0000000000..39f6da2890
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.javadoc;
+
+import org.mapstruct.Javadoc;
+import org.mapstruct.Mapper;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@Mapper
+@Javadoc
+public interface ErroneousJavadocMapper {
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java
new file mode 100644
index 0000000000..eb6285ff13
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.javadoc;
+
+import org.mapstruct.Javadoc;
+import org.mapstruct.Mapper;
+
+@Mapper
+@Javadoc(
+ value = "This is the description",
+ authors = { "author1", "author2" },
+ deprecated = "Use {@link OtherMapper} instead",
+ since = "0.1"
+)
+@Deprecated
+public interface JavadocAnnotatedWithAttributesMapper {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java
new file mode 100644
index 0000000000..150d7e5f76
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.javadoc;
+
+import org.mapstruct.Javadoc;
+import org.mapstruct.Mapper;
+
+@Mapper
+@Javadoc("This is the description\n"
+ + "\n"
+ + "@author author1\n"
+ + "@author author2\n"
+ + "\n"
+ + "@deprecated Use {@link OtherMapper} instead\n"
+ + "@since 0.1\n")
+@Deprecated
+public interface JavadocAnnotatedWithValueMapper {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java
new file mode 100644
index 0000000000..316114389f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.javadoc;
+
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+/**
+ * @author Jose Carlos Campanero Ortiz
+ */
+@IssueKey("2987")
+class JavadocTest {
+
+ @RegisterExtension
+ final GeneratedSource generatedSource = new GeneratedSource();
+
+ @ProcessorTest
+ @WithClasses( { JavadocAnnotatedWithValueMapper.class } )
+ void javadocAnnotatedWithValueMapper() {
+ generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithValueMapper.class );
+ }
+
+ @ProcessorTest
+ @WithClasses( { JavadocAnnotatedWithAttributesMapper.class } )
+ void javadocAnnotatedWithAttributesMapper() {
+ generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithAttributesMapper.class );
+ }
+
+ @ProcessorTest
+ @IssueKey("2987")
+ @WithClasses({ ErroneousJavadocMapper.class })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousJavadocMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 15,
+ message = "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, "
+ + "define at least one of them.")
+ }
+ )
+ void shouldFailOnEmptyJavadocAnnotation() {
+ }
+
+}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java
new file mode 100644
index 0000000000..53b9a08e92
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.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.javadoc;
+
+import javax.annotation.processing.Generated;
+
+/**
+* This is the description
+*
+* @author author1
+* @author author2
+*
+* @deprecated Use {@link OtherMapper} instead
+*
+* @since 0.1
+*/
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2023-04-30T17:36:38+0200",
+ comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)"
+)
+@Deprecated
+public class JavadocAnnotatedWithAttributesMapperImpl implements JavadocAnnotatedWithAttributesMapper {
+}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java
new file mode 100644
index 0000000000..7bcc54ca44
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.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.javadoc;
+
+import javax.annotation.processing.Generated;
+
+/**
+* This is the description
+*
+* @author author1
+* @author author2
+*
+* @deprecated Use {@link OtherMapper} instead
+* @since 0.1
+*
+*/
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2023-04-30T17:38:45+0200",
+ comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)"
+)
+@Deprecated
+public class JavadocAnnotatedWithValueMapperImpl implements JavadocAnnotatedWithValueMapper {
+}
From 4843123e6ef98589831a8cc8b7cc4330c7f9ed64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Etien=20Ro=C5=BEnik?=
<12816736+eroznik@users.noreply.github.com>
Date: Mon, 1 May 2023 09:42:58 +0200
Subject: [PATCH 075/320] #3165 Support adders for array / iterable to
collection
---
.../ap/internal/model/PropertyMapping.java | 2 +-
.../model/assignment/AdderWrapper.java | 10 ++-
.../ap/internal/model/common/SourceRHS.java | 6 ++
.../ap/test/bugs/_3165/Issue3165Mapper.java | 65 +++++++++++++++++++
.../test/bugs/_3165/Issue3165MapperTest.java | 32 +++++++++
5 files changed, 113 insertions(+), 2 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java
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 7114ccf57b..bedb37cc8f 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
@@ -527,7 +527,7 @@ private Assignment assignToPlainViaAdder( Assignment rightHandSide) {
Assignment result = rightHandSide;
String adderIteratorName = sourcePropertyName == null ? targetPropertyName : sourcePropertyName;
- if ( result.getSourceType().isCollectionType() ) {
+ if ( result.getSourceType().isIterableType() ) {
result = new AdderWrapper( result, method.getThrownTypes(), isFieldAssignment(), adderIteratorName );
}
else if ( result.getSourceType().isStreamType() ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java
index c5778628e9..13dfe832c9 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java
@@ -39,7 +39,15 @@ public AdderWrapper( Assignment rhs,
// localVar is iteratorVariable
String desiredName = Nouns.singularize( adderIteratorName );
rhs.setSourceLoopVarName( rhs.createUniqueVarName( desiredName ) );
- adderType = first( getSourceType().determineTypeArguments( Collection.class ) );
+ if ( getSourceType().isCollectionType() ) {
+ adderType = first( getSourceType().determineTypeArguments( Collection.class ) );
+ }
+ else if ( getSourceType().isArrayType() ) {
+ adderType = getSourceType().getComponentType();
+ }
+ else { // iterable
+ adderType = first( getSourceType().determineTypeArguments( Iterable.class ) );
+ }
}
@Override
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java
index b73d1ecf44..b4d422c797 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java
@@ -149,6 +149,12 @@ public Type getSourceTypeForMatching() {
else if ( sourceType.isStreamType() ) {
return first( sourceType.determineTypeArguments( Stream.class ) );
}
+ else if ( sourceType.isArrayType() ) {
+ return sourceType.getComponentType();
+ }
+ else if ( sourceType.isIterableType() ) {
+ return first( sourceType.determineTypeArguments( Iterable.class ) );
+ }
}
return sourceType;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java
new file mode 100644
index 0000000000..e065871ea3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3165;
+
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
+public interface Issue3165Mapper {
+
+ Issue3165Mapper INSTANCE = Mappers.getMapper( Issue3165Mapper.class );
+
+ Target toTarget(Source source);
+
+ class Source {
+ private String[] pets;
+ private Iterable cats;
+
+ public Source(String[] pets, Iterable cats) {
+ this.pets = pets;
+ this.cats = cats;
+ }
+
+ public String[] getPets() {
+ return pets;
+ }
+
+ public Iterable getCats() {
+ return cats;
+ }
+ }
+
+ class Target {
+ private List pets;
+ private List cats;
+
+ Target() {
+ this.pets = new ArrayList<>();
+ this.cats = new ArrayList<>();
+ }
+
+ public List getPets() {
+ return pets;
+ }
+
+ public void addPet(String pet) {
+ pets.add( pet );
+ }
+
+ public List getCats() {
+ return cats;
+ }
+
+ public void addCat(String cat) {
+ cats.add( cat );
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java
new file mode 100644
index 0000000000..4fb9e0ab3a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3165;
+
+import java.util.Arrays;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@WithClasses({
+ Issue3165Mapper.class
+})
+@IssueKey("3165")
+class Issue3165MapperTest {
+
+ @ProcessorTest
+ void supportsAdderWhenMappingArrayAndIterableToCollection() {
+ Issue3165Mapper.Source src = new Issue3165Mapper.Source(
+ new String[] { "cat", "dog", "mouse" },
+ Arrays.asList( "ivy", "flu", "freya" )
+ );
+ Issue3165Mapper.Target target = Issue3165Mapper.INSTANCE.toTarget( src );
+ assertThat( target.getPets() ).containsExactly( "cat", "dog", "mouse" );
+ assertThat( target.getCats() ).containsExactly( "ivy", "flu", "freya" );
+ }
+}
From f3dac94701b316e5a62df3a34e52b3d75c483fe7 Mon Sep 17 00:00:00 2001
From: MengxingYuan
Date: Mon, 1 May 2023 16:28:51 +0800
Subject: [PATCH 076/320] #2781 Remove unmapped source properties when source
parameter is directly mapped
---
.../ap/internal/model/BeanMappingMethod.java | 36 ++++++++-----
.../ap/test/bugs/_2781/Issue2781Mapper.java | 53 +++++++++++++++++++
.../ap/test/bugs/_2781/Issue2781Test.java | 24 +++++++++
3 files changed, 100 insertions(+), 13 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.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 58b4640c95..84cfd18285 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
@@ -1295,8 +1295,16 @@ else if ( mapping.getJavaExpression() != null ) {
.options( mapping )
.build();
handledTargets.add( targetPropertyName );
- unprocessedSourceParameters.remove( sourceRef.getParameter() );
- unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() );
+ Parameter sourceParameter = sourceRef.getParameter();
+ unprocessedSourceParameters.remove( sourceParameter );
+ // If the source parameter was directly mapped
+ if ( sourceRef.getPropertyEntries().isEmpty() ) {
+ // Ignore all of its source properties completely
+ ignoreSourceProperties( sourceParameter );
+ }
+ else {
+ unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() );
+ }
}
else {
errorOccured = true;
@@ -1471,23 +1479,25 @@ private void applyParameterNameBasedMapping() {
sourceParameters.remove();
unprocessedDefinedTargets.remove( targetProperty.getKey() );
unprocessedSourceProperties.remove( targetProperty.getKey() );
-
- // The source parameter was directly mapped so ignore all of its source properties completely
- if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) {
- // We explicitly ignore source properties from primitives or array types
- Map readAccessors = sourceParameter.getType()
- .getPropertyReadAccessors();
- for ( String sourceProperty : readAccessors.keySet() ) {
- unprocessedSourceProperties.remove( sourceProperty );
- }
- }
-
unprocessedConstructorProperties.remove( targetProperty.getKey() );
+ ignoreSourceProperties( sourceParameter );
}
}
}
}
+ private void ignoreSourceProperties(Parameter sourceParameter) {
+ // The source parameter was directly mapped so ignore all of its source properties completely
+ if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) {
+ // We explicitly ignore source properties from primitives or array types
+ Map readAccessors = sourceParameter.getType()
+ .getPropertyReadAccessors();
+ for ( String sourceProperty : readAccessors.keySet() ) {
+ unprocessedSourceProperties.remove( sourceProperty );
+ }
+ }
+ }
+
private SourceReference getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) {
SourceReference sourceRef = null;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java
new file mode 100644
index 0000000000..9abdf560c4
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2781;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Mengxing Yuan
+ */
+@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
+public interface Issue2781Mapper {
+
+ Issue2781Mapper INSTANCE = Mappers.getMapper( Issue2781Mapper.class );
+
+ @Mapping(target = "nested", source = "source")
+ Target map(Source source);
+
+ class Target {
+ private Source nested;
+
+ public Source getNested() {
+ return nested;
+ }
+
+ public void setNested(Source nested) {
+ this.nested = nested;
+ }
+ }
+
+ class Source {
+ private String field1;
+ private String field2;
+
+ public Source(String field1, String field2) {
+ this.field1 = field1;
+ this.field2 = field2;
+ }
+
+ public String getField1() {
+ return field1;
+ }
+
+ public String getField2() {
+ return field2;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java
new file mode 100644
index 0000000000..80ac4f5ce3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2781;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+/**
+ * @author Mengxing Yuan
+ */
+@IssueKey("2781")
+@WithClasses({
+ Issue2781Mapper.class
+})
+class Issue2781Test {
+
+ @ProcessorTest
+ void shouldCompileWithoutErrors() {
+ }
+}
From be94569791e8b587c8ef3a33c1c7e55db4a90ead Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 4 Oct 2022 23:28:54 +0000
Subject: [PATCH 077/320] Bump protobuf-java from 3.21.2 to 3.21.7 in /parent
Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.2 to 3.21.7.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.2...v3.21.7)
---
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 b306446b65..6e9c2836b6 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -41,7 +41,7 @@
The processor module needs at least Java 11.
-->
1.8
- 3.21.2
+ 3.21.7
2.3.2
From bc5a8771217365b32782f160f132d1dd2a1c4fa1 Mon Sep 17 00:00:00 2001
From: Zegveld <41897697+Zegveld@users.noreply.github.com>
Date: Mon, 1 May 2023 11:54:24 +0200
Subject: [PATCH 078/320] #3054: Allow abstract return type when all directly
sealed subtypes are covered by subclass mappings
Co-authored-by: Ben Zegveld
---
.../itest/tests/MavenIntegrationTest.java | 5 +
.../test/resources/sealedSubclassTest/pom.xml | 102 ++++++++++++++++++
.../mapstruct/itest/sealedsubclass/Bike.java | 18 ++++
.../itest/sealedsubclass/BikeDto.java | 18 ++++
.../mapstruct/itest/sealedsubclass/Car.java | 19 ++++
.../itest/sealedsubclass/CarDto.java | 18 ++++
.../itest/sealedsubclass/Davidson.java | 18 ++++
.../itest/sealedsubclass/DavidsonDto.java | 18 ++++
.../itest/sealedsubclass/Harley.java | 18 ++++
.../itest/sealedsubclass/HarleyDto.java | 18 ++++
.../mapstruct/itest/sealedsubclass/Motor.java | 18 ++++
.../itest/sealedsubclass/MotorDto.java | 18 ++++
.../sealedsubclass/SealedSubclassMapper.java | 31 ++++++
.../itest/sealedsubclass/Vehicle.java | 27 +++++
.../sealedsubclass/VehicleCollection.java | 17 +++
.../sealedsubclass/VehicleCollectionDto.java | 17 +++
.../itest/sealedsubclass/VehicleDto.java | 27 +++++
.../sealedsubclass/SealedSubclassTest.java | 59 ++++++++++
.../ap/internal/model/BeanMappingMethod.java | 35 +++++-
.../ap/internal/model/common/Type.java | 38 +++++++
20 files changed, 537 insertions(+), 2 deletions(-)
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/pom.xml
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java
create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.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 75513dd6c6..7e9175dd92 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
@@ -112,6 +112,11 @@ void namingStrategyTest() {
void protobufBuilderTest() {
}
+ @ProcessorTest(baseDir = "sealedSubclassTest")
+ @EnabledForJreRange(min = JRE.JAVA_17)
+ void sealedSubclassTest() {
+ }
+
@ProcessorTest(baseDir = "recordsTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/pom.xml b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml
new file mode 100644
index 0000000000..0706425e01
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ sealedSubclassTest
+ jar
+
+
+
+ generate-via-compiler-plugin
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ \${compiler-id}
+ --enable-preview
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-jdt
+ ${org.eclipse.tycho.compiler-jdt.version}
+
+
+
+
+
+
+
+ ${project.groupId}
+ mapstruct-processor
+ ${mapstruct.version}
+ provided
+
+
+
+
+ debug-forked-javac
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+
+ true
+
+ --enable-preview
+ -J-Xdebug
+ -J-Xnoagent
+ -J-Djava.compiler=NONE
+ -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 1
+ --enable-preview
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java
new file mode 100644
index 0000000000..5b68f52e64
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Bike extends Vehicle {
+ private int numberOfGears;
+
+ public int getNumberOfGears() {
+ return numberOfGears;
+ }
+
+ public void setNumberOfGears(int numberOfGears) {
+ this.numberOfGears = numberOfGears;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java
new file mode 100644
index 0000000000..d51e95633b
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class BikeDto extends VehicleDto {
+ private int numberOfGears;
+
+ public int getNumberOfGears() {
+ return numberOfGears;
+ }
+
+ public void setNumberOfGears(int numberOfGears) {
+ this.numberOfGears = numberOfGears;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java
new file mode 100644
index 0000000000..0ed238e2a5
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Car extends Vehicle {
+ private boolean manual;
+
+ public boolean isManual() {
+ return manual;
+ }
+
+ public void setManual(boolean manual) {
+ this.manual = manual;
+ }
+
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java
new file mode 100644
index 0000000000..800bd23d39
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class CarDto extends VehicleDto {
+ private boolean manual;
+
+ public boolean isManual() {
+ return manual;
+ }
+
+ public void setManual(boolean manual) {
+ this.manual = manual;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java
new file mode 100644
index 0000000000..e883c14be3
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Davidson extends Motor {
+ private int numberOfExhausts;
+
+ public int getNumberOfExhausts() {
+ return numberOfExhausts;
+ }
+
+ public void setNumberOfExhausts(int numberOfExhausts) {
+ this.numberOfExhausts = numberOfExhausts;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java
new file mode 100644
index 0000000000..e975226e3e
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class DavidsonDto extends MotorDto {
+ private int numberOfExhausts;
+
+ public int getNumberOfExhausts() {
+ return numberOfExhausts;
+ }
+
+ public void setNumberOfExhausts(int numberOfExhausts) {
+ this.numberOfExhausts = numberOfExhausts;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java
new file mode 100644
index 0000000000..87a48034c6
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Harley extends Motor {
+ private int engineDb;
+
+ public int getEngineDb() {
+ return engineDb;
+ }
+
+ public void setEngineDb(int engineDb) {
+ this.engineDb = engineDb;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java
new file mode 100644
index 0000000000..2090ee7450
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class HarleyDto extends MotorDto {
+ private int engineDb;
+
+ public int getEngineDb() {
+ return engineDb;
+ }
+
+ public void setEngineDb(int engineDb) {
+ this.engineDb = engineDb;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java
new file mode 100644
index 0000000000..fcd5f4e4dc
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public sealed abstract class Motor extends Vehicle permits Harley, Davidson {
+ private int cc;
+
+ public int getCc() {
+ return cc;
+ }
+
+ public void setCc(int cc) {
+ this.cc = cc;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java
new file mode 100644
index 0000000000..bd74eb9296
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public sealed abstract class MotorDto extends VehicleDto permits HarleyDto, DavidsonDto {
+ private int cc;
+
+ public int getCc() {
+ return cc;
+ }
+
+ public void setCc(int cc) {
+ this.cc = cc;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java
new file mode 100644
index 0000000000..b37f623686
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SealedSubclassMapper {
+ SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class );
+
+ VehicleCollectionDto map(VehicleCollection vehicles);
+
+ @SubclassMapping( source = Car.class, target = CarDto.class )
+ @SubclassMapping( source = Bike.class, target = BikeDto.class )
+ @SubclassMapping( source = Harley.class, target = HarleyDto.class )
+ @SubclassMapping( source = Davidson.class, target = DavidsonDto.class )
+ @Mapping( source = "vehicleManufacturingCompany", target = "maker")
+ VehicleDto map(Vehicle vehicle);
+
+ VehicleCollection mapInverse(VehicleCollectionDto vehicles);
+
+ @InheritInverseConfiguration
+ Vehicle mapInverse(VehicleDto dto);
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java
new file mode 100644
index 0000000000..2a4e7560f6
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public abstract sealed class Vehicle permits Bike, Car, Motor {
+ private String name;
+ private String vehicleManufacturingCompany;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVehicleManufacturingCompany() {
+ return vehicleManufacturingCompany;
+ }
+
+ public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) {
+ this.vehicleManufacturingCompany = vehicleManufacturingCompany;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java
new file mode 100644
index 0000000000..1ada92a298
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class VehicleCollection {
+ private Collection vehicles = new ArrayList<>();
+
+ public Collection getVehicles() {
+ return vehicles;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java
new file mode 100644
index 0000000000..0cae412177
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class VehicleCollectionDto {
+ private Collection vehicles = new ArrayList<>();
+
+ public Collection getVehicles() {
+ return vehicles;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java
new file mode 100644
index 0000000000..8c50bdcad9
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public abstract sealed class VehicleDto permits CarDto, BikeDto, MotorDto {
+ private String name;
+ private String maker;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getMaker() {
+ return maker;
+ }
+
+ public void setMaker(String maker) {
+ this.maker = maker;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java
new file mode 100644
index 0000000000..379341ff66
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+public class SealedSubclassTest {
+
+ @Test
+ public void mappingIsDoneUsingSubclassMapping() {
+ VehicleCollection vehicles = new VehicleCollection();
+ vehicles.getVehicles().add( new Car() );
+ vehicles.getVehicles().add( new Bike() );
+ vehicles.getVehicles().add( new Harley() );
+ vehicles.getVehicles().add( new Davidson() );
+
+ VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( CarDto.class, BikeDto.class, HarleyDto.class, DavidsonDto.class );
+ }
+
+ @Test
+ public void inverseMappingIsDoneUsingSubclassMapping() {
+ VehicleCollectionDto vehicles = new VehicleCollectionDto();
+ vehicles.getVehicles().add( new CarDto() );
+ vehicles.getVehicles().add( new BikeDto() );
+ vehicles.getVehicles().add( new HarleyDto() );
+ vehicles.getVehicles().add( new DavidsonDto() );
+
+ VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( Car.class, Bike.class, Harley.class, Davidson.class );
+ }
+
+ @Test
+ public void subclassMappingInheritsInverseMapping() {
+ VehicleCollectionDto vehiclesDto = new VehicleCollectionDto();
+ CarDto carDto = new CarDto();
+ carDto.setMaker( "BenZ" );
+ vehiclesDto.getVehicles().add( carDto );
+
+ VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto );
+
+ assertThat( result.getVehicles() )
+ .extracting( Vehicle::getVehicleManufacturingCompany )
+ .containsExactly( "BenZ" );
+ }
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
index 84cfd18285..cf3e23db9e 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
@@ -446,8 +446,39 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
}
private boolean isAbstractReturnTypeAllowed() {
- return method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed()
- && !method.getOptions().getSubclassMappings().isEmpty();
+ return !method.getOptions().getSubclassMappings().isEmpty()
+ && ( method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed()
+ || isCorrectlySealed() );
+ }
+
+ private boolean isCorrectlySealed() {
+ Type mappingSourceType = method.getMappingSourceType();
+ return isCorrectlySealed( mappingSourceType );
+ }
+
+ private boolean isCorrectlySealed(Type mappingSourceType) {
+ if ( mappingSourceType.isSealed() ) {
+ List extends TypeMirror> unusedPermittedSubclasses =
+ new ArrayList<>( mappingSourceType.getPermittedSubclasses() );
+ method.getOptions().getSubclassMappings().forEach( subClassOption -> {
+ for (Iterator extends TypeMirror> iterator = unusedPermittedSubclasses.iterator();
+ iterator.hasNext(); ) {
+ if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) {
+ iterator.remove();
+ }
+ }
+ } );
+ for ( Iterator extends TypeMirror> iterator = unusedPermittedSubclasses.iterator();
+ iterator.hasNext(); ) {
+ TypeMirror typeMirror = iterator.next();
+ Type type = ctx.getTypeFactory().getType( typeMirror );
+ if ( type.isAbstract() && isCorrectlySealed( type ) ) {
+ iterator.remove();
+ }
+ }
+ return unusedPermittedSubclasses.isEmpty();
+ }
+ return false;
}
private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) {
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 6894906266..4254913d83 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
@@ -5,6 +5,8 @@
*/
package org.mapstruct.ap.internal.model.common;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -53,6 +55,7 @@
import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor;
import org.mapstruct.ap.internal.util.accessor.ReadAccessor;
+import static java.util.Collections.emptyList;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
@@ -67,6 +70,18 @@
* @author Filip Hrisafov
*/
public class Type extends ModelElement implements Comparable {
+ private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD;
+
+ static {
+ Method permittedSubclassesMethod;
+ try {
+ permittedSubclassesMethod = TypeElement.class.getMethod( "getPermittedSubclasses" );
+ }
+ catch ( NoSuchMethodException e ) {
+ permittedSubclassesMethod = null;
+ }
+ SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod;
+ }
private final TypeUtils typeUtils;
private final ElementUtils elementUtils;
@@ -1661,4 +1676,27 @@ public boolean isEnumSet() {
return "java.util.EnumSet".equals( getFullyQualifiedName() );
}
+ /**
+ * return true if this type is a java 17+ sealed class
+ */
+ public boolean isSealed() {
+ return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals );
+ }
+
+ /**
+ * return the list of permitted TypeMirrors for the java 17+ sealed class
+ */
+ @SuppressWarnings( "unchecked" )
+ public List extends TypeMirror> getPermittedSubclasses() {
+ if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) {
+ return emptyList();
+ }
+ try {
+ return (List extends TypeMirror>) SEALED_PERMITTED_SUBCLASSES_METHOD.invoke( typeElement );
+ }
+ catch ( IllegalAccessException | IllegalArgumentException | InvocationTargetException e ) {
+ return emptyList();
+ }
+ }
+
}
From d0e4c48228dc03722d34deb5506ec159ac38514d Mon Sep 17 00:00:00 2001
From: Jason Bodnar
Date: Mon, 8 May 2023 15:23:03 -0500
Subject: [PATCH 079/320] #3172 Add mapping between Locale and String
---
.../chapter-5-data-type-conversions.asciidoc | 3 ++
.../ap/internal/conversion/Conversions.java | 2 +
.../conversion/LocaleToStringConversion.java | 37 +++++++++++++++
.../locale/LocaleConversionTest.java | 46 +++++++++++++++++++
.../test/conversion/locale/LocaleMapper.java | 19 ++++++++
.../test/conversion/locale/LocaleSource.java | 23 ++++++++++
.../test/conversion/locale/LocaleTarget.java | 21 +++++++++
7 files changed, 151 insertions(+)
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.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 258c7085f2..30430fe97e 100644
--- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
@@ -130,6 +130,9 @@ public interface CarMapper {
* Between `java.net.URL` and `String`.
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown.
+* Between `java.util.Locale` and `String`.
+** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information.
+
[[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 9a4085df83..6acb69492a 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
@@ -21,6 +21,7 @@
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -198,6 +199,7 @@ public Conversions(TypeFactory typeFactory) {
register( Currency.class, String.class, new CurrencyToStringConversion() );
register( UUID.class, String.class, new UUIDToStringConversion() );
+ register( Locale.class, String.class, new LocaleToStringConversion() );
registerURLConversion();
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java
new file mode 100644
index 0000000000..05a6da0b19
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.internal.conversion;
+
+import java.util.Locale;
+import java.util.Set;
+
+import org.mapstruct.ap.internal.model.common.ConversionContext;
+import org.mapstruct.ap.internal.model.common.Type;
+import org.mapstruct.ap.internal.util.Collections;
+
+import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale;
+
+/**
+ * Conversion between {@link java.util.Locale} and {@link String}.
+ *
+ * @author Jason Bodnar
+ */
+public class LocaleToStringConversion extends SimpleConversion {
+ @Override
+ protected String getToExpression(ConversionContext conversionContext) {
+ return ".toLanguageTag()";
+ }
+
+ @Override
+ protected String getFromExpression(ConversionContext conversionContext) {
+ return locale( conversionContext ) + ".forLanguageTag( )";
+ }
+
+ @Override
+ protected Set getFromConversionImportTypes(final ConversionContext conversionContext) {
+ return Collections.asSet( conversionContext.getTypeFactory().getType( Locale.class ) );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java
new file mode 100644
index 0000000000..cba7785512
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conversion.locale;
+
+import java.util.Locale;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests conversions between {@link Locale} and String.
+ *
+ * @author Jason Bodnar
+ */
+@IssueKey("3172")
+@WithClasses({ LocaleSource.class, LocaleTarget.class, LocaleMapper.class })
+public class LocaleConversionTest {
+
+ @ProcessorTest
+ public void shouldApplyLocaleConversion() {
+ LocaleSource source = new LocaleSource();
+ source.setLocaleA( Locale.getDefault() );
+
+ LocaleTarget target = LocaleMapper.INSTANCE.sourceToTarget( source );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.getLocaleA() ).isEqualTo( source.getLocaleA().toLanguageTag() );
+ }
+
+ @ProcessorTest
+ public void shouldApplyReverseLocaleConversion() {
+ LocaleTarget target = new LocaleTarget();
+ target.setLocaleA( Locale.getDefault().toLanguageTag() );
+
+ LocaleSource source = LocaleMapper.INSTANCE.targetToSource( target );
+
+ assertThat( source ).isNotNull();
+ assertThat( source.getLocaleA() ).isEqualTo( Locale.forLanguageTag( target.getLocaleA() ) );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java
new file mode 100644
index 0000000000..3da3fc4ae4
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conversion.locale;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface LocaleMapper {
+
+ LocaleMapper INSTANCE = Mappers.getMapper( LocaleMapper.class );
+
+ LocaleTarget sourceToTarget(LocaleSource source);
+
+ LocaleSource targetToSource(LocaleTarget target);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java
new file mode 100644
index 0000000000..69ea5bda2d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conversion.locale;
+
+import java.util.Locale;
+
+/**
+ * @author Jason Bodnar
+ */
+public class LocaleSource {
+ private Locale localeA;
+
+ public Locale getLocaleA() {
+ return localeA;
+ }
+
+ public void setLocaleA(Locale localeA) {
+ this.localeA = localeA;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java
new file mode 100644
index 0000000000..000ff86b05
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conversion.locale;
+
+/**
+ * @author Jason Bodnar
+ */
+public class LocaleTarget {
+ private String localeA;
+
+ public String getLocaleA() {
+ return localeA;
+ }
+
+ public void setLocaleA(String localeA) {
+ this.localeA = localeA;
+ }
+}
From a89c34f00c58e4b74bb81a102f7e615405c38692 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Mon, 1 May 2023 09:44:05 +0200
Subject: [PATCH 080/320] #3238 Compile error instead of null pointer exception
for invalid ignore with target this
---
.../internal/model/source/MappingOptions.java | 3 ++
.../mapstruct/ap/internal/util/Message.java | 1 +
.../bugs/_3238/ErroneousIssue3238Mapper.java | 42 +++++++++++++++++++
.../ap/test/bugs/_3238/Issue3238Test.java | 37 ++++++++++++++++
4 files changed, 83 insertions(+)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.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 5046ce8b29..2af1c95f71 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
@@ -254,6 +254,9 @@ else if ( gem.nullValuePropertyMappingStrategy().hasValue()
&& gem.ignore().hasValue() && gem.ignore().getValue() ) {
message = Message.PROPERTYMAPPING_IGNORE_AND_NVPMS;
}
+ else if ( ".".equals( gem.target().get() ) && gem.ignore().hasValue() && gem.ignore().getValue() ) {
+ message = Message.PROPERTYMAPPING_TARGET_THIS_AND_IGNORE;
+ }
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 6f72f720f1..249825ec50 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
@@ -68,6 +68,7 @@ public enum Message {
PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS( "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a constant or an nullValuePropertyMappingStrategy." ),
PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ),
PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ),
+ PROPERTYMAPPING_TARGET_THIS_AND_IGNORE( "Using @Mapping( target = \".\", ignore = true ) is not allowed. You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore all non explicitly mapped target properties." ),
PROPERTYMAPPING_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/_3238/ErroneousIssue3238Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java
new file mode 100644
index 0000000000..fd0d4672a6
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3238;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper
+public interface ErroneousIssue3238Mapper {
+
+ @Mapping(target = ".", ignore = true)
+ Target map(Source source);
+
+ class Target {
+
+ private final String value;
+
+ public Target(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ class Source {
+
+ private final String value;
+
+ public Source(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java
new file mode 100644
index 0000000000..6648495a70
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3238;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses(ErroneousIssue3238Mapper.class)
+@IssueKey("3238")
+class Issue3238Test {
+
+ @ProcessorTest
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = @Diagnostic( type = ErroneousIssue3238Mapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 14,
+ message = "Using @Mapping( target = \".\", ignore = true ) is not allowed." +
+ " You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore" +
+ " all non explicitly mapped target properties."
+ )
+ )
+ void shouldGenerateValidCompileError() {
+
+ }
+
+}
From efaa67aadf6f24c8f12804a1f18c1218d929d56f Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Mon, 1 May 2023 10:18:23 +0200
Subject: [PATCH 081/320] #3104 Update methods with
NullValuePropertyMappingStrategy.IGNORE should use
SetterWrapperForCollectionsAndMapsWithNullCheck
---
.../model/CollectionAssignmentBuilder.java | 11 +++
.../ap/test/bugs/_3104/Issue3104Mapper.java | 77 +++++++++++++++++++
.../ap/test/bugs/_3104/Issue3104Test.java | 38 +++++++++
3 files changed, 126 insertions(+)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.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 e3bc85342a..9a0a025845 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
@@ -31,6 +31,7 @@
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS;
+import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE;
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT;
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL;
@@ -177,6 +178,16 @@ else if ( method.isUpdateMethod() && !targetImmutable ) {
targetAccessorType.isFieldAssignment()
);
}
+ else if ( method.isUpdateMethod() && nvpms == IGNORE ) {
+
+ result = new SetterWrapperForCollectionsAndMapsWithNullCheck(
+ result,
+ method.getThrownTypes(),
+ targetType,
+ ctx.getTypeFactory(),
+ targetAccessorType.isFieldAssignment()
+ );
+ }
else if ( setterWrapperNeedsSourceNullCheck( result )
&& canBeMappedOrDirectlyAssigned( result ) ) {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java
new file mode 100644
index 0000000000..e561f40bab
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3104;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
+public interface Issue3104Mapper {
+
+ Issue3104Mapper INSTANCE = Mappers.getMapper( Issue3104Mapper.class );
+
+ @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+ void update(@MappingTarget Target target, Source source);
+
+ class Target {
+ private List children = Collections.emptyList();
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void setChildren(List children) {
+ if ( children == null ) {
+ throw new IllegalArgumentException( "children is null" );
+ }
+ this.children = Collections.unmodifiableList( children );
+ }
+ }
+
+ class Child {
+ private String myField;
+
+ public String getMyField() {
+ return myField;
+ }
+
+ public void setMyField(String myField) {
+ this.myField = myField;
+ }
+ }
+
+ class Source {
+ private final List children;
+
+ public Source(List children) {
+ this.children = children;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ }
+
+ class ChildSource {
+ private final String myField;
+
+ public ChildSource(String myField) {
+ this.myField = myField;
+ }
+
+ public String getMyField() {
+ return myField;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java
new file mode 100644
index 0000000000..5f3a9e160b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3104;
+
+import java.util.Collections;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("3104")
+@WithClasses(Issue3104Mapper.class)
+class Issue3104Test {
+
+ @ProcessorTest
+ void shouldCorrectlyMapUpdateMappingWithTargetImmutableCollectionStrategy() {
+ Issue3104Mapper.Target target = new Issue3104Mapper.Target();
+ Issue3104Mapper.INSTANCE.update( target, new Issue3104Mapper.Source( null ) );
+
+ assertThat( target.getChildren() ).isEmpty();
+
+ Issue3104Mapper.INSTANCE.update(
+ target,
+ new Issue3104Mapper.Source( Collections.singletonList( new Issue3104Mapper.ChildSource( "tester" ) ) )
+ );
+ assertThat( target.getChildren() )
+ .extracting( Issue3104Mapper.Child::getMyField )
+ .containsExactly( "tester" );
+ }
+}
From 7c90592d051c828cc20af0818f899c6fc5adc0e4 Mon Sep 17 00:00:00 2001
From: paparadva
Date: Sun, 30 Oct 2022 13:11:26 +0300
Subject: [PATCH 082/320] #2863 Add validation of String type to
@TargetPropertyName
---
.../processor/MethodRetrievalProcessor.java | 15 ++++++++++--
.../mapstruct/ap/internal/util/Message.java | 1 +
...sNonStringTargetPropertyNameParameter.java | 21 +++++++++++++++++
.../TargetPropertyNameTest.java | 23 +++++++++++++++++++
4 files changed, 58 insertions(+), 2 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.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 4ad531d334..6f1febae9d 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
@@ -28,6 +28,7 @@
import org.mapstruct.ap.internal.gem.ObjectFactoryGem;
import org.mapstruct.ap.internal.gem.SubclassMappingGem;
import org.mapstruct.ap.internal.gem.SubclassMappingsGem;
+import org.mapstruct.ap.internal.gem.TargetPropertyNameGem;
import org.mapstruct.ap.internal.gem.ValueMappingGem;
import org.mapstruct.ap.internal.gem.ValueMappingsGem;
import org.mapstruct.ap.internal.model.common.Parameter;
@@ -230,7 +231,7 @@ private SourceMethod getMethod(TypeElement usedMapper,
// otherwise add reference to existing mapper method
else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, parameters, returnType )
|| isValidLifecycleCallbackMethod( method )
- || isValidPresenceCheckMethod( method, returnType ) ) {
+ || isValidPresenceCheckMethod( method, parameters, returnType ) ) {
return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters );
}
else {
@@ -407,7 +408,17 @@ private boolean hasFactoryAnnotation(ExecutableElement method) {
return ObjectFactoryGem.instanceOn( method ) != null;
}
- private boolean isValidPresenceCheckMethod(ExecutableElement method, Type returnType) {
+ private boolean isValidPresenceCheckMethod(ExecutableElement method, List parameters, Type returnType) {
+ for ( Parameter param : parameters ) {
+ if ( param.isTargetPropertyName() && !param.getType().isString() ) {
+ messager.printMessage(
+ param.getElement(),
+ TargetPropertyNameGem.instanceOn( param.getElement() ).mirror(),
+ Message.RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE
+ );
+ return false;
+ }
+ }
return isBoolean( returnType ) && hasConditionAnnotation( method );
}
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 249825ec50..a24a43036c 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
@@ -176,6 +176,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_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ),
INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ),
INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ),
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/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java
new file mode 100644
index 0000000000..ec545a058b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+
+@Mapper
+public interface ErroneousNonStringTargetPropertyNameParameter {
+
+ Employee map(EmployeeDto employee);
+
+ @Condition
+ default boolean isNotBlank(String value, @TargetPropertyName int propName) {
+ return value != null && !value.trim().isEmpty();
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java
index 3d96e66664..91e4b77de2 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.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 java.util.Collections;
@@ -278,4 +281,24 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() {
"addresses.street"
);
}
+
+ @IssueKey("2863")
+ @ProcessorTest
+ @WithClasses({
+ ErroneousNonStringTargetPropertyNameParameter.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousNonStringTargetPropertyNameParameter.class,
+ line = 18,
+ message = "@TargetPropertyName can only by applied to a String parameter."
+ )
+ }
+ )
+ public void nonStringTargetPropertyNameParameter() {
+
+ }
}
From 6d205e5bc46365444774609e860f60819818ef4b Mon Sep 17 00:00:00 2001
From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com>
Date: Sun, 21 May 2023 22:49:41 +0200
Subject: [PATCH 083/320] #1454 Support for lifecycle methods on type being
built with builders
Add missing support for lifecycle methods with builders:
* `@BeforeMapping` with `@TargetType` the type being build
* `@AftereMapping` with `@TargetType` the type being build
* `@AfterMapping` with `@MappingTarget` the type being build
---
.../resources/build-config/checkstyle.xml | 4 +-
.../chapter-12-customizing-mapping.asciidoc | 14 ++--
.../chapter-13-using-mapstruct-spi.asciidoc | 2 +-
.../ap/internal/model/BeanMappingMethod.java | 72 ++++++++++++++++++-
.../ap/internal/model/MappingMethod.java | 4 +-
.../ap/internal/model/BeanMappingMethod.ftl | 21 +++++-
.../BuilderLifecycleCallbacksTest.java | 6 +-
.../builder/lifecycle/MappingContext.java | 10 ++-
8 files changed, 119 insertions(+), 14 deletions(-)
diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml
index a4591c39de..a1ff4af23a 100644
--- a/build-config/src/main/resources/build-config/checkstyle.xml
+++ b/build-config/src/main/resources/build-config/checkstyle.xml
@@ -29,7 +29,9 @@
-
+
+
+
diff --git a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
index 0c873eac09..dc07b30e62 100644
--- a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
@@ -248,9 +248,8 @@ All before/after-mapping methods that *can* be applied to a mapping method *will
The order of the method invocation is determined primarily by their variant:
-1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source
-parameters and constructing a new target bean.
-2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean.
+1. `@BeforeMapping` methods without parameters, a `@MappingTarget` parameter or a `@TargetType` parameter are called before any null-checks on source parameters and constructing a new target bean.
+2. `@BeforeMapping` methods with a `@MappingTarget` parameter are called after constructing a new target bean.
3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement.
Within those groups, the method invocations are ordered by their location of definition:
@@ -262,4 +261,11 @@ Within those groups, the method invocations are ordered by their location of def
*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation.
-*Important:* when using a builder, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter so that the method is able to modify the object going to be build. The `build` method is called when the `@AfterMapping` annotated method scope finishes. MapStruct will not call the `@AfterMapping` annotated method if the real target is used as `@MappingTarget` annotated parameter.
\ No newline at end of file
+[NOTE]
+====
+Before/After-mapping methods can also be used with builders:
+
+* `@BeforeMapping` methods with a `@MappingTarget` parameter of the real target will not be invoked because it is only available after the mapping was already performed.
+* To be able to modify the object that is going to be built, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter. The `build` method is called when the `@AfterMapping` annotated method scope finishes.
+* The `@AfterMapping` annotated method can also have the real target as `@TargetType` or `@MappingTarget`. It will be invoked after the real target was built (first the methods annotated with `@TargetType`, then the methods annotated with `@MappingTarget`)
+====
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
index ce9b288428..51b2bbff3c 100644
--- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
@@ -71,7 +71,7 @@ public class GolfPlayerDto {
public GolfPlayerDto withName(String name) {
this.name = name;
- return this
+ return this;
}
}
----
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 cf3e23db9e..b6afce74d2 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
@@ -94,6 +94,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Type returnTypeToConstruct;
private final BuilderType returnTypeBuilder;
private final MethodReference finalizerMethod;
+ private final String finalizedResultName;
+ private final List beforeMappingReferencesWithFinalizedReturnType;
+ private final List afterMappingReferencesWithFinalizedReturnType;
private final MappingReferences mappingReferences;
@@ -368,8 +371,35 @@ else if ( !method.isUpdateMethod() ) {
MethodReference finalizeMethod = null;
+ List beforeMappingReferencesWithFinalizedReturnType = new ArrayList<>();
+ List afterMappingReferencesWithFinalizedReturnType = new ArrayList<>();
if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
finalizeMethod = getFinalizerMethod();
+
+ Type actualReturnType = method.getReturnType();
+
+ beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget(
+ LifecycleMethodResolver.beforeMappingMethods(
+ method,
+ actualReturnType,
+ selectionParameters,
+ ctx,
+ existingVariableNames
+ ),
+ false
+ ) );
+
+ afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods(
+ method,
+ actualReturnType,
+ selectionParameters,
+ ctx,
+ existingVariableNames
+ ) );
+
+ // remove methods without parameters as they are already being invoked
+ removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType );
+ removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
}
return new BeanMappingMethod(
@@ -383,12 +413,18 @@ else if ( !method.isUpdateMethod() ) {
returnTypeBuilder,
beforeMappingMethods,
afterMappingMethods,
+ beforeMappingReferencesWithFinalizedReturnType,
+ afterMappingReferencesWithFinalizedReturnType,
finalizeMethod,
mappingReferences,
subclasses
);
}
+ private void removeMappingReferencesWithoutSourceParameters(List references) {
+ references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() );
+ }
+
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
return !isAbstractReturnTypeAllowed()
&& canReturnTypeBeConstructed( returnTypeImpl );
@@ -706,7 +742,6 @@ private boolean isReturnTypeAbstractOrCanBeConstructed(Type returnType) {
* Find a factory method for a return type or for a builder.
* @param returnTypeImpl the return type implementation to construct
* @param @selectionParameters
- * @return
*/
private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
List> matchingFactoryMethods =
@@ -1380,7 +1415,7 @@ else if ( mapping.getJavaExpression() != null ) {
*
* When a target property matches its name with the (nested) source property, it is added to the list if and
* only if it is an unprocessed target property.
- *
+ *
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
*/
private void applyTargetThisMapping() {
@@ -1766,6 +1801,8 @@ private BeanMappingMethod(Method method,
BuilderType returnTypeBuilder,
List beforeMappingReferences,
List afterMappingReferences,
+ List beforeMappingReferencesWithFinalizedReturnType,
+ List afterMappingReferencesWithFinalizedReturnType,
MethodReference finalizerMethod,
MappingReferences mappingReferences,
List subclassMappings) {
@@ -1783,9 +1820,20 @@ private BeanMappingMethod(Method method,
this.propertyMappings = propertyMappings;
this.returnTypeBuilder = returnTypeBuilder;
this.finalizerMethod = finalizerMethod;
+ if ( this.finalizerMethod != null ) {
+ this.finalizedResultName =
+ Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames );
+ existingVariableNames.add( this.finalizedResultName );
+ }
+ else {
+ this.finalizedResultName = null;
+ }
this.mappingReferences = mappingReferences;
- // intialize constant mappings as all mappings, but take out the ones that can be contributed to a
+ this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType;
+ this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType;
+
+ // initialize constant mappings as all mappings, but take out the ones that can be contributed to a
// parameter mapping.
this.mappingsByParameter = new HashMap<>();
this.constantMappings = new ArrayList<>( propertyMappings.size() );
@@ -1830,6 +1878,18 @@ public List getSubclassMappings() {
return subclassMappings;
}
+ public String getFinalizedResultName() {
+ return finalizedResultName;
+ }
+
+ public List getBeforeMappingReferencesWithFinalizedReturnType() {
+ return beforeMappingReferencesWithFinalizedReturnType;
+ }
+
+ public List getAfterMappingReferencesWithFinalizedReturnType() {
+ return afterMappingReferencesWithFinalizedReturnType;
+ }
+
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() );
@@ -1882,6 +1942,12 @@ public Set getImportTypes() {
if ( returnTypeBuilder != null ) {
types.add( returnTypeBuilder.getOwningType() );
}
+ for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithFinalizedReturnType ) {
+ types.addAll( reference.getImportTypes() );
+ }
+ for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithFinalizedReturnType ) {
+ types.addAll( reference.getImportTypes() );
+ }
return types;
}
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 5b9eda644b..4c59216e09 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
@@ -186,8 +186,8 @@ public String toString() {
return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")";
}
- private List filterMappingTarget(List methods,
- boolean mustHaveMappingTargetParameter) {
+ protected static List filterMappingTarget(
+ List methods, boolean mustHaveMappingTargetParameter) {
if ( methods == null ) {
return Collections.emptyList();
}
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 1b402a57f0..a0fbe24b3e 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,6 +21,12 @@
#if>
#list>
+ <#list beforeMappingReferencesWithFinalizedReturnType as callback>
+ <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/>
+ <#if !callback_has_next>
+
+ #if>
+ #list>
<#if !mapNullToDefault>
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && #if>#list> ) {
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod />#if><#else>null#if>#if>;
@@ -129,7 +135,20 @@
<#if returnType.name != "void">
<#if finalizerMethod??>
- return ${resultName}.<@includeModel object=finalizerMethod />;
+ <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)>
+ ${returnType.name} ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />;
+
+ <#list afterMappingReferencesWithFinalizedReturnType as callback>
+ <#if callback_index = 0>
+
+ #if>
+ <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/>
+ #list>
+
+ return ${finalizedResultName};
+ <#else>
+ return ${resultName}.<@includeModel object=finalizerMethod />;
+ #if>
<#else>
return ${resultName};
#if>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java
index e3892f2154..3d5fb7e533 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java
@@ -43,12 +43,16 @@ public void lifecycleMethodsShouldBeInvoked() {
assertThat( context.getInvokedMethods() )
.contains(
"beforeWithoutParameters",
+ "beforeWithTargetType",
"beforeWithBuilderTargetType",
"beforeWithBuilderTarget",
"afterWithoutParameters",
"afterWithBuilderTargetType",
"afterWithBuilderTarget",
- "afterWithBuilderTargetReturningTarget"
+ "afterWithBuilderTargetReturningTarget",
+ "afterWithTargetType",
+ "afterWithTarget",
+ "afterWithTargetReturningTarget"
);
}
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java
index 96b9b30db6..079be90a81 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java
@@ -74,7 +74,15 @@ public void afterWithBuilderTarget(OrderDto source, @MappingTarget Order.Builder
public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) {
invokedMethods.add( "afterWithBuilderTargetReturningTarget" );
- return orderBuilder.create();
+ // return null, so that @AfterMapping methods on the finalized object will be called in the tests
+ return null;
+ }
+
+ @AfterMapping
+ public Order afterWithTargetReturningTarget(@MappingTarget Order order) {
+ invokedMethods.add( "afterWithTargetReturningTarget" );
+
+ return order;
}
public List getInvokedMethods() {
From 84c443df9c141d9947253a19facbb952cbda816e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?=
Date: Wed, 24 May 2023 05:44:36 +0200
Subject: [PATCH 084/320] #3245 Remove redundant null checks in nested
properties
---
.../model/NestedPropertyMappingMethod.ftl | 9 ++--
.../test/bugs/_1561/Issue1561MapperImpl.java | 9 +---
.../ap/test/bugs/_1685/UserMapperImpl.java | 45 +++----------------
.../ap/test/bugs/_2245/TestMapperImpl.java | 9 +---
.../nestedsource/ArtistToChartEntryImpl.java | 27 ++---------
.../nestedtarget/ChartEntryToArtistImpl.java | 45 +++----------------
.../nestedbeans/mixed/FishTankMapperImpl.java | 27 ++---------
.../ArtistToChartEntryImpl.java | 27 ++---------
.../ChartEntryToArtistImpl.java | 45 +++----------------
9 files changed, 30 insertions(+), 213 deletions(-)
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 ce6dab0714..8eabe23a16 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl
@@ -7,15 +7,16 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.NestedPropertyMappingMethod" -->
<#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, #if>#list>)<@throws/> {
- if ( ${sourceParameter.name} == null ) {
- return ${returnType.null};
- }
<#list propertyEntries as entry>
<#if entry.presenceChecker?? >
if ( <#if entry_index != 0>${entry.previousPropertyName} == null || #if>!<@includeModel object=entry.presenceChecker /> ) {
return ${returnType.null};
}
#if>
+ <#if !entry_has_next>
+ return ${entry.previousPropertyName}.${entry.accessorName};
+ #if>
+ <#if entry_has_next>
<@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName};
<#if !entry.presenceChecker?? >
<#if !entry.type.primitive>
@@ -24,8 +25,6 @@
}
#if>
#if>
- <#if !entry_has_next>
- return ${entry.name};
#if>
#list>
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java
index dde54d65b7..83cebe2bde 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java
@@ -61,17 +61,10 @@ protected NestedTarget sourceToNestedTarget(Source source) {
}
private Stream targetNestedTargetProperties(Target target) {
- if ( target == null ) {
- return null;
- }
NestedTarget nestedTarget = target.getNestedTarget();
if ( nestedTarget == null ) {
return null;
}
- Stream properties = nestedTarget.getProperties();
- if ( properties == null ) {
- return null;
- }
- return properties;
+ return nestedTarget.getProperties();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java
index b8d3e9db4b..3c019a0dd8 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java
@@ -168,77 +168,42 @@ protected ContactDataDTO userToContactDataDTO(User user) {
}
private String userDTOContactDataDTOEmail(UserDTO userDTO) {
- if ( userDTO == null ) {
- return null;
- }
ContactDataDTO contactDataDTO = userDTO.getContactDataDTO();
if ( contactDataDTO == null ) {
return null;
}
- String email = contactDataDTO.getEmail();
- if ( email == null ) {
- return null;
- }
- return email;
+ return contactDataDTO.getEmail();
}
private String userDTOContactDataDTOPhone(UserDTO userDTO) {
- if ( userDTO == null ) {
- return null;
- }
ContactDataDTO contactDataDTO = userDTO.getContactDataDTO();
if ( contactDataDTO == null ) {
return null;
}
- String phone = contactDataDTO.getPhone();
- if ( phone == null ) {
- return null;
- }
- return phone;
+ return contactDataDTO.getPhone();
}
private String userDTOContactDataDTOAddress(UserDTO userDTO) {
- if ( userDTO == null ) {
- return null;
- }
ContactDataDTO contactDataDTO = userDTO.getContactDataDTO();
if ( contactDataDTO == null ) {
return null;
}
- String address = contactDataDTO.getAddress();
- if ( address == null ) {
- return null;
- }
- return address;
+ return contactDataDTO.getAddress();
}
private List userDTOContactDataDTOPreferences(UserDTO userDTO) {
- if ( userDTO == null ) {
- return null;
- }
ContactDataDTO contactDataDTO = userDTO.getContactDataDTO();
if ( contactDataDTO == null ) {
return null;
}
- List preferences = contactDataDTO.getPreferences();
- if ( preferences == null ) {
- return null;
- }
- return preferences;
+ return contactDataDTO.getPreferences();
}
private String[] userDTOContactDataDTOSettings(UserDTO userDTO) {
- if ( userDTO == null ) {
- return null;
- }
ContactDataDTO contactDataDTO = userDTO.getContactDataDTO();
if ( contactDataDTO == null ) {
return null;
}
- String[] settings = contactDataDTO.getSettings();
- if ( settings == null ) {
- return null;
- }
- return settings;
+ return contactDataDTO.getSettings();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java
index c3ecc02911..b7a18048a0 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java
@@ -34,17 +34,10 @@ public Tenant map(TenantDTO tenant) {
}
private String tenantInnerId(TenantDTO tenantDTO) {
- if ( tenantDTO == null ) {
- return null;
- }
Inner inner = tenantDTO.getInner();
if ( inner == null ) {
return null;
}
- String id = inner.getId();
- if ( id == null ) {
- return null;
- }
- return id;
+ return inner.getId();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java
index 09f96b4b3a..40c16c6086 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java
@@ -96,24 +96,14 @@ public ChartEntry map(Chart name) {
}
private String songArtistName(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
}
- String name = artist.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return artist.getName();
}
private String songArtistLabelStudioName(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
@@ -126,17 +116,10 @@ private String songArtistLabelStudioName(Song song) {
if ( studio == null ) {
return null;
}
- String name = studio.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return studio.getName();
}
private String songArtistLabelStudioCity(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
@@ -149,10 +132,6 @@ private String songArtistLabelStudioCity(Song song) {
if ( studio == null ) {
return null;
}
- String city = studio.getCity();
- if ( city == null ) {
- return null;
- }
- return city;
+ return studio.getCity();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java
index 0413ef5b79..cbd125366e 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java
@@ -132,24 +132,14 @@ protected Song chartEntryToSong(ChartEntry chartEntry) {
}
private String chartSongTitle(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
}
- String title = song.getTitle();
- if ( title == null ) {
- return null;
- }
- return title;
+ return song.getTitle();
}
private String chartSongArtistName(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -158,17 +148,10 @@ private String chartSongArtistName(Chart chart) {
if ( artist == null ) {
return null;
}
- String name = artist.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return artist.getName();
}
private String chartSongArtistLabelStudioName(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -185,17 +168,10 @@ private String chartSongArtistLabelStudioName(Chart chart) {
if ( studio == null ) {
return null;
}
- String name = studio.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return studio.getName();
}
private String chartSongArtistLabelStudioCity(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -212,25 +188,14 @@ private String chartSongArtistLabelStudioCity(Chart chart) {
if ( studio == null ) {
return null;
}
- String city = studio.getCity();
- if ( city == null ) {
- return null;
- }
- return city;
+ return studio.getCity();
}
private List chartSongPositions(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
}
- List positions = song.getPositions();
- if ( positions == null ) {
- return null;
- }
- return positions;
+ return song.getPositions();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
index 3642d15340..dc70a28ce8 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
@@ -159,18 +159,11 @@ protected WaterQualityDto waterQualityToWaterQualityDto(WaterQuality waterQualit
}
private Ornament sourceInteriorOrnament(FishTank fishTank) {
- if ( fishTank == null ) {
- return null;
- }
Interior interior = fishTank.getInterior();
if ( interior == null ) {
return null;
}
- Ornament ornament = interior.getOrnament();
- if ( ornament == null ) {
- return null;
- }
- return ornament;
+ return interior.getOrnament();
}
protected OrnamentDto ornamentToOrnamentDto(Ornament ornament) {
@@ -295,18 +288,11 @@ protected Interior fishTankDtoToInterior(FishTankDto fishTankDto) {
}
private String waterQualityReportDtoOrganisationName(WaterQualityReportDto waterQualityReportDto) {
- if ( waterQualityReportDto == null ) {
- return null;
- }
WaterQualityOrganisationDto organisation = waterQualityReportDto.getOrganisation();
if ( organisation == null ) {
return null;
}
- String name = organisation.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return organisation.getName();
}
protected WaterQualityReport waterQualityReportDtoToWaterQualityReport(WaterQualityReportDto waterQualityReportDto) {
@@ -335,18 +321,11 @@ protected WaterQuality waterQualityDtoToWaterQuality(WaterQualityDto waterQualit
}
private MaterialTypeDto sourceMaterialMaterialType(FishTankDto fishTankDto) {
- if ( fishTankDto == null ) {
- return null;
- }
MaterialDto material = fishTankDto.getMaterial();
if ( material == null ) {
return null;
}
- MaterialTypeDto materialType = material.getMaterialType();
- if ( materialType == null ) {
- return null;
- }
- return materialType;
+ return material.getMaterialType();
}
protected MaterialType materialTypeDtoToMaterialType(MaterialTypeDto materialTypeDto) {
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java
index 131566ad4f..e43534dc84 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java
@@ -74,24 +74,14 @@ public ChartEntry map(Chart name) {
}
private String songArtistName(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
}
- String name = artist.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return artist.getName();
}
private String songArtistLabelStudioName(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
@@ -104,17 +94,10 @@ private String songArtistLabelStudioName(Song song) {
if ( studio == null ) {
return null;
}
- String name = studio.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return studio.getName();
}
private String songArtistLabelStudioCity(Song song) {
- if ( song == null ) {
- return null;
- }
Artist artist = song.getArtist();
if ( artist == null ) {
return null;
@@ -127,10 +110,6 @@ private String songArtistLabelStudioCity(Song song) {
if ( studio == null ) {
return null;
}
- String city = studio.getCity();
- if ( city == null ) {
- return null;
- }
- return city;
+ return studio.getCity();
}
}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java
index 8877bd421f..725f5b8efe 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java
@@ -198,24 +198,14 @@ protected void chartEntryToSong2(ChartEntry chartEntry, Song mappingTarget) {
}
private String chartSongTitle(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
}
- String title = song.getTitle();
- if ( title == null ) {
- return null;
- }
- return title;
+ return song.getTitle();
}
private String chartSongArtistName(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -224,17 +214,10 @@ private String chartSongArtistName(Chart chart) {
if ( artist == null ) {
return null;
}
- String name = artist.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return artist.getName();
}
private String chartSongArtistLabelStudioName(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -251,17 +234,10 @@ private String chartSongArtistLabelStudioName(Chart chart) {
if ( studio == null ) {
return null;
}
- String name = studio.getName();
- if ( name == null ) {
- return null;
- }
- return name;
+ return studio.getName();
}
private String chartSongArtistLabelStudioCity(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
@@ -278,25 +254,14 @@ private String chartSongArtistLabelStudioCity(Chart chart) {
if ( studio == null ) {
return null;
}
- String city = studio.getCity();
- if ( city == null ) {
- return null;
- }
- return city;
+ return studio.getCity();
}
private List chartSongPositions(Chart chart) {
- if ( chart == null ) {
- return null;
- }
Song song = chart.getSong();
if ( song == null ) {
return null;
}
- List positions = song.getPositions();
- if ( positions == null ) {
- return null;
- }
- return positions;
+ return song.getPositions();
}
}
From 51f4e7eba9c65d9078491711d616c7b5b38ecc1a Mon Sep 17 00:00:00 2001
From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com>
Date: Wed, 24 May 2023 06:04:13 +0200
Subject: [PATCH 085/320] #3231 Prefer record constructor annotated with
`@Default`
---
.../org/mapstruct/itest/records/Default.java | 21 ++++++++++++++++++
.../org/mapstruct/itest/records/Task.java | 15 +++++++++++++
.../org/mapstruct/itest/records/TaskDto.java | 20 +++++++++++++++++
.../mapstruct/itest/records/TaskMapper.java | 22 +++++++++++++++++++
.../mapstruct/itest/records/RecordsTest.java | 12 ++++++++++
.../ap/internal/model/BeanMappingMethod.java | 18 ++++++++++++++-
6 files changed, 107 insertions(+), 1 deletion(-)
create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java
create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java
create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java
create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java
new file mode 100644
index 0000000000..b845bdd730
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Documented
+@Target(ElementType.CONSTRUCTOR)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Default {
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java
new file mode 100644
index 0000000000..3f990fc89a
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Oliver Erhart
+ */
+public record Task( String id, Long number ) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java
new file mode 100644
index 0000000000..1ba6eb2be9
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Oliver Erhart
+ */
+public record TaskDto(String id, Long number) {
+
+ @Default
+ TaskDto(String id) {
+ this( id, 1L );
+ }
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java
new file mode 100644
index 0000000000..8d9da767cd
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Oliver Erhart
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface TaskMapper {
+
+ TaskMapper INSTANCE = Mappers.getMapper( TaskMapper.class );
+
+ TaskDto toRecord(Task source);
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java
index e98df8797e..2f77e8d49f 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
@@ -83,4 +83,16 @@ public void shouldMapIntoMemberRecord() {
assertThat( value.isActive() ).isEqualTo( false );
assertThat( value.premium() ).isEqualTo( true );
}
+
+ @Test
+ public void shouldUseDefaultConstructor() {
+ Task entity = new Task( "some-id", 1000L );
+
+ TaskDto value = TaskMapper.INSTANCE.toRecord( entity );
+
+ assertThat( value ).isNotNull();
+ assertThat( value.id() ).isEqualTo( "some-id" );
+ assertThat( value.number() ).isEqualTo( 1L );
+ }
+
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
index b6afce74d2..9d0acea91b 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
@@ -791,7 +791,23 @@ private ConstructorAccessor getConstructorAccessor(Type type) {
}
if ( type.isRecord() ) {
- // If the type is a record then just get the record components and use then
+
+ List constructors = ElementFilter.constructorsIn( type.getTypeElement()
+ .getEnclosedElements() );
+
+ for ( ExecutableElement constructor : constructors ) {
+ if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) {
+ continue;
+ }
+
+ // prefer constructor annotated with @Default
+ if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) {
+ return getConstructorAccessor( type, constructor );
+ }
+ }
+
+
+ // Other than that, just get the record components and use them
List recordComponents = type.getRecordComponents();
List parameterBindings = new ArrayList<>( recordComponents.size() );
Map constructorAccessors = new LinkedHashMap<>();
From c2eed45df12802cacaec158420656431c061471c Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sun, 21 May 2023 23:01:58 +0200
Subject: [PATCH 086/320] #3126 Apply target this references in the
BeanMappingMethod
---
.../ap/internal/model/BeanMappingMethod.java | 14 ++-
.../model/beanmapping/MappingReferences.java | 30 +-----
.../ap/test/bugs/_3126/Issue3126Mapper.java | 92 +++++++++++++++++++
.../ap/test/bugs/_3126/Issue3126Test.java | 28 ++++++
.../nestedbeans/mixed/FishTankMapperImpl.java | 41 +--------
5 files changed, 137 insertions(+), 68 deletions(-)
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java
create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.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 9d0acea91b..361d04b390 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
@@ -117,6 +117,7 @@ public static class Builder extends AbstractMappingMethodBuilder> unprocessedDefinedTargets = new LinkedHashMap<>();
private MappingReferences mappingReferences;
+ private List targetThisReferences;
private MethodReference factoryMethod;
private boolean hasFactoryMethod;
@@ -1068,6 +1069,14 @@ private boolean handleDefinedMappings(Type resultTypeToMap) {
for ( MappingReference mapping : mappingReferences.getMappingReferences() ) {
if ( mapping.isValid() ) {
String target = mapping.getTargetReference().getShallowestPropertyName();
+ if ( target == null ) {
+ // When the shallowest property name is null then it is for @Mapping(target = ".")
+ if ( this.targetThisReferences == null ) {
+ this.targetThisReferences = new ArrayList<>();
+ }
+ this.targetThisReferences.add( mapping );
+ continue;
+ }
if ( !handledTargets.contains( target ) ) {
if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) {
errorOccurred = true;
@@ -1435,8 +1444,11 @@ else if ( mapping.getJavaExpression() != null ) {
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
*/
private void applyTargetThisMapping() {
+ if ( this.targetThisReferences == null ) {
+ return;
+ }
Set handledTargetProperties = new HashSet<>();
- for ( MappingReference targetThis : mappingReferences.getTargetThisReferences() ) {
+ for ( MappingReference targetThis : this.targetThisReferences ) {
// handle all prior unprocessed target properties, but let duplicates fall through
List sourceRefs = targetThis
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 569c75e0eb..ced945bb90 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
@@ -5,10 +5,8 @@
*/
package org.mapstruct.ap.internal.model.beanmapping;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
-import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -23,7 +21,6 @@ public class MappingReferences {
private static final MappingReferences EMPTY = new MappingReferences( Collections.emptySet(), false );
private final Set mappingReferences;
- private final List targetThisReferences;
private final boolean restrictToDefinedMappings;
private final boolean forForgedMethods;
@@ -38,7 +35,6 @@ public static MappingReferences forSourceMethod(SourceMethod sourceMethod,
TypeFactory typeFactory) {
Set references = new LinkedHashSet<>();
- List targetThisReferences = new ArrayList<>( );
for ( MappingOptions mapping : sourceMethod.getOptions().getMappings() ) {
@@ -61,30 +57,16 @@ public static MappingReferences forSourceMethod(SourceMethod sourceMethod,
// add when inverse is also valid
MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference );
if ( isValidWhenInversed( mappingReference ) ) {
- if ( ".".equals( mapping.getTargetName() ) ) {
- targetThisReferences.add( mappingReference );
- }
- else {
- references.add( mappingReference );
- }
+ references.add( mappingReference );
}
}
- return new MappingReferences( references, targetThisReferences, false );
- }
-
- public MappingReferences(Set mappingReferences, List targetThisReferences,
- boolean restrictToDefinedMappings) {
- this.mappingReferences = mappingReferences;
- this.restrictToDefinedMappings = restrictToDefinedMappings;
- this.forForgedMethods = restrictToDefinedMappings;
- this.targetThisReferences = targetThisReferences;
+ return new MappingReferences( references, false );
}
public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings) {
this.mappingReferences = mappingReferences;
this.restrictToDefinedMappings = restrictToDefinedMappings;
this.forForgedMethods = restrictToDefinedMappings;
- this.targetThisReferences = Collections.emptyList();
}
public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings,
@@ -92,7 +74,6 @@ public MappingReferences(Set mappingReferences, boolean restri
this.mappingReferences = mappingReferences;
this.restrictToDefinedMappings = restrictToDefinedMappings;
this.forForgedMethods = forForgedMethods;
- this.targetThisReferences = Collections.emptyList();
}
public Set getMappingReferences() {
@@ -136,10 +117,6 @@ public boolean hasNestedTargetReferences() {
return false;
}
- public List getTargetThisReferences() {
- return targetThisReferences;
- }
-
@Override
public boolean equals(Object o) {
if ( this == o ) {
@@ -160,9 +137,6 @@ public boolean equals(Object o) {
if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) {
return false;
}
- if ( Objects.equals( targetThisReferences, that.targetThisReferences ) ) {
- return false;
- }
return true;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java
new file mode 100644
index 0000000000..e680db9317
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package org.mapstruct.ap.test.bugs._3126;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.SubclassExhaustiveStrategy;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION)
+public interface Issue3126Mapper {
+
+ Issue3126Mapper INSTANCE = Mappers.getMapper( Issue3126Mapper.class );
+
+ @SubclassMapping(target = HomeAddressDto.class, source = HomeAddress.class)
+ @SubclassMapping(target = OfficeAddressDto.class, source = OfficeAddress.class)
+ @Mapping(target = ".", source = "auditable")
+ AddressDto map(Address address);
+
+ interface AddressDto {
+
+ }
+
+ class HomeAddressDto implements AddressDto {
+ private String createdBy;
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+ }
+
+ class OfficeAddressDto implements AddressDto {
+ private String createdBy;
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+ }
+
+ class HomeAddress extends Address {
+
+ public HomeAddress(Auditable auditable) {
+ super( auditable );
+ }
+ }
+
+ class OfficeAddress extends Address {
+
+ public OfficeAddress(Auditable auditable) {
+ super( auditable );
+ }
+ }
+
+ abstract class Address {
+
+ private final Auditable auditable;
+
+ protected Address(Auditable auditable) {
+ this.auditable = auditable;
+ }
+
+ public Auditable getAuditable() {
+ return auditable;
+ }
+ }
+
+ class Auditable {
+
+ private final String createdBy;
+
+ public Auditable(String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java
new file mode 100644
index 0000000000..35e3eb873c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._3126;
+
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@IssueKey("3126")
+@WithClasses(Issue3126Mapper.class)
+class Issue3126Test {
+
+ @ProcessorTest
+ void shouldCompile() {
+ Issue3126Mapper.Auditable auditable = new Issue3126Mapper.Auditable( "home-user" );
+ Issue3126Mapper.Address address = new Issue3126Mapper.HomeAddress( auditable );
+ Issue3126Mapper.AddressDto addressDto = Issue3126Mapper.INSTANCE.map( address );
+
+ assertThat( addressDto ).isInstanceOfSatisfying( Issue3126Mapper.HomeAddressDto.class, homeAddress -> {
+ assertThat( homeAddress.getCreatedBy() ).isEqualTo( "home-user" );
+ } );
+ }
+}
diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
index dc70a28ce8..b98223fa6f 100644
--- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java
@@ -57,9 +57,9 @@ public FishTankDto mapAsWell(FishTank source) {
FishTankDto fishTankDto = new FishTankDto();
- fishTankDto.setFish( fishToFishDto1( source.getFish() ) );
+ fishTankDto.setFish( fishToFishDto( source.getFish() ) );
fishTankDto.setMaterial( fishTankToMaterialDto1( source ) );
- fishTankDto.setQuality( waterQualityToWaterQualityDto1( source.getQuality() ) );
+ fishTankDto.setQuality( waterQualityToWaterQualityDto( source.getQuality() ) );
fishTankDto.setOrnament( ornamentToOrnamentDto( sourceInteriorOrnament( source ) ) );
fishTankDto.setPlant( waterPlantToWaterPlantDto( source.getPlant() ) );
fishTankDto.setName( source.getName() );
@@ -190,18 +190,6 @@ protected WaterPlantDto waterPlantToWaterPlantDto(WaterPlant waterPlant) {
return waterPlantDto;
}
- protected FishDto fishToFishDto1(Fish fish) {
- if ( fish == null ) {
- return null;
- }
-
- FishDto fishDto = new FishDto();
-
- fishDto.setKind( fish.getType() );
-
- return fishDto;
- }
-
protected MaterialDto fishTankToMaterialDto1(FishTank fishTank) {
if ( fishTank == null ) {
return null;
@@ -226,31 +214,6 @@ protected WaterQualityOrganisationDto waterQualityReportToWaterQualityOrganisati
return waterQualityOrganisationDto;
}
- protected WaterQualityReportDto waterQualityReportToWaterQualityReportDto1(WaterQualityReport waterQualityReport) {
- if ( waterQualityReport == null ) {
- return null;
- }
-
- WaterQualityReportDto waterQualityReportDto = new WaterQualityReportDto();
-
- waterQualityReportDto.setOrganisation( waterQualityReportToWaterQualityOrganisationDto1( waterQualityReport ) );
- waterQualityReportDto.setVerdict( waterQualityReport.getVerdict() );
-
- return waterQualityReportDto;
- }
-
- protected WaterQualityDto waterQualityToWaterQualityDto1(WaterQuality waterQuality) {
- if ( waterQuality == null ) {
- return null;
- }
-
- WaterQualityDto waterQualityDto = new WaterQualityDto();
-
- waterQualityDto.setReport( waterQualityReportToWaterQualityReportDto1( waterQuality.getReport() ) );
-
- return waterQualityDto;
- }
-
protected Fish fishDtoToFish(FishDto fishDto) {
if ( fishDto == null ) {
return null;
From 62d1bd3490974468494853a46ec79f6bd5904bdd Mon Sep 17 00:00:00 2001
From: Filip Hrisafov
Date: Sat, 27 May 2023 15:04:34 +0200
Subject: [PATCH 087/320] #3280 Refactor method selection and use a context to
be able to more easily access information
---
.../model/LifecycleMethodResolver.java | 12 +-
.../model/ObjectFactoryMethodResolver.java | 10 +-
.../model/PresenceCheckMethodResolver.java | 12 +-
.../selector/CreateOrUpdateSelector.java | 11 +-
.../selector/FactoryParameterSelector.java | 9 +-
.../source/selector/InheritanceSelector.java | 14 +-
.../source/selector/MethodFamilySelector.java | 10 +-
.../model/source/selector/MethodSelector.java | 15 +-
.../source/selector/MethodSelectors.java | 28 +-
.../MostSpecificResultTypeSelector.java | 8 +-
.../source/selector/QualifierSelector.java | 9 +-
.../source/selector/SelectionContext.java | 240 ++++++++++++++++++
.../source/selector/SourceRhsSelector.java | 8 +-
.../source/selector/TargetTypeSelector.java | 10 +-
.../model/source/selector/TypeSelector.java | 112 +-------
.../selector/XmlElementDeclSelector.java | 12 +-
.../creation/MappingResolverImpl.java | 10 +-
17 files changed, 302 insertions(+), 228 deletions(-)
create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
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 8b44dee254..b713fa5f0c 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,7 +6,6 @@
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.stream.Collectors;
@@ -19,7 +18,7 @@
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
-import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
+import org.mapstruct.ap.internal.model.source.selector.SelectionContext;
/**
* Factory for creating lists of appropriate {@link LifecycleCallbackMethodReference}s
@@ -134,15 +133,12 @@ private static List collectLifecycleCallbackMe
MappingBuilderContext ctx, Set existingVariableNames) {
MethodSelectors selectors =
- new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
+ new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() );
List> matchingMethods = selectors.getMatchingMethods(
- method,
callbackMethods,
- Collections.emptyList(),
- targetType,
- method.getResultType(),
- SelectionCriteria.forLifecycleMethods( selectionParameters ) );
+ SelectionContext.forLifecycleMethods( method, targetType, selectionParameters, ctx.getTypeFactory() )
+ );
return toLifecycleCallbackMethodRefs(
method,
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 aded4cbe5f..4cf653b46b 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
@@ -23,7 +23,7 @@
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
-import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
+import org.mapstruct.ap.internal.model.source.selector.SelectionContext;
import org.mapstruct.ap.internal.util.Message;
/**
@@ -126,15 +126,11 @@ public static List> getMatchingFactoryMethods( Meth
MappingBuilderContext ctx) {
MethodSelectors selectors =
- new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
+ new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() );
return selectors.getMatchingMethods(
- method,
getAllAvailableMethods( method, ctx.getSourceModel() ),
- java.util.Collections.emptyList(),
- alternativeTarget,
- alternativeTarget,
- SelectionCriteria.forFactoryMethods( selectionParameters )
+ SelectionContext.forFactoryMethods( method, alternativeTarget, selectionParameters, ctx.getTypeFactory() )
);
}
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 9d4910bdce..a5c873743d 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
@@ -6,20 +6,18 @@
package org.mapstruct.ap.internal.model;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
-import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
+import org.mapstruct.ap.internal.model.source.selector.SelectionContext;
import org.mapstruct.ap.internal.util.Message;
/**
@@ -60,18 +58,12 @@ private static SelectedMethod findMatchingPresenceCheckMethod(
MethodSelectors selectors = new MethodSelectors(
ctx.getTypeUtils(),
ctx.getElementUtils(),
- ctx.getTypeFactory(),
ctx.getMessager()
);
- Type booleanType = ctx.getTypeFactory().getType( Boolean.class );
List> matchingMethods = selectors.getMatchingMethods(
- method,
getAllAvailableMethods( method, ctx.getSourceModel() ),
- Collections.emptyList(),
- booleanType,
- booleanType,
- SelectionCriteria.forPresenceCheckMethods( selectionParameters )
+ SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() )
);
if ( matchingMethods.isEmpty() ) {
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 ed1c72ab2a..03a671de57 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java
@@ -8,7 +8,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -29,13 +28,9 @@
public class CreateOrUpdateSelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
-
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired()
|| criteria.isPresenceCheckRequired() ) {
return methods;
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java
index 41d37e8b69..50896195f1 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java
@@ -8,7 +8,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -21,11 +20,9 @@
public class FactoryParameterSelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType, Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) {
return methods;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java
index a624c1accb..297dcaa4a3 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java
@@ -22,18 +22,14 @@
public class InheritanceSelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType, Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
- if ( sourceTypes.size() != 1 ) {
+ Type sourceType = context.getSourceType();
+ if ( sourceType == null ) {
return methods;
}
- Type singleSourceType = first( sourceTypes );
-
List> candidatesWithBestMatchingSourceType = new ArrayList<>();
int bestMatchingSourceTypeDistance = Integer.MAX_VALUE;
@@ -41,7 +37,7 @@ public List> getMatchingMethods(Method mapp
for ( SelectedMethod method : methods ) {
Parameter singleSourceParam = first( method.getMethod().getSourceParameters() );
- int sourceTypeDistance = singleSourceType.distanceTo( singleSourceParam.getType() );
+ int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() );
bestMatchingSourceTypeDistance =
addToCandidateListIfMinimal(
candidatesWithBestMatchingSourceType,
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
index 37502ec56d..d81269421f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
@@ -8,7 +8,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -20,12 +19,9 @@
public class MethodFamilySelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
List> result = new ArrayList<>( methods.size() );
for ( SelectedMethod method : methods ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java
index 86d35bb3aa..375d5a9bf8 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java
@@ -7,7 +7,6 @@
import java.util.List;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -23,18 +22,10 @@ interface MethodSelector {
* Selects those methods which match the given types and other criteria
*
* @param either SourceMethod or BuiltInMethod
- * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out
* @param candidates list of available methods
- * @param sourceTypes parameter type(s) that should be matched
- * @param mappingTargetType mappingTargetType that should be matched
- * @param returnType return type that should be matched
- * @param criteria criteria used in the selection process
+ * @param context the context for the matching
* @return list of methods that passes the matching process
*/
- List> getMatchingMethods(Method mappingMethod,
- List> candidates,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria);
+ List> getMatchingMethods(List> candidates,
+ SelectionContext context);
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java
index 74fa1a33a3..c14729a90f 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
@@ -9,8 +9,6 @@
import java.util.Arrays;
import java.util.List;
-import org.mapstruct.ap.internal.model.common.Type;
-import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.FormattingMessager;
@@ -25,11 +23,11 @@ public class MethodSelectors {
private final List selectors;
- public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFactory,
+ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils,
FormattingMessager messager) {
selectors = Arrays.asList(
new MethodFamilySelector(),
- new TypeSelector( typeFactory, messager ),
+ new TypeSelector( messager ),
new QualifierSelector( typeUtils, elementUtils ),
new TargetTypeSelector( typeUtils ),
new JavaxXmlElementDeclSelector( typeUtils ),
@@ -46,20 +44,12 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFacto
* Selects those methods which match the given types and other criteria
*
* @param either SourceMethod or BuiltInMethod
- * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out
* @param methods list of available methods
- * @param sourceTypes parameter type(s) that should be matched
- * @param mappingTargetType the mapping target type that should be matched
- * @param returnType return type that should be matched
- * @param criteria criteria used in the selection process
+ * @param context the selection context that should be used in the matching process
* @return list of methods that passes the matching process
*/
- public List> getMatchingMethods(Method mappingMethod,
- List methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List methods,
+ SelectionContext context) {
List> candidates = new ArrayList<>( methods.size() );
for ( T method : methods ) {
@@ -67,13 +57,7 @@ public List> getMatchingMethods(Method mapp
}
for ( MethodSelector selector : selectors ) {
- candidates = selector.getMatchingMethods(
- mappingMethod,
- candidates,
- sourceTypes,
- mappingTargetType,
- returnType,
- criteria );
+ candidates = selector.getMatchingMethods( candidates, context );
}
return candidates;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java
index 6e86a30bc7..23af0a55f9 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
@@ -17,10 +17,10 @@
public class MostSpecificResultTypeSelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> candidates,
- List sourceTypes, Type mappingTargetType,
- Type returnType, SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> candidates,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
+ Type mappingTargetType = context.getMappingTargetType();
if ( candidates.size() < 2 || !criteria.isForMapping() || criteria.getQualifyingResultType() != null) {
return candidates;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java
index 210e462e3e..f2f5b591d6 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java
@@ -49,12 +49,9 @@ public QualifierSelector(TypeUtils typeUtils, ElementUtils elementUtils ) {
}
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
int numberOfQualifiersToMatch = 0;
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
new file mode 100644
index 0000000000..9e82dae25d
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.internal.model.source.selector;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.mapstruct.ap.internal.model.common.Parameter;
+import org.mapstruct.ap.internal.model.common.ParameterBinding;
+import org.mapstruct.ap.internal.model.common.SourceRHS;
+import org.mapstruct.ap.internal.model.common.Type;
+import org.mapstruct.ap.internal.model.common.TypeFactory;
+import org.mapstruct.ap.internal.model.source.Method;
+import org.mapstruct.ap.internal.model.source.SelectionParameters;
+
+/**
+ * Context passed to the selectors to get the information they need.
+ *
+ * @author Filip Hrisafov
+ */
+public class SelectionContext {
+
+ private final Type sourceType;
+ private final SelectionCriteria selectionCriteria;
+ private final Method mappingMethod;
+ private final Type mappingTargetType;
+ private final Type returnType;
+ private final Supplier> parameterBindingsProvider;
+ private List parameterBindings;
+
+ private SelectionContext(Type sourceType, SelectionCriteria selectionCriteria, Method mappingMethod,
+ Type mappingTargetType, Type returnType,
+ Supplier> parameterBindingsProvider) {
+ this.sourceType = sourceType;
+ this.selectionCriteria = selectionCriteria;
+ this.mappingMethod = mappingMethod;
+ this.mappingTargetType = mappingTargetType;
+ this.returnType = returnType;
+ this.parameterBindingsProvider = parameterBindingsProvider;
+ }
+
+ /**
+ * @return the source type that should be matched
+ */
+ public Type getSourceType() {
+ return sourceType;
+ }
+
+ /**
+ * @return the criteria used in the selection process
+ */
+ public SelectionCriteria getSelectionCriteria() {
+ return selectionCriteria;
+ }
+
+ /**
+ * @return the mapping target type that should be matched
+ */
+ public Type getMappingTargetType() {
+ return mappingTargetType;
+ }
+
+ /**
+ * @return the return type that should be matched
+ */
+ public Type getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * @return the available parameter bindings for the matching
+ */
+ public List getAvailableParameterBindings() {
+ if ( this.parameterBindings == null ) {
+ this.parameterBindings = this.parameterBindingsProvider.get();
+ }
+ return parameterBindings;
+ }
+
+ /**
+ * @return the mapping method, defined in Mapper for which this selection is carried out
+ */
+ public Method getMappingMethod() {
+ return mappingMethod;
+ }
+
+ public static SelectionContext forMappingMethods(Method mappingMethod, Type source, Type target,
+ SelectionCriteria criteria, TypeFactory typeFactory) {
+ return new SelectionContext(
+ source,
+ criteria,
+ mappingMethod,
+ target,
+ target,
+ () -> getAvailableParameterBindingsFromSourceType(
+ source,
+ target,
+ mappingMethod,
+ typeFactory
+ )
+ );
+ }
+
+ public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType,
+ SelectionParameters selectionParameters,
+ TypeFactory typeFactory) {
+ SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters );
+ return new SelectionContext(
+ null,
+ criteria,
+ mappingMethod,
+ targetType,
+ mappingMethod.getResultType(),
+ () -> getAvailableParameterBindingsFromMethod(
+ mappingMethod,
+ targetType,
+ criteria.getSourceRHS(),
+ typeFactory
+ )
+ );
+ }
+
+ public static SelectionContext forFactoryMethods(Method mappingMethod, Type alternativeTarget,
+ SelectionParameters selectionParameters,
+ TypeFactory typeFactory) {
+ SelectionCriteria criteria = SelectionCriteria.forFactoryMethods( selectionParameters );
+ return new SelectionContext(
+ null,
+ criteria,
+ mappingMethod,
+ alternativeTarget,
+ alternativeTarget,
+ () -> getAvailableParameterBindingsFromMethod(
+ mappingMethod,
+ alternativeTarget,
+ criteria.getSourceRHS(),
+ typeFactory
+ )
+ );
+ }
+
+ public static SelectionContext forPresenceCheckMethods(Method mappingMethod,
+ SelectionParameters selectionParameters,
+ TypeFactory typeFactory) {
+ SelectionCriteria criteria = SelectionCriteria.forPresenceCheckMethods( selectionParameters );
+ Type booleanType = typeFactory.getType( Boolean.class );
+ return new SelectionContext(
+ null,
+ criteria,
+ mappingMethod,
+ booleanType,
+ booleanType,
+ () -> getAvailableParameterBindingsFromMethod(
+ mappingMethod,
+ booleanType,
+ criteria.getSourceRHS(),
+ typeFactory
+ )
+ );
+ }
+
+ private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType,
+ SourceRHS sourceRHS,
+ TypeFactory typeFactory) {
+ List availableParams = new ArrayList<>( method.getParameters().size() + 3 );
+
+ if ( sourceRHS != null ) {
+ availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
+ availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) );
+ }
+ else {
+ availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
+ }
+
+ addTargetRelevantBindings( availableParams, targetType, typeFactory );
+
+ return availableParams;
+ }
+
+ private static List getAvailableParameterBindingsFromSourceType(Type sourceType,
+ Type targetType,
+ Method mappingMethod,
+ TypeFactory typeFactory) {
+
+ List availableParams = new ArrayList<>();
+
+ availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) );
+
+ for ( Parameter param : mappingMethod.getParameters() ) {
+ if ( param.isMappingContext() ) {
+ availableParams.add( ParameterBinding.fromParameter( param ) );
+ }
+ }
+
+ addTargetRelevantBindings( availableParams, targetType, typeFactory );
+
+ return availableParams;
+ }
+
+ /**
+ * Adds default parameter bindings for the mapping-target and target-type if not already available.
+ *
+ * @param availableParams Already available params, new entries will be added to this list
+ * @param targetType Target type
+ */
+ private static void addTargetRelevantBindings(List availableParams, Type targetType,
+ TypeFactory typeFactory) {
+ boolean mappingTargetAvailable = false;
+ boolean targetTypeAvailable = false;
+ boolean targetPropertyNameAvailable = false;
+
+ // search available parameter bindings if mapping-target and/or target-type is available
+ for ( ParameterBinding pb : availableParams ) {
+ if ( pb.isMappingTarget() ) {
+ mappingTargetAvailable = true;
+ }
+ else if ( pb.isTargetType() ) {
+ targetTypeAvailable = true;
+ }
+ else if ( pb.isTargetPropertyName() ) {
+ targetPropertyNameAvailable = true;
+ }
+ }
+
+ if ( !mappingTargetAvailable ) {
+ availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) );
+ }
+ if ( !targetTypeAvailable ) {
+ availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
+ }
+ if ( !targetPropertyNameAvailable ) {
+ availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) );
+ }
+ }
+
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java
index 930ce2d403..fb797f5808 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
@@ -9,7 +9,6 @@
import java.util.List;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -20,10 +19,9 @@
public class SourceRhsSelector implements MethodSelector {
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> candidates,
- List sourceTypes, Type mappingTargetType,
- Type returnType, SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> candidates,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
if ( candidates.size() < 2 || criteria.getSourceRHS() == null ) {
return candidates;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java
index 38a907aedb..0afae641db 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java
@@ -11,7 +11,6 @@
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.util.TypeUtils;
-import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -31,12 +30,9 @@ public TargetTypeSelector( TypeUtils typeUtils ) {
}
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
+ SelectionCriteria criteria = context.getSelectionCriteria();
TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType();
if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java
index 8acdd327c4..24275f594b 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java
@@ -11,9 +11,7 @@
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
-import org.mapstruct.ap.internal.model.common.SourceRHS;
import org.mapstruct.ap.internal.model.common.Type;
-import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.MethodMatcher;
import org.mapstruct.ap.internal.util.FormattingMessager;
@@ -29,44 +27,24 @@
*/
public class TypeSelector implements MethodSelector {
- private TypeFactory typeFactory;
private FormattingMessager messager;
- public TypeSelector(TypeFactory typeFactory, FormattingMessager messager) {
- this.typeFactory = typeFactory;
+ public TypeSelector(FormattingMessager messager) {
this.messager = messager;
}
@Override
- public List> getMatchingMethods(Method mappingMethod,
- List> methods,
- List sourceTypes,
- Type mappingTargetType,
- Type returnType,
- SelectionCriteria criteria) {
-
+ public List> getMatchingMethods(List> methods,
+ SelectionContext context) {
if ( methods.isEmpty() ) {
return methods;
}
+ Type returnType = context.getReturnType();
+
List> result = new ArrayList<>();
- List availableBindings;
- if ( sourceTypes.isEmpty() ) {
- // if no source types are given, we have a factory or lifecycle method
- availableBindings = getAvailableParameterBindingsFromMethod(
- mappingMethod,
- mappingTargetType,
- criteria.getSourceRHS()
- );
- }
- else {
- availableBindings = getAvailableParameterBindingsFromSourceTypes(
- sourceTypes,
- mappingTargetType,
- mappingMethod
- );
- }
+ List availableBindings = context.getAvailableParameterBindings();
for ( SelectedMethod method : methods ) {
List> parameterBindingPermutations =
@@ -74,7 +52,7 @@ public List> getMatchingMethods(Method mapp
if ( parameterBindingPermutations != null ) {
SelectedMethod matchingMethod =
- getMatchingParameterBinding( returnType, mappingMethod, method, parameterBindingPermutations );
+ getMatchingParameterBinding( returnType, context, method, parameterBindingPermutations );
if ( matchingMethod != null ) {
result.add( matchingMethod );
@@ -84,80 +62,8 @@ public List> getMatchingMethods(Method mapp
return result;
}
- private List getAvailableParameterBindingsFromMethod(Method method, Type targetType,
- SourceRHS sourceRHS) {
- List availableParams = new ArrayList<>( method.getParameters().size() + 3 );
-
- if ( sourceRHS != null ) {
- availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
- availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) );
- }
- else {
- availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
- }
-
- addTargetRelevantBindings( availableParams, targetType );
-
- return availableParams;
- }
-
- private List getAvailableParameterBindingsFromSourceTypes(List sourceTypes,
- Type targetType, Method mappingMethod) {
-
- List