diff --git a/build-config/pom.xml b/build-config/pom.xml
index aaae7b5404..e5a6284580 100644
--- a/build-config/pom.xml
+++ b/build-config/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml
index 293043e6f9..6375c8d99c 100644
--- a/core-jdk8/pom.xml
+++ b/core-jdk8/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
diff --git a/core/pom.xml b/core/pom.xml
index b02b809ed7..eccbf6ba25 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 30beb1d319..51788794d9 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
diff --git a/documentation/pom.xml b/documentation/pom.xml
index 0426805375..3111008ed5 100644
--- a/documentation/pom.xml
+++ b/documentation/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
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 ddf7fbcaed..4ccad69f2c 100644
--- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
@@ -161,13 +161,13 @@ The mechanism is also present on iterable mapping and map mapping. `@IterableMap
MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned.
-However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MappingConfig`, the mapping result can be altered to return empty *default* values. This means for:
+However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MapperConfig`, the mapping result can be altered to return empty *default* values. This means for:
* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present.
* *Iterables / Arrays*: an empty iterable will be returned.
* *Maps*: an empty map will be returned.
-The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`.
+The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MapperConfig#nullValueMappingStrategy`.
[[mapping-result-for-null-properties]]
@@ -179,13 +179,13 @@ By default the target property will be set to null.
However:
-1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result can be altered to return *default* values.
+1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result can be altered to return *default* values.
For `List` MapStruct generates an `ArrayList`, for `Map` a `HashMap`, for arrays an empty array, for `String` `""` and for primitive / boxed types a representation of `false` or `0`.
For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`.
-2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target.
+2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target.
-The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MappingConfig#nullValuePropertyMappingStrategy`.
+The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MapperConfig#nullValuePropertyMappingStrategy`.
[NOTE]
====
@@ -213,7 +213,7 @@ First calling a mapping method on the source property is not protected by a null
The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean.
-The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MappingConfig#nullValueCheckStrategy`.
+The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MaperConfig#nullValueCheckStrategy`.
[[source-presence-check]]
=== Source presence checking
diff --git a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc
new file mode 100644
index 0000000000..c31424a0de
--- /dev/null
+++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc
@@ -0,0 +1,190 @@
+[[third-party-api-integration]]
+== Third-party API integration
+
+[[non-shipped-annotations]]
+=== Non-shipped annotations
+
+There are various use-cases you must resolve ambiguity for MapStruct to use a correct piece of code.
+However, the primary goal of MapStruct is to focus on bean mapping without polluting the entity code.
+For that reason, MapStruct is flexible enough to interact with already defined annotations from third-party libraries.
+The requirement to enable this behavior is to match the _name_ of such annotation.
+Hence, we say that annotation can be _from any package_.
+
+The annotations _named_ `@ConstructorProperties` and `@Default` are currently examples of this kind of annotation.
+
+[WARNING]
+====
+If such named third-party annotation exists, it does not guarantee its `@Target` matches with the intended placement.
+Be aware of placing a third-party annotation just for sake of mapping is not recommended as long as it might lead to unwanted side effects caused by that library.
+====
+
+A very common case is that no third-party dependency imported to your project provides such annotation or is inappropriate for use as already described.
+In such cases create your own annotation, for example:
+
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+package foo.support.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.CONSTRUCTOR)
+@Retention(RetentionPolicy.CLASS)
+public @interface Default {
+
+}
+----
+====
+
+[[lombok]]
+=== Lombok
+
+MapStruct works together with https://projectlombok.org/[Project Lombok] as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14.
+
+MapStruct takes advantage of generated getters, setters, and constructors and uses them to generate the mapper implementations.
+
+[NOTE]
+====
+Lombok 1.18.16 introduces a breaking change (https://projectlombok.org/changelog[changelog]).
+The additional annotation processor `lombok-mapstruct-binding` (https://mvnrepository.com/artifact/org.projectlombok/lombok-mapstruct-binding[Maven]) must be added otherwise MapStruct stops working with Lombok.
+This resolves the compilation issues of Lombok and MapStruct modules.
+
+[source, xml]
+----
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.1.0
+
+----
+====
+
+==== Set up
+
+The set up using Maven or Gradle does not differ from what is described in <>. Additionally, you need to provide Lombok dependencies.
+
+.Maven configuration
+====
+[source, xml, linenums]
+[subs="verbatim,attributes"]
+----
+
+
+ {mapstructVersion}
+ 1.18.16
+ 1.8
+ 1.8
+
+
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+
+
+ org.projectlombok
+ lombok
+ ${org.projectlombok.version}
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${org.projectlombok.version}
+
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.1.0
+
+
+
+
+
+
+----
+====
+
+.Gradle configuration (3.4 and later)
+====
+[source, groovy, linenums]
+[subs="verbatim,attributes"]
+----
+
+dependencies {
+
+ implementation "org.mapstruct:mapstruct:${mapstructVersion}"
+ implementation "org.projectlombok:lombok:1.18.16"
+ annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.1.0"
+ annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
+ annotationProcessor "org.projectlombok:lombok:1.18.16"
+}
+
+----
+====
+
+The usage combines what you already know from <> and Lombok.
+
+.Usage of MapStruct with Lombok
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Data
+public class Source {
+
+ private String test;
+}
+
+public class Target {
+
+ private Long testing;
+
+ public Long getTesting() {
+ return testing;
+ }
+
+ public void setTesting( Long testing ) {
+ this.testing = testing;
+ }
+}
+
+@Mapper
+public interface SourceTargetMapper {
+
+ SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
+
+ @Mapping( source = "test", target = "testing" )
+ Target toTarget( Source s );
+}
+
+----
+====
+
+A working example can be found on the GitHub project https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-lombok[mapstruct-lombok].
diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
index fb954ac58f..76626f44ce 100644
--- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
@@ -76,7 +76,7 @@ It will not work with older versions.
Add the following to your Gradle build file in order to enable MapStruct:
-.Gradle configuration (3.4 and later)
+.Gradle configuration
====
[source, groovy, linenums]
[subs="verbatim,attributes"]
@@ -84,14 +84,9 @@ Add the following to your Gradle build file in order to enable MapStruct:
...
plugins {
...
- id 'net.ltgt.apt' version '0.20'
+ id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}
-// You can integrate with your IDEs.
-// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
-apply plugin: 'net.ltgt.apt-idea'
-apply plugin: 'net.ltgt.apt-eclipse'
-
dependencies {
...
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
@@ -103,33 +98,6 @@ dependencies {
...
----
====
-.Gradle (3.3 and older)
-====
-[source, groovy, linenums]
-[subs="verbatim,attributes"]
-----
-...
-plugins {
- ...
- id 'net.ltgt.apt' version '0.20'
-}
-
-// You can integrate with your IDEs.
-// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
-apply plugin: 'net.ltgt.apt-idea'
-apply plugin: 'net.ltgt.apt-eclipse'
-
-dependencies {
- ...
- compile "org.mapstruct:mapstruct:${mapstructVersion}"
- annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
-
- // If you are using mapstruct in test code
- testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
-}
-...
-----
-====
You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-gradle[mapstruct-examples] project on GitHub.
@@ -187,15 +155,15 @@ When invoking javac directly, these options are passed to the compiler in the fo
true
-
+
-Amapstruct.suppressGeneratorTimestamp=true
-
-
+
+
-Amapstruct.suppressGeneratorVersionInfoComment=true
-
-
+
+
-Amapstruct.verbose=true
-
+
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 54a0dbd709..be3435b541 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -516,7 +516,8 @@ public class PersonMapperImpl implements PersonMapper {
Supported builder frameworks:
-* https://projectlombok.org/[Lombok] - requires having the Lombok classes in a separate module. See for more information https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538]
+* https://projectlombok.org/[Lombok] - It is required to have the Lombok classes in a separate module.
+See for more information at https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] and to set up Lombok with MapStruct, refer to <>.
* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue]
* https://immutables.github.io/[Immutables] - When Immutables are present on the annotation processor path then the `ImmutablesAccessorNamingStrategy` and `ImmutablesBuilderProvider` would be used by default
* https://github.com/google/FreeBuilder[FreeBuilder] - When FreeBuilder is present on the annotation processor path then the `FreeBuilderAccessorNamingStrategy` would be used by default.
@@ -537,10 +538,10 @@ When doing a mapping MapStruct checks if there is a builder for the type being m
If there is no builder, then MapStruct looks for a single accessible constructor.
When there are multiple constructors then the following is done to pick the one which should be used:
-* If a constructor is annotated with an annotation named `@Default` (from any package) it will be used.
+* If a constructor is annotated with an annotation _named_ `@Default` (from any package, see <>) it will be used.
* If a single public constructor exists then it will be used to construct the object, and the other non public constructors will be ignored.
* If a parameterless constructor exists then it will be used to construct the object, and the other constructors will be ignored.
-* If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotation named `@Default` (from any package) can used.
+* If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotation _named_ `@Default` (from any package, see <>) can used.
.Deciding which constructor to use
====
@@ -585,7 +586,7 @@ public class Van {
====
When using a constructor then the names of the parameters of the constructor will be used and matched to the target properties.
-When the constructor has an annotation named `@ConstructorProperties` (from any package) then this annotation will be used to get the names of the parameters.
+When the constructor has an annotation _named_ `@ConstructorProperties` (from any package, see <>) then this annotation will be used to get the names of the parameters.
[NOTE]
====
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 725c69bad0..9619d0fe26 100644
--- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
@@ -155,7 +155,7 @@ When generating the implementation of a mapping method, MapStruct will apply the
. If no such method was found MapStruct will try to generate an automatic sub-mapping method that will do the mapping between the source and target attributes.
. If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path.
-A mapping control (`MappingControl`) can be defined on all levels (`@MappingConfig`, `@Mapper`, `@BeanMapping`, `@Mapping`), the latter taking precedence over the former. For example: `@Mapper( mappingControl = NoComplexMapping.class )` takes precedence over `@MapperConfig( mappingControl = DeepClone.class )`. `@IterableMapping` and `@MapMapping` work similar as `@Mapping`. MappingControl is experimental from MapStruct 1.4.
+A mapping control (`MappingControl`) can be defined on all levels (`@MapperConfig`, `@Mapper`, `@BeanMapping`, `@Mapping`), the latter taking precedence over the former. For example: `@Mapper( mappingControl = NoComplexMapping.class )` takes precedence over `@MapperConfig( mappingControl = DeepClone.class )`. `@IterableMapping` and `@MapMapping` work similar as `@Mapping`. MappingControl is experimental from MapStruct 1.4.
`MappingControl` has an enum that corresponds to the first 4 options above: `MappingControl.Use#DIRECT`, `MappingControl.Use#MAPPING_METHOD`, `MappingControl.Use#BUILT_IN_CONVERSION` and `MappingControl.Use#COMPLEX_MAPPING` the presence of which allows the user to switch *on* a option. The absence of an enum switches *off* a mapping option. Default they are all present enabling all mapping options.
[NOTE]
diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc
index 1aa08b17b8..b83c874c5e 100644
--- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc
+++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc
@@ -44,4 +44,6 @@ include::chapter-11-reusing-mapping-configurations.asciidoc[]
include::chapter-12-customizing-mapping.asciidoc[]
-include::chapter-13-using-mapstruct-spi.asciidoc[]
\ No newline at end of file
+include::chapter-13-using-mapstruct-spi.asciidoc[]
+
+include::chapter-14-third-party-api-integration.asciidoc[]
\ No newline at end of file
diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml
index f19ecfe343..5495b5b74e 100644
--- a/integrationtest/pom.xml
+++ b/integrationtest/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
diff --git a/parent/pom.xml b/parent/pom.xml
index 9e5c04f27a..943471039e 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -11,7 +11,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
pom
MapStruct Parent
diff --git a/pom.xml b/pom.xml
index 28c35aea74..1e5c125d53 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
parent/pom.xml
diff --git a/processor/pom.xml b/processor/pom.xml
index bbec83bf29..8cdc4cf876 100644
--- a/processor/pom.xml
+++ b/processor/pom.xml
@@ -12,7 +12,7 @@
org.mapstruct
mapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.4.3-SNAPSHOT
../parent/pom.xml
@@ -123,6 +123,7 @@
org.mapstruct.processor
+ annotation-processor
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 4d086b5126..898f7df8a0 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
@@ -938,6 +938,15 @@ private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTyp
if ( targetWriteAccessor == null ) {
if ( targetReadAccessor == null ) {
+ if ( mapping.getInheritContext() != null && mapping.getInheritContext().isForwarded() &&
+ mapping.getInheritContext().getTemplateMethod().isUpdateMethod() != method.isUpdateMethod() ) {
+ // When a configuration is inherited and the template method is not same type as the current
+ // method then we can safely ignore this mapping.
+ // This means that a property which is inherited might be present for a direct mapping
+ // via the Builder, but not for an update mapping (directly on the object itself),
+ // or vice versa
+ return false;
+ }
Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet();
String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors );
@@ -1315,6 +1324,17 @@ 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() );
}
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java
index b9bbf6d094..be08ac1ca4 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java
@@ -22,6 +22,8 @@
import static org.mapstruct.ap.internal.util.Collections.first;
+import javax.lang.model.element.AnnotationMirror;
+
/**
* Builder that can be used to build {@link ContainerMappingMethod}(s).
*
@@ -37,6 +39,7 @@ public abstract class ContainerMappingMethodBuilder selfType, String errorMessagePart) {
super( selfType );
@@ -58,6 +61,11 @@ public B callingContextTargetPropertyName(String callingContextTargetPropertyNam
return myself;
}
+ public B positionHint(AnnotationMirror positionHint) {
+ this.positionHint = positionHint;
+ return myself;
+ }
+
@Override
public final M build() {
Type sourceParameterType = first( method.getSourceParameters() ).getType();
@@ -88,7 +96,7 @@ public final M build() {
formattingParameters,
criteria,
sourceRHS,
- null,
+ positionHint,
() -> forge( sourceRHS, sourceElementType, targetElementType )
);
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 240343ad9b..459c428058 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
@@ -374,6 +374,11 @@ private Assignment getDefaultValueAssignment( Assignment rhs ) {
return null;
}
+ private boolean hasDefaultValueAssignment(Assignment rhs) {
+ return ( defaultValue != null || defaultJavaExpression != null ) &&
+ ( !rhs.getSourceType().isPrimitive() || rhs.getSourcePresenceCheckerReference() != null);
+ }
+
private Assignment assignToPlain(Type targetType, AccessorType targetAccessorType,
Assignment rightHandSide) {
@@ -415,7 +420,9 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) {
);
}
else {
- boolean includeSourceNullCheck = SetterWrapper.doSourceNullCheck( rhs, nvcs, nvpms, targetType );
+ // If the property mapping has a default value assignment then we have to do a null value check
+ boolean includeSourceNullCheck = SetterWrapper.doSourceNullCheck( rhs, nvcs, nvpms, targetType )
+ || hasDefaultValueAssignment( rhs );
if ( !includeSourceNullCheck ) {
// solution for #834 introduced a local var and null check for nested properties always.
// however, a local var is not needed if there's no need to check for null.
@@ -610,6 +617,7 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou
.method( methodRef )
.selectionParameters( selectionParameters )
.callingContextTargetPropertyName( targetPropertyName )
+ .positionHint( positionHint )
.build();
return createForgedAssignment( source, methodRef, iterableMappingMethod );
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 b2b12b8a25..6bb6d7369b 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
@@ -1160,7 +1160,12 @@ public Type visitTypeVariable(TypeVariable t, Type parameterized) {
@Override
public Type visitDeclared(DeclaredType t, Type parameterized) {
if ( types.isAssignable( types.erasure( t ), types.erasure( parameterized.getTypeMirror() ) ) ) {
- // if same type, we can cast en assume number of type args are also the same
+ // We can't assume that the type args are the same
+ // e.g. List is assignable to Object
+ if ( t.getTypeArguments().size() != parameterized.getTypeParameters().size() ) {
+ return super.visitDeclared( t, parameterized );
+ }
+
for ( int i = 0; i < t.getTypeArguments().size(); i++ ) {
Type result = visit( t.getTypeArguments().get( i ), parameterized.getTypeParameters().get( i ) );
if ( result != 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 81f870e25b..ecc05888d6 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
@@ -119,7 +119,7 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() {
public BuilderGem getBuilder() {
// TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility
// but assuming a default @Builder will make testcases fail. Not having a default means that you need to
- // specify this mandatory on @MappingConfig and @Mapper.
+ // specify this mandatory on @MapperConfig and @Mapper.
return null;
}
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 b7fd7f00d0..859a4c6f5f 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
@@ -230,9 +230,11 @@ private void addAllNonRedefined(Set inheritedMappings) {
}
private boolean isRedefined(Set redefinedNames, String inheritedName ) {
- for ( String redefinedName : redefinedNames ) {
- if ( elementsAreContainedIn( redefinedName, inheritedName ) ) {
- return true;
+ if ( inheritedName != null ) {
+ for ( String redefinedName : redefinedNames ) {
+ if ( elementsAreContainedIn( inheritedName, redefinedName ) ) {
+ return true;
+ }
}
}
return false;
@@ -241,7 +243,7 @@ private boolean isRedefined(Set redefinedNames, String inheritedName ) {
private boolean elementsAreContainedIn( String redefinedName, String inheritedName ) {
if ( inheritedName != null && redefinedName.startsWith( inheritedName ) ) {
// it is possible to redefine an exact matching source name, because the same source can be mapped to
- // multiple targets. It is not possible for target, but caught by the Set and equals methoded in
+ // multiple targets. It is not possible for target, but caught by the Set and equals method in
// MappingOptions. SourceName == null also could hint at redefinition
if ( redefinedName.length() > inheritedName.length() ) {
// redefined.lenght() > inherited.length(), first following character should be separator
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 73392a00f8..4394ab7b43 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
@@ -298,7 +298,13 @@ && allowDirect( sourceType, targetType ) ) {
}
if ( hasQualfiers() ) {
- printQualifierMessage( selectionCriteria );
+ if ((sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType()) {
+ // Allow forging iterable mapping when no iterable mapping already found
+ return forger.get();
+ }
+ else {
+ printQualifierMessage( selectionCriteria );
+ }
}
else if ( allowMappingMethod() ) {
// only forge if we would allow mapping method
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.java
new file mode 100644
index 0000000000..5aa899d055
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.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._2245;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2245")
+@RunWith(AnnotationProcessorTestRunner.class)
+@WithClasses({
+ TestMapper.class
+})
+public class Issue2245Test {
+
+ @Rule
+ public final GeneratedSource generatedSource = new GeneratedSource()
+ .addComparisonToFixtureFor( TestMapper.class );
+
+ @Test
+ public void shouldGenerateSourceGetMethodOnce() {
+
+ TestMapper.Tenant tenant =
+ TestMapper.INSTANCE.map( new TestMapper.TenantDTO( new TestMapper.Inner( "acme" ) ) );
+
+ assertThat( tenant ).isNotNull();
+ assertThat( tenant.getId() ).isEqualTo( "acme" );
+
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.java
new file mode 100644
index 0000000000..750ee12fa5
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2245;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface TestMapper {
+
+ TestMapper INSTANCE = Mappers.getMapper( TestMapper.class );
+
+ class Tenant {
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+ }
+
+ class Inner {
+ private final String id;
+
+ public Inner(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+ }
+
+ class TenantDTO {
+ private final Inner inner;
+
+ public TenantDTO(Inner inner) {
+ this.inner = inner;
+ }
+
+ public Inner getInner() {
+ return inner;
+ }
+ }
+
+ @Mapping(target = "id", source = "inner.id", defaultValue = "test")
+ Tenant map(TenantDTO tenant);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.java
new file mode 100644
index 0000000000..d09f3440fe
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2251;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface Issue2251Mapper {
+
+ Issue2251Mapper INSTANCE = Mappers.getMapper( Issue2251Mapper.class );
+
+ @Mapping(target = "value1", source = "source.value")
+ Target map(Source source, String value2);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.java
new file mode 100644
index 0000000000..d08a5f7d74
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.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._2251;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2251")
+@RunWith(AnnotationProcessorTestRunner.class)
+@WithClasses({
+ Issue2251Mapper.class,
+ Source.class,
+ Target.class,
+})
+public class Issue2251Test {
+
+ @Test
+ public void shouldGenerateCorrectCode() {
+
+ Target target = Issue2251Mapper.INSTANCE.map( new Source( "source" ), "test" );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.getValue1() ).isEqualTo( "source" );
+ assertThat( target.getValue2() ).isEqualTo( "test" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.java
new file mode 100644
index 0000000000..cbd882a8f8
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2251;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Source {
+
+ private final String value;
+
+ public Source(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.java
new file mode 100644
index 0000000000..363129797f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2251;
+
+public class Target {
+ private final String value1;
+ private final String value2;
+
+ public Target(String value1, String value2) {
+ this.value1 = value1;
+ this.value2 = value2;
+ }
+
+ public String getValue1() {
+ return value1;
+ }
+
+ public String getValue2() {
+ return value2;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.java
new file mode 100644
index 0000000000..fc0e007daa
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.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._2253;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2253")
+@RunWith( AnnotationProcessorTestRunner.class )
+@WithClasses( {
+ TestMapper.class,
+} )
+public class Issue2253Test {
+
+ @Test
+ public void shouldNotTreatMatchedSourceParameterAsBean() {
+
+ TestMapper.Person person = TestMapper.INSTANCE.map( new TestMapper.PersonDto( 20 ), "Tester" );
+
+ assertThat( person.getAge() ).isEqualTo( 20 );
+ assertThat( person.getName() ).isEqualTo( "Tester" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java
new file mode 100644
index 0000000000..1e05e444a2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2253;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
+public interface TestMapper {
+
+ TestMapper INSTANCE = Mappers.getMapper( TestMapper.class );
+
+ Person map(PersonDto personDto, String name);
+
+ class Person {
+ private final int age;
+ private final String name;
+
+ public Person(int age, String name) {
+ this.age = age;
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ class PersonDto {
+ private final int age;
+
+ public PersonDto(int age) {
+ this.age = age;
+ }
+
+ public int getAge() {
+ return age;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.java
new file mode 100644
index 0000000000..59a0a42ddb
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2263;
+
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Erroneous2263Mapper {
+
+ Target map(Source source);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.java
new file mode 100644
index 0000000000..03e545b210
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2263;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2263")
+@RunWith(AnnotationProcessorTestRunner.class)
+@WithClasses({
+ Erroneous2263Mapper.class,
+ Source.class,
+ Target.class,
+})
+public class Issue2263Test {
+
+ @Test
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = Erroneous2263Mapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 16,
+ message = "Can't map property \"Object value\" to \"String value\". " +
+ "Consider to declare/implement a mapping method: \"String map(Object value)\".")
+ })
+ public void shouldCorrectlyReportUnmappableTargetObject() {
+
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.java
new file mode 100644
index 0000000000..54e5b7fd38
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2263;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Source {
+
+ private final Object value;
+
+ public Source(Object value) {
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.java
new file mode 100644
index 0000000000..943a51264c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2263;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Target {
+
+ private final String value;
+
+ public Target(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.java
new file mode 100644
index 0000000000..8527be3f89
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2278;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * ReproducerA
+ *
+ */
+@Mapper
+public interface Issue2278MapperA {
+
+ Issue2278MapperA INSTANCE = Mappers.getMapper( Issue2278MapperA.class );
+
+ @Mapping( target = "detailsDTO", source = "details" )
+ @Mapping( target = "detailsDTO.fuelType", ignore = true )
+ CarDTO map(Car in);
+
+ // checkout the Issue2278ReferenceMapper, the @InheritInverseConfiguration
+ // is de-facto @Mapping( target = "details", source = "detailsDTO" )
+ @InheritInverseConfiguration
+ @Mapping( target = "details.model", ignore = true )
+ @Mapping( target = "details.type", constant = "gto")
+ @Mapping( target = "details.fuel", source = "detailsDTO.fuelType")
+ Car map(CarDTO in);
+
+ class Car {
+ //CHECKSTYLE:OFF
+ public Details details;
+ //CHECKSTYLE:ON
+ }
+
+ class CarDTO {
+ //CHECKSTYLE:OFF
+ public DetailsDTO detailsDTO;
+ //CHECKSTYLE:ON
+ }
+
+ class Details {
+ //CHECKSTYLE:OFF
+ public String brand;
+ public String model;
+ public String type;
+ public String fuel;
+ //CHECKSTYLE:ON
+ }
+
+ class DetailsDTO {
+ //CHECKSTYLE:OFF
+ public String brand;
+ public String fuelType;
+ //CHECKSTYLE:ON
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java
new file mode 100644
index 0000000000..b9d0abd523
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2278;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * ReproducerB
+ *
+ */
+@Mapper
+public interface Issue2278MapperB {
+
+ Issue2278MapperB INSTANCE = Mappers.getMapper( Issue2278MapperB.class );
+
+ // id mapping is cros-linked
+ @Mapping( target = "amount", source = "price" )
+ @Mapping( target = "detailsDTO.brand", source = "details.type" )
+ @Mapping( target = "detailsDTO.id1", source = "details.id2" )
+ @Mapping( target = "detailsDTO.id2", source = "details.id1" )
+ CarDTO map(Car in);
+
+ // inherit inverse, but undo cross-link in one sweep
+ @InheritInverseConfiguration // inherits all
+ @Mapping( target = "details", source = "detailsDTO" ) // resets everything on details <> detailsDto
+ @Mapping( target = "details.type", source = "detailsDTO.brand" ) // so this needs to be redone
+ Car map2(CarDTO in);
+
+ class Car {
+ //CHECKSTYLE:OFF
+ public float price;
+ public Details details;
+ //CHECKSTYLE:ON
+ }
+
+ class CarDTO {
+ //CHECKSTYLE:OFF
+ public float amount;
+ public DetailsDTO detailsDTO;
+ //CHECKSTYLE:ON
+ }
+
+ class Details {
+ //CHECKSTYLE:OFF
+ public String type;
+ public String id1;
+ public String id2;
+ //CHECKSTYLE:ON
+ }
+
+ class DetailsDTO {
+ //CHECKSTYLE:OFF
+ public String brand;
+ public String id1;
+ public String id2;
+ //CHECKSTYLE:ON
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.java
new file mode 100644
index 0000000000..48b457f84c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2278;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * A reference mapper, that checks how a regular forward merge works
+ */
+@Mapper
+public interface Issue2278ReferenceMapper {
+
+ Issue2278ReferenceMapper INSTANCE = Mappers.getMapper( Issue2278ReferenceMapper.class );
+
+ @Mapping( target = "details", source = "detailsDTO" )
+ @Mapping( target = "details.model", ignore = true )
+ @Mapping( target = "details.type", constant = "gto")
+ @Mapping( target = "details.fuel", source = "detailsDTO.fuelType")
+ Car map(CarDTO in);
+
+ class Car {
+ //CHECKSTYLE:OFF
+ public Details details;
+ //CHECKSTYLE:ON
+ }
+
+ class CarDTO {
+ //CHECKSTYLE:OFF
+ public DetailsDTO detailsDTO;
+ //CHECKSTYLE:ON
+ }
+
+ class Details {
+ //CHECKSTYLE:OFF
+ public String brand;
+ public String model;
+ public String type;
+ public String fuel;
+ //CHECKSTYLE:ON
+ }
+
+ class DetailsDTO {
+ //CHECKSTYLE:OFF
+ public String brand;
+ public String fuelType;
+ //CHECKSTYLE:ON
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java
new file mode 100644
index 0000000000..eefebfa870
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2278;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+@IssueKey("2278")
+@RunWith( AnnotationProcessorTestRunner.class)
+public class Issue2278Test {
+
+ @Test
+ @WithClasses( Issue2278ReferenceMapper.class )
+ public void testReferenceMergeBehaviour() {
+
+ Issue2278ReferenceMapper.CarDTO dto = new Issue2278ReferenceMapper.CarDTO();
+ dto.detailsDTO = new Issue2278ReferenceMapper.DetailsDTO();
+ dto.detailsDTO.brand = "Ford";
+ dto.detailsDTO.fuelType = "petrol";
+
+ Issue2278ReferenceMapper.Car target = Issue2278ReferenceMapper.INSTANCE.map( dto );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.details ).isNotNull();
+ assertThat( target.details.brand ).isEqualTo( "Ford" );
+ assertThat( target.details.model ).isNull();
+ assertThat( target.details.type ).isEqualTo( "gto" );
+ assertThat( target.details.fuel ).isEqualTo( "petrol" );
+
+ }
+
+ @Test
+ @WithClasses( Issue2278MapperA.class )
+ public void shouldBehaveJustAsTestReferenceMergeBehaviour() {
+
+ Issue2278MapperA.CarDTO dto = new Issue2278MapperA.CarDTO();
+ dto.detailsDTO = new Issue2278MapperA.DetailsDTO();
+ dto.detailsDTO.brand = "Ford";
+ dto.detailsDTO.fuelType = "petrol";
+
+ Issue2278MapperA.Car target = Issue2278MapperA.INSTANCE.map( dto );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.details ).isNotNull();
+ assertThat( target.details.brand ).isEqualTo( "Ford" );
+ assertThat( target.details.model ).isNull();
+ assertThat( target.details.type ).isEqualTo( "gto" );
+ assertThat( target.details.fuel ).isEqualTo( "petrol" );
+
+ }
+
+ @Test
+ @WithClasses( Issue2278MapperB.class )
+ public void shouldOverrideDetailsMappingWithRedefined() {
+
+ Issue2278MapperB.Car source = new Issue2278MapperB.Car();
+ source.details = new Issue2278MapperB.Details();
+ source.details.type = "Ford";
+ source.details.id1 = "id1";
+ source.details.id2 = "id2";
+ source.price = 20000f;
+
+ Issue2278MapperB.CarDTO target1 = Issue2278MapperB.INSTANCE.map( source );
+
+ assertThat( target1 ).isNotNull();
+ assertThat( target1.amount ).isEqualTo( 20000f );
+ assertThat( target1.detailsDTO ).isNotNull();
+ assertThat( target1.detailsDTO.brand ).isEqualTo( "Ford" );
+ assertThat( target1.detailsDTO.id1 ).isEqualTo( "id2" );
+ assertThat( target1.detailsDTO.id2 ).isEqualTo( "id1" );
+
+ // restore the mappings, just to make it logical again
+ target1.detailsDTO.id1 = "id1";
+ target1.detailsDTO.id2 = "id2";
+
+ // now check the reverse inheritance
+ Issue2278MapperB.Car target2 = Issue2278MapperB.INSTANCE.map2( target1 );
+
+ assertThat( target2 ).isNotNull();
+ assertThat( target2.price ).isEqualTo( 20000f );
+ assertThat( target2.details ).isNotNull();
+ assertThat( target2.details.type ).isEqualTo( "Ford" ); // should inherit
+ assertThat( target2.details.id1 ).isEqualTo( "id1" ); // should be undone
+ assertThat( target2.details.id2 ).isEqualTo( "id2" ); // should be undone
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.java
new file mode 100644
index 0000000000..316959f48b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2301;
+
+import java.util.Set;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Artifact {
+
+ private String name;
+ private Set dependantBuildRecords;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getDependantBuildRecords() {
+ return dependantBuildRecords;
+ }
+
+ public void setDependantBuildRecords(Set dependantBuildRecords) {
+ this.dependantBuildRecords = dependantBuildRecords;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private String name;
+ private Set dependantBuildRecords;
+
+ public Artifact build() {
+ Artifact artifact = new Artifact();
+ artifact.setName( name );
+ artifact.setDependantBuildRecords( dependantBuildRecords );
+
+ return artifact;
+ }
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder dependantBuildRecord(String dependantBuildRecord) {
+ this.dependantBuildRecords.add( dependantBuildRecord );
+ return this;
+ }
+
+ public Builder dependantBuildRecords(Set dependantBuildRecords) {
+ this.dependantBuildRecords = dependantBuildRecords;
+ return this;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.java
new file mode 100644
index 0000000000..68b9280930
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2301;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ArtifactDto {
+
+ private final String name;
+
+ public ArtifactDto(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.java
new file mode 100644
index 0000000000..cd3cca3744
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2301;
+
+import org.mapstruct.InheritConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface Issue2301Mapper {
+
+ Issue2301Mapper INSTANCE = Mappers.getMapper( Issue2301Mapper.class );
+
+ @Mapping(target = "dependantBuildRecords", ignore = true)
+ @Mapping(target = "dependantBuildRecord", ignore = true)
+ Artifact map(ArtifactDto dto);
+
+ @InheritConfiguration
+ void update(@MappingTarget Artifact artifact, ArtifactDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.java
new file mode 100644
index 0000000000..8c237a2d30
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2301;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@IssueKey("2301")
+@RunWith(AnnotationProcessorTestRunner.class)
+@WithClasses({
+ Artifact.class,
+ ArtifactDto.class,
+ Issue2301Mapper.class
+})
+public class Issue2301Test {
+
+ @Test
+ public void shouldCorrectlyIgnoreProperties() {
+ Artifact artifact = Issue2301Mapper.INSTANCE.map( new ArtifactDto( "mapstruct" ) );
+
+ assertThat( artifact ).isNotNull();
+ assertThat( artifact.getName() ).isEqualTo( "mapstruct" );
+ assertThat( artifact.getDependantBuildRecords() ).isNull();
+
+ Issue2301Mapper.INSTANCE.update( artifact, new ArtifactDto( "mapstruct-processor" ) );
+
+ assertThat( artifact.getName() ).isEqualTo( "mapstruct-processor" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.java
new file mode 100644
index 0000000000..20621e4d01
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2318;
+
+import org.mapstruct.InheritConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.MapperConfig;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(config = Issue2318Mapper.Config.class)
+public interface Issue2318Mapper {
+
+ Issue2318Mapper INSTANCE = Mappers.getMapper( Issue2318Mapper.class );
+
+ @MapperConfig
+ interface Config {
+
+ @Mapping(target = "parentValue1", source = "holder")
+ TargetParent mapParent(SourceParent parent);
+
+ @InheritConfiguration(name = "mapParent")
+ @Mapping(target = "childValue", source = "value")
+ @Mapping(target = "parentValue2", source = "holder.parentValue2")
+ TargetChild mapChild(SourceChild child);
+ }
+
+ @InheritConfiguration(name = "mapChild")
+ TargetChild mapChild(SourceChild child);
+
+ default String parentValue1(SourceParent.Holder holder) {
+ return holder.getParentValue1();
+ }
+
+ class SourceParent {
+ private Holder holder;
+
+ public Holder getHolder() {
+ return holder;
+ }
+
+ public void setHolder(Holder holder) {
+ this.holder = holder;
+ }
+
+ public static class Holder {
+ private String parentValue1;
+ private Integer parentValue2;
+
+ public String getParentValue1() {
+ return parentValue1;
+ }
+
+ public void setParentValue1(String parentValue1) {
+ this.parentValue1 = parentValue1;
+ }
+
+ public Integer getParentValue2() {
+ return parentValue2;
+ }
+
+ public void setParentValue2(Integer parentValue2) {
+ this.parentValue2 = parentValue2;
+ }
+ }
+ }
+
+ class SourceChild extends SourceParent {
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ class TargetParent {
+ private String parentValue1;
+ private Integer parentValue2;
+
+ public String getParentValue1() {
+ return parentValue1;
+ }
+
+ public void setParentValue1(String parentValue1) {
+ this.parentValue1 = parentValue1;
+ }
+
+ public Integer getParentValue2() {
+ return parentValue2;
+ }
+
+ public void setParentValue2(Integer parentValue2) {
+ this.parentValue2 = parentValue2;
+ }
+ }
+
+ class TargetChild extends TargetParent {
+ private String childValue;
+
+ public String getChildValue() {
+ return childValue;
+ }
+
+ public void setChildValue(String childValue) {
+ this.childValue = childValue;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.java
new file mode 100644
index 0000000000..a1f1ad250b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.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._2318;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.bugs._2318.Issue2318Mapper.SourceChild;
+import org.mapstruct.ap.test.bugs._2318.Issue2318Mapper.TargetChild;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@IssueKey("2318")
+@RunWith(AnnotationProcessorTestRunner.class)
+public class Issue2318Test {
+
+ @Test
+ @WithClasses( Issue2318Mapper.class )
+ public void shouldMap() {
+
+ SourceChild source = new SourceChild();
+ source.setValue( "From child" );
+ source.setHolder( new Issue2318Mapper.SourceParent.Holder() );
+ source.getHolder().setParentValue1( "From parent" );
+ source.getHolder().setParentValue2( 12 );
+
+ TargetChild target = Issue2318Mapper.INSTANCE.mapChild( source );
+
+ assertThat( target.getParentValue1() ).isEqualTo( "From parent" );
+ assertThat( target.getParentValue2() ).isEqualTo( 12 );
+ assertThat( target.getChildValue() ).isEqualTo( "From child" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedWithIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedWithIterableMapper.java
new file mode 100644
index 0000000000..088207c610
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/ErroneousMessageByNamedWithIterableMapper.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.qualifier.errors;
+
+import java.util.Collection;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper
+public interface ErroneousMessageByNamedWithIterableMapper {
+
+ @Mapping(target = "elements", qualifiedByName = "SelectMe")
+ Target map(Source source);
+
+ // CHECKSTYLE:OFF
+ class Source {
+
+ public Collection elements;
+ }
+
+ class Target {
+
+ public Collection elements;
+ }
+ // CHECKSTYLE ON
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java
index 4c8edc64dd..48295ed4fb 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/errors/QualfierMessageTest.java
@@ -87,4 +87,27 @@ public void testNoQualifyingMethodByNamedFound() {
)
public void testNoQualifyingMethodByAnnotationAndNamedFound() {
}
+
+ @Test
+ @WithClasses(ErroneousMessageByNamedWithIterableMapper.class)
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ type = ErroneousMessageByNamedWithIterableMapper.class,
+ kind = ERROR,
+ line = 16,
+ message = "Qualifier error. No method found annotated with @Named#value: [ SelectMe ]. " +
+ "See https://mapstruct.org/faq/#qualifier for more info."),
+ @Diagnostic(
+ type = ErroneousMessageByNamedWithIterableMapper.class,
+ kind = ERROR,
+ line = 16,
+ messageRegExp = "Can't map property.*"),
+
+ }
+ )
+ public void testNoQualifyingMethodByNamedForForgedIterableFound() {
+ }
+
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Cities.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Cities.java
new file mode 100644
index 0000000000..828750399a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Cities.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.selection.qualifier.iterable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.Qualifier;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Qualifier
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Cities {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/IterableAndQualifiersTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/IterableAndQualifiersTest.java
index db8de9496e..c2465b255e 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/IterableAndQualifiersTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/IterableAndQualifiersTest.java
@@ -15,6 +15,7 @@
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+import org.mapstruct.factory.Mappers;
/**
*
@@ -22,20 +23,22 @@
*/
@IssueKey( "707" )
@WithClasses( {
+ Cities.class,
CityDto.class,
CityEntity.class,
RiverDto.class,
RiverEntity.class,
+ Rivers.class,
TopologyDto.class,
TopologyEntity.class,
TopologyFeatureDto.class,
TopologyFeatureEntity.class,
- TopologyMapper.class
} )
@RunWith( AnnotationProcessorTestRunner.class )
public class IterableAndQualifiersTest {
@Test
+ @WithClasses(TopologyMapper.class)
public void testGenerationBasedOnQualifier() {
TopologyDto topologyDto1 = new TopologyDto();
@@ -67,4 +70,39 @@ public void testGenerationBasedOnQualifier() {
assertThat( ( (CityEntity) result2.getTopologyFeatures().get( 0 ) ).getPopulation() ).isEqualTo( 800000 );
}
+
+ @Test
+ @WithClasses(TopologyWithoutIterableMappingMapper.class)
+ public void testIterableGeneratorBasedOnQualifier() {
+
+ TopologyWithoutIterableMappingMapper mapper = Mappers.getMapper( TopologyWithoutIterableMappingMapper.class );
+
+ TopologyDto riverTopologyDto = new TopologyDto();
+ List topologyFeatures1 = new ArrayList<>();
+ RiverDto riverDto = new RiverDto();
+ riverDto.setName( "Rhine" );
+ riverDto.setLength( 5 );
+ topologyFeatures1.add( riverDto );
+ riverTopologyDto.setTopologyFeatures( topologyFeatures1 );
+
+ TopologyEntity riverTopology = mapper.mapTopologyAsRiver( riverTopologyDto );
+ assertThat( riverTopology.getTopologyFeatures() ).hasSize( 1 );
+ assertThat( riverTopology.getTopologyFeatures().get( 0 ).getName() ).isEqualTo( "Rhine" );
+ assertThat( riverTopology.getTopologyFeatures().get( 0 ) ).isInstanceOf( RiverEntity.class );
+ assertThat( ( (RiverEntity) riverTopology.getTopologyFeatures().get( 0 ) ).getLength() ).isEqualTo( 5 );
+
+ TopologyDto cityTopologyDto = new TopologyDto();
+ List topologyFeatures2 = new ArrayList<>();
+ CityDto cityDto = new CityDto();
+ cityDto.setName( "Amsterdam" );
+ cityDto.setPopulation( 800000 );
+ topologyFeatures2.add( cityDto );
+ cityTopologyDto.setTopologyFeatures( topologyFeatures2 );
+
+ TopologyEntity cityTopology = mapper.mapTopologyAsCity( cityTopologyDto );
+ assertThat( cityTopology.getTopologyFeatures() ).hasSize( 1 );
+ assertThat( cityTopology.getTopologyFeatures().get( 0 ).getName() ).isEqualTo( "Amsterdam" );
+ assertThat( cityTopology.getTopologyFeatures().get( 0 ) ).isInstanceOf( CityEntity.class );
+ assertThat( ( (CityEntity) cityTopology.getTopologyFeatures().get( 0 ) ).getPopulation() ).isEqualTo( 800000 );
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Rivers.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Rivers.java
new file mode 100644
index 0000000000..f6a56951a1
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/Rivers.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.selection.qualifier.iterable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.Qualifier;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Qualifier
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Rivers {
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyMapper.java
index 22e87dd084..84c4bc58a1 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyMapper.java
@@ -5,16 +5,11 @@
*/
package org.mapstruct.ap.test.selection.qualifier.iterable;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.util.List;
import org.mapstruct.IterableMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
-import org.mapstruct.Qualifier;
import org.mapstruct.factory.Mappers;
/**
@@ -64,16 +59,4 @@ protected TopologyFeatureEntity mapCity( TopologyFeatureDto dto ) {
return topologyFeatureEntity;
}
- @Qualifier
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.CLASS)
- public @interface Rivers {
- }
-
- @Qualifier
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.CLASS)
- public @interface Cities {
- }
-
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyWithoutIterableMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyWithoutIterableMappingMapper.java
new file mode 100644
index 0000000000..4b72b34663
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/iterable/TopologyWithoutIterableMappingMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.selection.qualifier.iterable;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface TopologyWithoutIterableMappingMapper {
+
+ @Mapping( target = "topologyFeatures", qualifiedBy = Rivers.class )
+ TopologyEntity mapTopologyAsRiver(TopologyDto dto);
+
+ @Mapping( target = "topologyFeatures", qualifiedBy = Cities.class )
+ TopologyEntity mapTopologyAsCity(TopologyDto dto);
+
+ @Rivers
+ default TopologyFeatureEntity mapRiver( TopologyFeatureDto dto ) {
+ TopologyFeatureEntity topologyFeatureEntity = null;
+ if ( dto instanceof RiverDto ) {
+ RiverEntity riverEntity = new RiverEntity();
+ riverEntity.setLength( ( (RiverDto) dto ).getLength() );
+ topologyFeatureEntity = riverEntity;
+ topologyFeatureEntity.setName( dto.getName() );
+ }
+ return topologyFeatureEntity;
+ }
+
+ @Cities
+ default TopologyFeatureEntity mapCity( TopologyFeatureDto dto ) {
+ TopologyFeatureEntity topologyFeatureEntity = null;
+ if ( dto instanceof CityDto ) {
+ CityEntity cityEntity = new CityEntity();
+ cityEntity.setPopulation( ( (CityDto) dto ).getPopulation() );
+ topologyFeatureEntity = cityEntity;
+ topologyFeatureEntity.setName( dto.getName() );
+ }
+ return topologyFeatureEntity;
+ }
+
+}
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
new file mode 100644
index 0000000000..c3ecc02911
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.bugs._2245;
+
+import javax.annotation.Generated;
+
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2020-10-24T17:57:23+0200",
+ comments = "version: , compiler: javac, environment: Java 1.8.0_202 (AdoptOpenJdk)"
+)
+public class TestMapperImpl implements TestMapper {
+
+ @Override
+ public Tenant map(TenantDTO tenant) {
+ if ( tenant == null ) {
+ return null;
+ }
+
+ Tenant tenant1 = new Tenant();
+
+ String id = tenantInnerId( tenant );
+ if ( id != null ) {
+ tenant1.setId( id );
+ }
+ else {
+ tenant1.setId( "test" );
+ }
+
+ return tenant1;
+ }
+
+ 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;
+ }
+}
diff --git a/readme.md b/readme.md
index c39a408ac2..45eeda27e6 100644
--- a/readme.md
+++ b/readme.md
@@ -109,14 +109,9 @@ For Gradle, you need something along the following lines:
```groovy
plugins {
...
- id 'net.ltgt.apt' version '0.15'
+ id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}
-// You can integrate with your IDEs.
-// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
-apply plugin: 'net.ltgt.apt-idea'
-apply plugin: 'net.ltgt.apt-eclipse'
-
dependencies {
...
compile 'org.mapstruct:mapstruct:1.4.1.Final'