diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml
index 945015d5c..a4591c39d 100644
--- a/build-config/src/main/resources/build-config/checkstyle.xml
+++ b/build-config/src/main/resources/build-config/checkstyle.xml
@@ -122,9 +122,6 @@
-
-
-
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 5255ac6f1..ad5d8111f 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -528,3 +528,80 @@ Otherwise, you would need to write a custom `BuilderProvider`
====
In case you want to disable using builders then you can use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content.
====
+
+[[mapping-with-constructors]]
+=== Using Constructors
+
+MapStruct supports using constructors for mapping target types.
+When doing a mapping MapStruct checks if there is a builder for the type being mapped.
+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 parameterless constructor exists then it would be used to construct the object, and the other constructors will be ignored
+* If there are multiple constructors then the one annotated with annotation named `@Default` (from any package) will be used
+
+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.
+
+[NOTE]
+====
+When an object factory method or a method annotated with `@ObjectFactory` exists, it will take precedence over any constructor defined in the target.
+The target object constructor will not be used in that case.
+====
+
+
+.Person with constructor parameters
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+public class Person {
+
+ private final String name;
+ private final String surname;
+
+ public Person(String name, String surname) {
+ this.name = name;
+ this.surname = surname;
+ }
+}
+----
+====
+
+.Person With Constructor Mapper definition
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+public interface PersonMapper {
+
+ Person map(PersonDto dto);
+}
+----
+====
+
+.Generated mapper with constructor
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class PersonMapperImpl implements PersonMapper {
+
+ public Person map(PersonDto dto) {
+ if (dto == null) {
+ return null;
+ }
+
+ String name;
+ String surname;
+ name = dto.getName();
+ surname = dto.getSurname();
+
+ Person person = new Person( name, surname );
+
+ return person;
+ }
+}
+----
+====
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 7a22aad55..725c69bad 100644
--- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
@@ -174,6 +174,12 @@ During the generation of automatic sub-mapping methods <>
Follow issue https://github.com/mapstruct/mapstruct/issues/1086[#1086] for more information.
====
+[NOTE]
+====
+Constructor properties of the target object are also considered as target properties.
+You can read more about that in <>
+====
+
[[controlling-nested-bean-mappings]]
=== Controlling nested bean mappings
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 0789e3051..76a72b4b7 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
@@ -107,6 +107,14 @@ public class MavenIntegrationTest {
void recordsTest() {
}
+ @ProcessorTest(baseDir = "kotlinDataTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC
+ }, forkJvm = true)
+ // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion
+ // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907
+ void kotlinDataTest() {
+ }
+
@ProcessorTest(baseDir = "simpleTest")
void simpleTest() {
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
index f6f9186ba..839bf86f1 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
@@ -71,6 +71,9 @@ public class ProcessorInvocationInterceptor implements InvocationInterceptor {
}
else {
verifier = new Verifier( destination.getCanonicalPath() );
+ if ( processorTestContext.isForkJvm() ) {
+ verifier.setForkJvm( true );
+ }
}
List goals = new ArrayList<>( 3 );
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
index 1db77ae6b..eb8553960 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
@@ -106,4 +106,7 @@ public @interface ProcessorTest {
* @return the {@link CommandLineEnhancer} implementation. Must have a default constructor.
*/
Class extends CommandLineEnhancer> commandLineEnhancer() default CommandLineEnhancer.class;
+
+ boolean forkJvm() default false;
+
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java
index be1e4647c..feb5c7dc0 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java
@@ -13,13 +13,16 @@ public class ProcessorTestContext {
private final String baseDir;
private final ProcessorTest.ProcessorType processor;
private final Class extends ProcessorTest.CommandLineEnhancer> cliEnhancerClass;
+ private final boolean forkJvm;
public ProcessorTestContext(String baseDir,
ProcessorTest.ProcessorType processor,
- Class extends ProcessorTest.CommandLineEnhancer> cliEnhancerClass) {
+ Class extends ProcessorTest.CommandLineEnhancer> cliEnhancerClass,
+ boolean forkJvm) {
this.baseDir = baseDir;
this.processor = processor;
this.cliEnhancerClass = cliEnhancerClass;
+ this.forkJvm = forkJvm;
}
public String getBaseDir() {
@@ -33,4 +36,8 @@ public class ProcessorTestContext {
public Class extends ProcessorTest.CommandLineEnhancer> getCliEnhancerClass() {
return cliEnhancerClass;
}
+
+ public boolean isForkJvm() {
+ return forkJvm;
+ }
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java
index b7a4f331c..5ceb44804 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java
@@ -34,7 +34,8 @@ public class ProcessorTestTemplateInvocationContextProvider implements TestTempl
.map( processorType -> new ProcessorTestTemplateInvocationContext( new ProcessorTestContext(
processorTest.baseDir(),
processorType,
- processorTest.commandLineEnhancer()
+ processorTest.commandLineEnhancer(),
+ processorTest.forkJvm()
) ) );
}
}
diff --git a/integrationtest/src/test/resources/kotlinDataTest/pom.xml b/integrationtest/src/test/resources/kotlinDataTest/pom.xml
new file mode 100644
index 000000000..8a43a0419
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinDataTest/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ kotlinDataTest
+ jar
+
+
+ 1.3.70
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+
+
+
+ generate-via-compiler-plugin
+
+ false
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+
+ \${project.basedir}/src/main/kotlin
+ \${project.basedir}/src/main/java
+
+
+
+
+ test-compile
+ test-compile
+
+
+ \${project.basedir}/src/test/kotlin
+ \${project.basedir}/src/test/java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+ compile
+
+
+ java-test-compile
+ test-compile
+ testCompile
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.java b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.java
new file mode 100644
index 000000000..e992eb0e6
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.kotlin.data;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class CustomerEntity {
+
+ private String name;
+ private String mail;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getMail() {
+ return mail;
+ }
+
+ public void setMail(String mail) {
+ this.mail = mail;
+ }
+}
diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.java b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.java
new file mode 100644
index 000000000..b2157252d
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.kotlin.data;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface CustomerMapper {
+
+ CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
+
+ @Mapping(target = "mail", source = "email")
+ CustomerEntity fromRecord(CustomerDto record);
+
+ @InheritInverseConfiguration
+ CustomerDto toRecord(CustomerEntity entity);
+
+}
diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt b/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt
new file mode 100644
index 000000000..820a8b56c
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.kotlin.data;
+
+data class CustomerDto(var name: String?, var email: String?) {
+
+}
diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java
new file mode 100644
index 000000000..513f080a8
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.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.itest.kotlin.data;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.mapstruct.itest.kotlin.data.CustomerDto;
+import org.mapstruct.itest.kotlin.data.CustomerEntity;
+import org.mapstruct.itest.kotlin.data.CustomerMapper;
+
+public class KotlinDataTest {
+
+ @Test
+ public void shouldMapData() {
+ CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) );
+
+ assertThat( customer ).isNotNull();
+ assertThat( customer.getName() ).isEqualTo( "Kermit" );
+ assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" );
+ }
+
+ @Test
+ public void shouldMapIntoData() {
+ CustomerEntity entity = new CustomerEntity();
+ entity.setName( "Kermit" );
+ entity.setMail( "kermit@test.com" );
+
+ CustomerDto customer = CustomerMapper.INSTANCE.toRecord( entity );
+
+ assertThat( customer ).isNotNull();
+ assertThat( customer.getName() ).isEqualTo( "Kermit" );
+ assertThat( customer.getEmail() ).isEqualTo( "kermit@test.com" );
+ }
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java
index 0e8a0d256..a3c7037f0 100644
--- a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java
@@ -5,6 +5,7 @@
*/
package org.mapstruct.itest.records;
+import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@@ -20,4 +21,7 @@ public interface CustomerMapper {
@Mapping(target = "mail", source = "email")
CustomerEntity fromRecord(CustomerDto record);
+ @InheritInverseConfiguration
+ CustomerDto toRecord(CustomerEntity entity);
+
}
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 5a7a62f78..824a763fc 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
@@ -22,4 +22,17 @@ public class RecordsTest {
assertThat( customer.getName() ).isEqualTo( "Kermit" );
assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" );
}
+
+ @Test
+ public void shouldMapIntoRecord() {
+ CustomerEntity entity = new CustomerEntity();
+ entity.setName( "Kermit" );
+ entity.setMail( "kermit@test.com" );
+
+ CustomerDto customer = CustomerMapper.INSTANCE.toRecord( entity );
+
+ assertThat( customer ).isNotNull();
+ assertThat( customer.name() ).isEqualTo( "Kermit" );
+ assertThat( customer.email() ).isEqualTo( "kermit@test.com" );
+ }
}
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 53a271858..ea4f3fbde 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
@@ -8,18 +8,26 @@ package org.mapstruct.ap.internal.model;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
@@ -33,6 +41,7 @@ import org.mapstruct.ap.internal.model.beanmapping.SourceReference;
import org.mapstruct.ap.internal.model.beanmapping.TargetReference;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Parameter;
+import org.mapstruct.ap.internal.model.common.ParameterBinding;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder;
@@ -41,15 +50,20 @@ import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod;
+import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
+import org.mapstruct.ap.internal.util.accessor.AccessorType;
+import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor;
import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_NOT_ASSIGNABLE;
import static org.mapstruct.ap.internal.util.Message.GENERAL_ABSTRACT_RETURN_TYPE;
+import static org.mapstruct.ap.internal.util.Message.GENERAL_AMBIGIOUS_CONSTRUCTORS;
+import static org.mapstruct.ap.internal.util.Message.GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS;
/**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally
@@ -61,7 +75,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List propertyMappings;
private final Map> mappingsByParameter;
+ private final Map> constructorMappingsByParameter;
private final List constantMappings;
+ private final List constructorConstantMappings;
private final Type returnTypeToConstruct;
private final BuilderType returnTypeBuilder;
private final MethodReference finalizerMethod;
@@ -73,6 +89,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
/* returnType to construct can have a builder */
private BuilderType returnTypeBuilder;
+ private Map unprocessedConstructorProperties;
private Map unprocessedTargetProperties;
private Map unprocessedSourceProperties;
private Set targetProperties;
@@ -82,6 +99,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Map> unprocessedDefinedTargets = new LinkedHashMap<>();
private MappingReferences mappingReferences;
+ private MethodReference factoryMethod;
+ private boolean hasFactoryMethod;
public Builder mappingContext(MappingBuilderContext mappingContext) {
this.ctx = mappingContext;
@@ -125,15 +144,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
/* 3) or the builder whenever the return type is immutable */
Type returnTypeToConstruct = null;
- /* factory or builder method to construct the returnTypeToConstruct */
- MethodReference factoryMethod = null;
-
// determine which return type to construct
boolean cannotConstructReturnType = false;
if ( !method.getReturnType().isVoid() ) {
Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters );
if ( returnTypeImpl != null ) {
- factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
+ initializeFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
@@ -143,7 +159,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
else if ( isBuilderRequired() ) {
returnTypeImpl = returnTypeBuilder.getBuilder();
- factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
+ initializeFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
@@ -153,7 +169,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
else if ( !method.isUpdateMethod() ) {
returnTypeImpl = method.getReturnType();
- factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
+ initializeFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
@@ -171,13 +187,39 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
/* the type that needs to be used in the mapping process as target */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
+ existingVariableNames.addAll( method.getParameterNames() );
+
CollectionMappingStrategyGem cms = this.method.getOptions().getMapper().getCollectionMappingStrategy();
// determine accessors
Map accessors = resultTypeToMap.getPropertyWriteAccessors( cms );
- this.targetProperties = accessors.keySet();
+ this.targetProperties = new LinkedHashSet<>( accessors.keySet() );
this.unprocessedTargetProperties = new LinkedHashMap<>( accessors );
+
+ if ( !method.isUpdateMethod() && !hasFactoryMethod ) {
+ ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap );
+ if ( constructorAccessor != null ) {
+
+ this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors;
+
+ factoryMethod = MethodReference.forConstructorInvocation(
+ resultTypeToMap,
+ constructorAccessor.parameterBindings
+ );
+
+ }
+ else {
+ this.unprocessedConstructorProperties = new LinkedHashMap<>();
+ }
+
+ this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() );
+ this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties );
+ }
+ else {
+ unprocessedConstructorProperties = new LinkedHashMap<>();
+ }
+
this.unprocessedSourceProperties = new LinkedHashMap<>();
for ( Parameter sourceParameter : method.getSourceParameters() ) {
unprocessedSourceParameters.add( sourceParameter );
@@ -191,7 +233,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
unprocessedSourceProperties.put( entry.getKey(), entry.getValue() );
}
}
- existingVariableNames.addAll( method.getParameterNames() );
// get bean mapping (when specified as annotation )
if ( beanMapping != null ) {
@@ -223,6 +264,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// Process the unprocessed defined targets
handleUnprocessedDefinedTargets();
+ // Initialize unprocessed constructor properties
+ handleUnmappedConstructorProperties();
+
// report errors on unmapped properties
reportErrorForUnmappedTargetPropertiesIfRequired();
reportErrorForUnmappedSourcePropertiesIfRequired();
@@ -364,6 +408,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
if ( propertyMapping != null ) {
unprocessedTargetProperties.remove( propertyName );
+ unprocessedConstructorProperties.remove( propertyName );
unprocessedSourceProperties.remove( propertyName );
iterator.remove();
propertyMappings.add( propertyMapping );
@@ -374,6 +419,28 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
+ private void handleUnmappedConstructorProperties() {
+ for ( Entry entry : unprocessedConstructorProperties.entrySet() ) {
+ Accessor accessor = entry.getValue();
+ Type accessedType = ctx.getTypeFactory()
+ .getType( accessor.getAccessedType() );
+ String targetPropertyName = entry.getKey();
+
+ propertyMappings.add( new JavaExpressionMappingBuilder()
+ .mappingContext( ctx )
+ .sourceMethod( method )
+ .javaExpression( accessedType.getNull() )
+ .existingVariableNames( existingVariableNames )
+ .target( targetPropertyName, null, accessor )
+ .dependsOn( Collections.emptySet() )
+ .mirror( null )
+ .build()
+ );
+ }
+
+ unprocessedConstructorProperties.clear();
+ }
+
/**
* Sources the given mappings as per the dependency relationships given via {@code dependsOn()}. If a cycle is
* detected, an error is reported.
@@ -434,7 +501,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
error = false;
}
- else if ( !resultType.hasEmptyAccessibleConstructor() ) {
+ else if ( !resultType.hasAccessibleConstructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
method.getOptions().getBeanMapping().getMirror(),
@@ -456,7 +523,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
error = false;
}
- else if ( !returnType.hasEmptyAccessibleConstructor() ) {
+ else if ( !returnType.hasAccessibleConstructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
@@ -470,20 +537,225 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
/**
* Find a factory method for a return type or for a builder.
* @param returnTypeImpl the return type implementation to construct
- * @param selectionParameters
+ * @param @selectionParameters
* @return
*/
- private MethodReference getFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
- MethodReference factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method,
- returnTypeImpl,
- selectionParameters,
- ctx
- );
- if ( factoryMethod == null && returnTypeBuilder != null ) {
- factoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( method, returnTypeBuilder );
+ private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
+ List> matchingFactoryMethods =
+ ObjectFactoryMethodResolver.getMatchingFactoryMethods(
+ method,
+ returnTypeImpl,
+ selectionParameters,
+ ctx
+ );
+
+ if ( matchingFactoryMethods.isEmpty() ) {
+ if ( factoryMethod == null && returnTypeBuilder != null ) {
+ factoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( method, returnTypeBuilder );
+ hasFactoryMethod = factoryMethod != null;
+ }
+ }
+ else if ( matchingFactoryMethods.size() == 1 ) {
+ factoryMethod = ObjectFactoryMethodResolver.getFactoryMethodReference(
+ method,
+ first( matchingFactoryMethods ),
+ ctx
+ );
+ hasFactoryMethod = true;
+ }
+ else {
+ ctx.getMessager().printMessage(
+ method.getExecutable(),
+ Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
+ returnTypeImpl,
+ Strings.join( matchingFactoryMethods, ", " )
+ );
+ hasFactoryMethod = true;
+ }
+ }
+
+ private ConstructorAccessor getConstructorAccessor(Type type) {
+
+ if ( type.isRecord() ) {
+ // If the type is a record then just get the record components and use then
+ List recordComponents = type.getRecordComponents();
+ List parameterBindings = new ArrayList<>( recordComponents.size() );
+ Map constructorAccessors = new LinkedHashMap<>();
+ for ( Element recordComponent : recordComponents ) {
+ String parameterName = recordComponent.getSimpleName().toString();
+ Accessor accessor = createConstructorAccessor( recordComponent, parameterName );
+ constructorAccessors.put(
+ parameterName,
+ accessor
+ );
+
+ parameterBindings.add( ParameterBinding.fromTypeAndName(
+ ctx.getTypeFactory().getType( recordComponent.asType() ),
+ accessor.getSimpleName()
+ ) );
+ }
+ return new ConstructorAccessor( parameterBindings, constructorAccessors );
}
- return factoryMethod;
+ List constructors = ElementFilter.constructorsIn( type.getTypeElement()
+ .getEnclosedElements() );
+
+ ExecutableElement defaultConstructor = null;
+ List accessibleConstructors = new ArrayList<>( constructors.size() );
+
+ for ( ExecutableElement constructor : constructors ) {
+ if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) {
+ continue;
+ }
+
+ if ( constructor.getParameters().isEmpty() ) {
+ defaultConstructor = constructor;
+ }
+ else {
+ accessibleConstructors.add( constructor );
+ }
+ }
+
+ if ( defaultConstructor != null ) {
+ return null;
+ }
+
+ if ( accessibleConstructors.isEmpty() ) {
+ return null;
+ }
+
+ ExecutableElement constructor = null;
+ if ( accessibleConstructors.size() > 1 ) {
+
+ for ( ExecutableElement accessibleConstructor : accessibleConstructors ) {
+ for ( AnnotationMirror annotationMirror : accessibleConstructor.getAnnotationMirrors() ) {
+ if ( annotationMirror.getAnnotationType()
+ .asElement()
+ .getSimpleName()
+ .contentEquals( "Default" ) ) {
+ constructor = accessibleConstructor;
+ break;
+ }
+ }
+ }
+
+ if ( constructor == null ) {
+ ctx.getMessager().printMessage(
+ method.getExecutable(),
+ GENERAL_AMBIGIOUS_CONSTRUCTORS,
+ type,
+ Strings.join( constructors, ", " )
+ );
+ return null;
+ }
+ }
+ else {
+ constructor = accessibleConstructors.get( 0 );
+ }
+
+ List constructorParameters = ctx.getTypeFactory()
+ .getParameters( (DeclaredType) type.getTypeMirror(), constructor );
+
+
+ List constructorProperties = null;
+ for ( AnnotationMirror annotationMirror : constructor.getAnnotationMirrors() ) {
+ if ( annotationMirror.getAnnotationType()
+ .asElement()
+ .getSimpleName()
+ .contentEquals( "ConstructorProperties" ) ) {
+ for ( Entry extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
+ .getElementValues()
+ .entrySet() ) {
+ if ( entry.getKey().getSimpleName().contentEquals( "value" ) ) {
+ constructorProperties = getArrayValues( entry.getValue() );
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if ( constructorProperties == null ) {
+ Map constructorAccessors = new LinkedHashMap<>();
+ List parameterBindings = new ArrayList<>( constructorParameters.size() );
+ for ( Parameter constructorParameter : constructorParameters ) {
+ String parameterName = constructorParameter.getName();
+ Element parameterElement = constructorParameter.getElement();
+ Accessor constructorAccessor = createConstructorAccessor( parameterElement, parameterName );
+ constructorAccessors.put(
+ parameterName,
+ constructorAccessor
+ );
+ parameterBindings.add( ParameterBinding.fromTypeAndName(
+ constructorParameter.getType(),
+ constructorAccessor.getSimpleName()
+ ) );
+ }
+
+ return new ConstructorAccessor( parameterBindings, constructorAccessors );
+ }
+ else if ( constructorProperties.size() != constructorParameters.size() ) {
+ ctx.getMessager().printMessage(
+ method.getExecutable(),
+ GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS,
+ type
+ );
+ return null;
+ }
+ else {
+ Map constructorAccessors = new LinkedHashMap<>();
+ List parameterBindings = new ArrayList<>( constructorProperties.size() );
+ for ( int i = 0; i < constructorProperties.size(); i++ ) {
+ String parameterName = constructorProperties.get( i );
+ Parameter constructorParameter = constructorParameters.get( i );
+ Element parameterElement = constructorParameter.getElement();
+ Accessor constructorAccessor = createConstructorAccessor( parameterElement, parameterName );
+ constructorAccessors.put(
+ parameterName,
+ constructorAccessor
+ );
+ parameterBindings.add( ParameterBinding.fromTypeAndName(
+ constructorParameter.getType(),
+ constructorAccessor.getSimpleName()
+ ) );
+ }
+
+ return new ConstructorAccessor( parameterBindings, constructorAccessors );
+ }
+ }
+
+ private Accessor createConstructorAccessor(Element element, String parameterName) {
+ String safeParameterName = Strings.getSafeVariableName(
+ parameterName,
+ existingVariableNames
+ );
+ existingVariableNames.add( safeParameterName );
+ return new ParameterElementAccessor( element, safeParameterName );
+ }
+
+ private List getArrayValues(AnnotationValue av) {
+
+ if ( av.getValue() instanceof List ) {
+ List result = new ArrayList<>();
+ for ( AnnotationValue v : getValueAsList( av ) ) {
+ Object value = v.getValue();
+ if ( value instanceof String ) {
+ result.add( (String) value );
+ }
+ else {
+ return null;
+ }
+ }
+ return result;
+ }
+ else {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List getValueAsList(AnnotationValue av) {
+ return (List) av.getValue();
}
/**
@@ -531,6 +803,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// remove the remaining name based properties
for ( String handledTarget : handledTargets ) {
unprocessedTargetProperties.remove( handledTarget );
+ unprocessedConstructorProperties.remove( handledTarget );
unprocessedDefinedTargets.remove( handledTarget );
}
@@ -672,7 +945,22 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// check the mapping options
// its an ignored property mapping
if ( mapping.isIgnored() ) {
- propertyMapping = null;
+ if ( targetWriteAccessor != null && targetWriteAccessor.getAccessorType() == AccessorType.PARAMETER ) {
+ // Even though the property is ignored this is a constructor parameter.
+ // Therefore we have to initialize it
+ Type accessedType = ctx.getTypeFactory()
+ .getType( targetWriteAccessor.getAccessedType() );
+
+ propertyMapping = new JavaExpressionMappingBuilder()
+ .mappingContext( ctx )
+ .sourceMethod( method )
+ .javaExpression( accessedType.getNull() )
+ .existingVariableNames( existingVariableNames )
+ .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
+ .dependsOn( mapping.getDependsOn() )
+ .mirror( mapping.getMirror() )
+ .build();
+ }
handledTargets.add( targetPropertyName );
}
@@ -821,6 +1109,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
String targetPropertyName = sourceRef.getDeepestPropertyName();
Accessor targetPropertyWriteAccessor = unprocessedTargetProperties.remove( targetPropertyName );
+ unprocessedConstructorProperties.remove( targetPropertyName );
if ( targetPropertyWriteAccessor == null ) {
// TODO improve error message
ctx.getMessager()
@@ -1056,6 +1345,18 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
+ private static class ConstructorAccessor {
+ private final List parameterBindings;
+ private final Map constructorAccessors;
+
+ private ConstructorAccessor(
+ List parameterBindings,
+ Map constructorAccessors) {
+ this.parameterBindings = parameterBindings;
+ this.constructorAccessors = constructorAccessors;
+ }
+ }
+
private BeanMappingMethod(Method method,
Collection existingVariableNames,
List propertyMappings,
@@ -1082,15 +1383,30 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// intialize 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 );
- for ( Parameter sourceParameter : getSourceParameters() ) {
- ArrayList mappingsOfParameter = new ArrayList<>();
- mappingsByParameter.put( sourceParameter.getName(), mappingsOfParameter );
- for ( PropertyMapping mapping : propertyMappings ) {
- if ( sourceParameter.getName().equals( mapping.getSourceBeanName() ) ) {
- mappingsOfParameter.add( mapping );
- constantMappings.remove( mapping );
+ this.constantMappings = new ArrayList<>( propertyMappings.size() );
+ this.constructorMappingsByParameter = new LinkedHashMap<>();
+ this.constructorConstantMappings = new ArrayList<>();
+ Set sourceParameterNames = getSourceParameters().stream()
+ .map( Parameter::getName )
+ .collect( Collectors.toSet() );
+ for ( PropertyMapping mapping : propertyMappings ) {
+ if ( mapping.isConstructorMapping() ) {
+ if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) {
+ constructorMappingsByParameter.computeIfAbsent(
+ mapping.getSourceBeanName(),
+ key -> new ArrayList<>()
+ ).add( mapping );
}
+ else {
+ constructorConstantMappings.add( mapping );
+ }
+ }
+ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) {
+ mappingsByParameter.computeIfAbsent( mapping.getSourceBeanName(), key -> new ArrayList<>() )
+ .add( mapping );
+ }
+ else {
+ constantMappings.add( mapping );
}
}
this.returnTypeToConstruct = returnTypeToConstruct;
@@ -1100,15 +1416,28 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return constantMappings;
}
+ public List getConstructorConstantMappings() {
+ return constructorConstantMappings;
+ }
+
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.get( parameter.getName() );
+ return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() );
+ }
+
+ public List constructorPropertyMappingsByParameter(Parameter parameter) {
+ // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value
+ return constructorMappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() );
}
public Type getReturnTypeToConstruct() {
return returnTypeToConstruct;
}
+ public boolean hasConstructorMappings() {
+ return !constructorMappingsByParameter.isEmpty() || !constructorConstantMappings.isEmpty();
+ }
+
public MethodReference getFinalizerMethod() {
return finalizerMethod;
}
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 04331c4e1..7ab9814e2 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
@@ -130,7 +130,7 @@ public class CollectionAssignmentBuilder {
CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy();
boolean targetImmutable = cms == CollectionMappingStrategyGem.TARGET_IMMUTABLE || targetReadAccessor == null;
- if ( targetAccessorType == AccessorType.SETTER || targetAccessorType == AccessorType.FIELD ) {
+ if ( targetAccessorType == AccessorType.SETTER || targetAccessorType.isFieldAssignment() ) {
if ( result.isCallingUpdateMethod() && !targetImmutable ) {
@@ -149,7 +149,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
factoryMethod,
- targetAccessorType == AccessorType.FIELD,
+ targetAccessorType.isFieldAssignment(),
targetType,
true,
nvpms == SET_TO_NULL && !targetType.isPrimitive(),
@@ -165,7 +165,7 @@ public class CollectionAssignmentBuilder {
nvcs,
nvpms,
ctx.getTypeFactory(),
- targetAccessorType == AccessorType.FIELD
+ targetAccessorType.isFieldAssignment()
);
}
else if ( result.getType() == Assignment.AssignmentType.DIRECT ||
@@ -176,16 +176,18 @@ public class CollectionAssignmentBuilder {
method.getThrownTypes(),
targetType,
ctx.getTypeFactory(),
- targetAccessorType == AccessorType.FIELD
+ targetAccessorType.isFieldAssignment()
);
}
else {
+ //TODO init default value
+
// target accessor is setter, so wrap the setter in setter map/ collection handling
result = new SetterWrapperForCollectionsAndMaps(
result,
method.getThrownTypes(),
targetType,
- targetAccessorType == AccessorType.FIELD
+ targetAccessorType.isFieldAssignment()
);
}
}
@@ -203,7 +205,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
targetType,
- targetAccessorType == AccessorType.FIELD
+ targetAccessorType.isFieldAssignment()
);
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java
index dc2bf5218..58c4ba890 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java
@@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -57,6 +58,7 @@ public class MethodReference extends ModelElement implements Assignment {
private final List parameterBindings;
private final Parameter providingParameter;
private final boolean isStatic;
+ private final boolean isConstructor;
/**
* Creates a new reference to the given method.
@@ -91,6 +93,7 @@ public class MethodReference extends ModelElement implements Assignment {
this.definingType = method.getDefiningType();
this.isStatic = method.isStatic();
this.name = method.getName();
+ this.isConstructor = false;
}
private MethodReference(BuiltInMethod method, ConversionContext contextParam) {
@@ -106,6 +109,7 @@ public class MethodReference extends ModelElement implements Assignment {
this.parameterBindings = ParameterBinding.fromParameters( method.getParameters() );
this.isStatic = method.isStatic();
this.name = method.getName();
+ this.isConstructor = false;
}
private MethodReference(String name, Type definingType, boolean isStatic) {
@@ -121,6 +125,37 @@ public class MethodReference extends ModelElement implements Assignment {
this.parameterBindings = Collections.emptyList();
this.providingParameter = null;
this.isStatic = isStatic;
+ this.isConstructor = false;
+ }
+
+ private MethodReference(Type definingType, List parameterBindings) {
+ this.name = null;
+ this.definingType = definingType;
+ this.sourceParameters = Collections.emptyList();
+ this.returnType = null;
+ this.declaringMapper = null;
+ this.thrownTypes = Collections.emptyList();
+ this.isUpdateMethod = false;
+ this.contextParam = null;
+ this.parameterBindings = parameterBindings;
+ this.providingParameter = null;
+ this.isStatic = false;
+ this.isConstructor = true;
+
+ if ( parameterBindings.isEmpty() ) {
+ this.importTypes = Collections.emptySet();
+ }
+ else {
+ Set imported = new LinkedHashSet<>();
+
+ for ( ParameterBinding binding : parameterBindings ) {
+ imported.add( binding.getType() );
+ }
+
+ imported.add( definingType );
+
+ this.importTypes = Collections.unmodifiableSet( imported );
+ }
}
public MapperReference getDeclaringMapper() {
@@ -271,6 +306,10 @@ public class MethodReference extends ModelElement implements Assignment {
return isStatic;
}
+ public boolean isConstructor() {
+ return isConstructor;
+ }
+
public List getParameterBindings() {
return parameterBindings;
}
@@ -341,6 +380,10 @@ public class MethodReference extends ModelElement implements Assignment {
return new MethodReference( methodName, null, false );
}
+ public static MethodReference forConstructorInvocation(Type type, List parameterBindings) {
+ return new MethodReference( type, parameterBindings );
+ }
+
@Override
public String toString() {
String mapper = declaringMapper != null ? declaringMapper.getType().getName() : "";
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java
index 4687a626d..fcd8d70d5 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java
@@ -42,6 +42,9 @@ public abstract class NormalTypeMappingMethod extends MappingMethod {
types.addAll( getReturnType().getImplementationType().getImportTypes() );
}
}
+ else if ( factoryMethod != null ) {
+ types.addAll( factoryMethod.getImportTypes() );
+ }
return types;
}
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 f4b39ab1a..34d294427 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
@@ -67,16 +67,12 @@ public class ObjectFactoryMethodResolver {
MappingBuilderContext ctx) {
- MethodSelectors selectors =
- new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
-
- List> matchingFactoryMethods =
- selectors.getMatchingMethods(
- method,
- getAllAvailableMethods( method, ctx.getSourceModel() ),
- java.util.Collections.emptyList(),
- alternativeTarget,
- SelectionCriteria.forFactoryMethods( selectionParameters ) );
+ List> matchingFactoryMethods = getMatchingFactoryMethods(
+ method,
+ alternativeTarget,
+ selectionParameters,
+ ctx
+ );
if (matchingFactoryMethods.isEmpty()) {
return null;
@@ -94,6 +90,11 @@ public class ObjectFactoryMethodResolver {
SelectedMethod matchingFactoryMethod = first( matchingFactoryMethods );
+ return getFactoryMethodReference( method, matchingFactoryMethod, ctx );
+ }
+
+ public static MethodReference getFactoryMethodReference(Method method,
+ SelectedMethod matchingFactoryMethod, MappingBuilderContext ctx) {
Parameter providingParameter =
method.getContextProvidedMethods().getParameterForProvidedMethod( matchingFactoryMethod.getMethod() );
@@ -115,6 +116,22 @@ public class ObjectFactoryMethodResolver {
}
}
+ public static List> getMatchingFactoryMethods( Method method,
+ Type alternativeTarget,
+ SelectionParameters selectionParameters,
+ MappingBuilderContext ctx) {
+
+ MethodSelectors selectors =
+ new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
+
+ return selectors.getMatchingMethods(
+ method,
+ getAllAvailableMethods( method, ctx.getSourceModel() ),
+ java.util.Collections.emptyList(),
+ alternativeTarget,
+ SelectionCriteria.forFactoryMethods( selectionParameters ) );
+ }
+
public static MethodReference getBuilderFactoryMethod(Method method, BuilderType builder ) {
if ( builder == null ) {
return null;
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java
index 1558fc7f8..571c3530f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java
@@ -70,6 +70,7 @@ public class PropertyMapping extends ModelElement {
private final Assignment assignment;
private final Set dependsOn;
private final Assignment defaultValueAssignment;
+ private final boolean constructorMapping;
@SuppressWarnings("unchecked")
private static class MappingBuilderBase> extends AbstractBaseBuilder {
@@ -126,7 +127,7 @@ public class PropertyMapping extends ModelElement {
}
protected boolean isFieldAssignment() {
- return targetWriteAccessorType == AccessorType.FIELD;
+ return targetWriteAccessorType.isFieldAssignment();
}
}
@@ -278,7 +279,8 @@ public class PropertyMapping extends ModelElement {
targetType,
assignment,
dependsOn,
- getDefaultValueAssignment( assignment )
+ getDefaultValueAssignment( assignment ),
+ targetWriteAccessorType == AccessorType.PARAMETER
);
}
@@ -376,7 +378,7 @@ public class PropertyMapping extends ModelElement {
Assignment result;
- if ( targetAccessorType == AccessorType.SETTER || targetAccessorType == AccessorType.FIELD ) {
+ if ( targetAccessorType == AccessorType.SETTER || targetAccessorType.isFieldAssignment() ) {
result = assignToPlainViaSetter( targetType, rightHandSide );
}
else {
@@ -473,6 +475,7 @@ public class PropertyMapping extends ModelElement {
private Assignment assignToArray(Type targetType, Assignment rightHandSide) {
Type arrayType = ctx.getTypeFactory().getType( Arrays.class );
+ //TODO init default value
return new ArrayCopyWrapper(
rightHandSide,
targetPropertyName,
@@ -803,7 +806,7 @@ public class PropertyMapping extends ModelElement {
if ( assignment != null ) {
if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER ||
- targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) {
+ targetWriteAccessor.getAccessorType().isFieldAssignment() ) {
// target accessor is setter, so decorate assignment as setter
if ( assignment.isCallingUpdateMethod() ) {
@@ -874,7 +877,8 @@ public class PropertyMapping extends ModelElement {
targetType,
assignment,
dependsOn,
- null
+ null,
+ targetWriteAccessorType == AccessorType.PARAMETER
);
}
@@ -919,7 +923,7 @@ public class PropertyMapping extends ModelElement {
Assignment assignment = new SourceRHS( javaExpression, null, existingVariableNames, "" );
if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER ||
- targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) {
+ targetWriteAccessor.getAccessorType().isFieldAssignment() ) {
// setter, so wrap in setter
assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() );
}
@@ -939,7 +943,8 @@ public class PropertyMapping extends ModelElement {
targetType,
assignment,
dependsOn,
- null
+ null,
+ targetWriteAccessorType == AccessorType.PARAMETER
);
}
@@ -947,18 +952,19 @@ public class PropertyMapping extends ModelElement {
// Constructor for creating mappings of constant expressions.
private PropertyMapping(String name, String targetWriteAccessorName,
- ValueProvider targetReadAccessorProvider,
- Type targetType, Assignment propertyAssignment,
- Set dependsOn, Assignment defaultValueAssignment ) {
+ ValueProvider targetReadAccessorProvider,
+ Type targetType, Assignment propertyAssignment,
+ Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) {
this( name, null, targetWriteAccessorName, targetReadAccessorProvider,
- targetType, propertyAssignment, dependsOn, defaultValueAssignment
+ targetType, propertyAssignment, dependsOn, defaultValueAssignment,
+ constructorMapping
);
}
private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName,
- ValueProvider targetReadAccessorProvider, Type targetType,
- Assignment assignment,
- Set dependsOn, Assignment defaultValueAssignment) {
+ ValueProvider targetReadAccessorProvider, Type targetType,
+ Assignment assignment,
+ Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) {
this.name = name;
this.sourceBeanName = sourceBeanName;
this.targetWriteAccessorName = targetWriteAccessorName;
@@ -968,6 +974,7 @@ public class PropertyMapping extends ModelElement {
this.assignment = assignment;
this.dependsOn = dependsOn != null ? dependsOn : Collections.emptySet();
this.defaultValueAssignment = defaultValueAssignment;
+ this.constructorMapping = constructorMapping;
}
/**
@@ -1001,6 +1008,10 @@ public class PropertyMapping extends ModelElement {
return defaultValueAssignment;
}
+ public boolean isConstructorMapping() {
+ return constructorMapping;
+ }
+
@Override
public Set getImportTypes() {
if ( defaultValueAssignment == null ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java
index 958725c96..2fd2b69ee 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import org.mapstruct.ap.internal.gem.ContextGem;
@@ -23,6 +24,7 @@ import org.mapstruct.ap.internal.util.Collections;
*/
public class Parameter extends ModelElement {
+ private final Element element;
private final String name;
private final String originalName;
private final Type type;
@@ -32,8 +34,20 @@ public class Parameter extends ModelElement {
private final boolean varArgs;
+ private Parameter(Element element, Type type, boolean varArgs) {
+ this.element = element;
+ this.name = element.getSimpleName().toString();
+ this.originalName = name;
+ this.type = type;
+ this.mappingTarget = MappingTargetGem.instanceOn( element ) != null;
+ this.targetType = TargetTypeGem.instanceOn( element ) != null;
+ this.mappingContext = ContextGem.instanceOn( element ) != null;
+ this.varArgs = varArgs;
+ }
+
private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext,
boolean varArgs) {
+ this.element = null;
this.name = name;
this.originalName = name;
this.type = type;
@@ -47,6 +61,10 @@ public class Parameter extends ModelElement {
this( name, type, false, false, false, false );
}
+ public Element getElement() {
+ return element;
+ }
+
public String getName() {
return name;
}
@@ -115,11 +133,8 @@ public class Parameter extends ModelElement {
public static Parameter forElementAndType(VariableElement element, Type parameterType, boolean isVarArgs) {
return new Parameter(
- element.getSimpleName().toString(),
+ element,
parameterType,
- MappingTargetGem.instanceOn( element ) != null,
- TargetTypeGem.instanceOn( element ) != null,
- ContextGem.instanceOn( element ) != null,
isVarArgs
);
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java
index 5f807578e..36e4d3e86 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java
@@ -111,6 +111,17 @@ public class ParameterBinding {
return result;
}
+ public static ParameterBinding fromTypeAndName(Type parameterType, String parameterName) {
+ return new ParameterBinding(
+ parameterType,
+ parameterName,
+ false,
+ false,
+ false,
+ null
+ );
+ }
+
/**
* @param classTypeOf the type representing {@code Class} for the target type {@code X}
* @return a parameter binding representing a target type parameter
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 52a076270..0974fe347 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
@@ -90,14 +90,16 @@ public class Type extends ModelElement implements Comparable {
private List allMethods = null;
private List allFields = null;
+ private List recordComponents = null;
private List setters = null;
private List adders = null;
private List alternativeTargetAccessors = null;
+ private Map constructorAccessors = null;
private Type boundingBase = null;
- private Boolean hasEmptyAccessibleConstructor;
+ private Boolean hasAccessibleConstructor;
private final Filters filters;
@@ -290,6 +292,10 @@ public class Type extends ModelElement implements Comparable {
return packageName != null && packageName.startsWith( "java." );
}
+ public boolean isRecord() {
+ return typeElement.getKind().name().equals( "RECORD" );
+ }
+
/**
* Whether this type is a sub-type of {@link java.util.stream.Stream}.
*
@@ -498,7 +504,7 @@ public class Type extends ModelElement implements Comparable {
}
}
- Map recordAccessors = filters.recordsIn( typeElement );
+ Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() );
for ( Map.Entry recordEntry : recordAccessors.entrySet() ) {
modifiableGetters.putIfAbsent( recordEntry.getKey(), recordEntry.getValue() );
}
@@ -600,6 +606,14 @@ public class Type extends ModelElement implements Comparable {
return result;
}
+ public List getRecordComponents() {
+ if ( recordComponents == null ) {
+ recordComponents = filters.recordComponentsIn( typeElement );
+ }
+
+ return recordComponents;
+ }
+
private Type determinePreferredType(Accessor readAccessor) {
if ( readAccessor != null ) {
return typeFactory.getReturnType( (DeclaredType) typeMirror, readAccessor );
@@ -613,7 +627,7 @@ public class Type extends ModelElement implements Comparable {
return parameter.getType();
}
else if ( candidate.getAccessorType() == AccessorType.GETTER
- || candidate.getAccessorType() == AccessorType.FIELD ) {
+ || candidate.getAccessorType().isFieldAssignment() ) {
return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate );
}
return null;
@@ -636,11 +650,12 @@ public class Type extends ModelElement implements Comparable {
}
private String getPropertyName(Accessor accessor ) {
- if ( accessor.getAccessorType() == AccessorType.FIELD ) {
- return accessorNaming.getPropertyName( (VariableElement) accessor.getElement() );
+ Element accessorElement = accessor.getElement();
+ if ( accessorElement instanceof ExecutableElement ) {
+ return accessorNaming.getPropertyName( (ExecutableElement) accessorElement );
}
else {
- return accessorNaming.getPropertyName( (ExecutableElement) accessor.getElement() );
+ return accessor.getSimpleName();
}
}
@@ -1009,20 +1024,18 @@ public class Type extends ModelElement implements Comparable {
return boundingBase;
}
- public boolean hasEmptyAccessibleConstructor() {
-
- if ( this.hasEmptyAccessibleConstructor == null ) {
- hasEmptyAccessibleConstructor = false;
+ public boolean hasAccessibleConstructor() {
+ if ( hasAccessibleConstructor == null ) {
+ hasAccessibleConstructor = false;
List constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() );
for ( ExecutableElement constructor : constructors ) {
- if ( !constructor.getModifiers().contains( Modifier.PRIVATE )
- && constructor.getParameters().isEmpty() ) {
- hasEmptyAccessibleConstructor = true;
+ if ( !constructor.getModifiers().contains( Modifier.PRIVATE ) ) {
+ hasAccessibleConstructor = true;
break;
}
}
}
- return hasEmptyAccessibleConstructor;
+ return hasAccessibleConstructor;
}
/**
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java
index 594c2c7f7..a74c02fd7 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java
@@ -50,7 +50,6 @@ import org.mapstruct.ap.internal.util.NativeTypes;
import org.mapstruct.ap.internal.util.RoundContext;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
-import org.mapstruct.ap.internal.util.accessor.AccessorType;
import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
import org.mapstruct.ap.spi.BuilderInfo;
import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException;
@@ -360,7 +359,7 @@ public class TypeFactory {
}
public Parameter getSingleParameter(DeclaredType includingType, Accessor method) {
- if ( method.getAccessorType() == AccessorType.FIELD ) {
+ if ( method.getAccessorType().isFieldAssignment() ) {
return null;
}
ExecutableElement executable = (ExecutableElement) method.getElement();
@@ -376,11 +375,15 @@ public class TypeFactory {
public List getParameters(DeclaredType includingType, Accessor accessor) {
ExecutableElement method = (ExecutableElement) accessor.getElement();
- TypeMirror methodType = getMethodType( includingType, accessor.getElement() );
+ return getParameters( includingType, method );
+ }
+
+ public List getParameters(DeclaredType includingType, ExecutableElement method) {
+ ExecutableType methodType = getMethodType( includingType, method );
if ( method == null || methodType.getKind() != TypeKind.EXECUTABLE ) {
return new ArrayList<>();
}
- return getParameters( (ExecutableType) methodType, method );
+ return getParameters( methodType, method );
}
public List getParameters(ExecutableType methodType, ExecutableElement method) {
@@ -433,7 +436,7 @@ public class TypeFactory {
}
public List getThrownTypes(Accessor accessor) {
- if (accessor.getAccessorType() == AccessorType.FIELD) {
+ if (accessor.getAccessorType().isFieldAssignment()) {
return new ArrayList<>();
}
return extractTypes( ( (ExecutableElement) accessor.getElement() ).getThrownTypes() );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java
index f060385a0..0f17946f7 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java
@@ -7,7 +7,6 @@ package org.mapstruct.ap.internal.util;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -68,10 +67,6 @@ public final class AccessorNamingUtils {
return accessorNamingStrategy.getPropertyName( executable );
}
- public String getPropertyName(VariableElement variable) {
- return variable.getSimpleName().toString();
- }
-
/**
* @param adderMethod the adder method
*
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java
index 6981a1ebb..f4d52d709 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java
@@ -7,6 +7,7 @@ package org.mapstruct.ap.internal.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@@ -23,7 +24,7 @@ import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
-import org.mapstruct.ap.internal.util.accessor.VariableElementAccessor;
+import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.accessor.AccessorType.ADDER;
@@ -75,13 +76,25 @@ public class Filters {
.collect( Collectors.toCollection( LinkedList::new ) );
}
- public Map recordsIn(TypeElement typeElement) {
- if ( RECORD_COMPONENTS_METHOD == null || RECORD_COMPONENT_ACCESSOR_METHOD == null ) {
+ @SuppressWarnings("unchecked")
+ public List recordComponentsIn(TypeElement typeElement) {
+ if ( RECORD_COMPONENTS_METHOD == null ) {
+ return java.util.Collections.emptyList();
+ }
+
+ try {
+ return (List) RECORD_COMPONENTS_METHOD.invoke( typeElement );
+ }
+ catch ( IllegalAccessException | InvocationTargetException e ) {
+ return java.util.Collections.emptyList();
+ }
+ }
+
+ public Map recordAccessorsIn(Collection recordComponents) {
+ if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) {
return java.util.Collections.emptyMap();
}
try {
- @SuppressWarnings("unchecked")
- List recordComponents = (List) RECORD_COMPONENTS_METHOD.invoke( typeElement );
Map recordAccessors = new LinkedHashMap<>();
for ( Element recordComponent : recordComponents ) {
ExecutableElement recordExecutableElement =
@@ -109,7 +122,7 @@ public class Filters {
public List fieldsIn(List accessors) {
return accessors.stream()
.filter( Fields::isFieldAccessor )
- .map( VariableElementAccessor::new )
+ .map( FieldElementAccessor::new )
.collect( Collectors.toCollection( LinkedList::new ) );
}
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 e4b199adf..d2cb9857e 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
@@ -110,12 +110,14 @@ public enum Message {
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." ),
GENERAL_AMBIGIOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s." ),
GENERAL_AMBIGIOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s." ),
+ GENERAL_AMBIGIOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ),
+ GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ),
GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ),
GENERAL_VALID_DATE( "Given date format \"%s\" is valid.", Diagnostic.Kind.NOTE ),
GENERAL_INVALID_DATE( "Given date format \"%s\" is invalid. Message: \"%s\"." ),
GENERAL_JODA_NOT_ON_CLASSPATH( "Cannot validate Joda dateformat, no Joda on classpath. Consider adding Joda to the annotation processorpath.", Diagnostic.Kind.WARNING ),
GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ),
- GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ),
+ GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible constructor." ),
GENERAL_NO_QUALIFYING_METHOD( "No qualifying method found for qualifiers: %s and / or qualifying names: %s" ),
BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java
index 0f64c2b37..bbd73943d 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java
@@ -6,7 +6,6 @@
package org.mapstruct.ap.internal.util;
import org.mapstruct.ap.internal.util.accessor.Accessor;
-import org.mapstruct.ap.internal.util.accessor.AccessorType;
/**
* This a wrapper class which provides the value that needs to be used in the models.
@@ -46,7 +45,7 @@ public class ValueProvider {
return null;
}
String value = accessor.getSimpleName();
- if ( accessor.getAccessorType() != AccessorType.FIELD ) {
+ if ( !accessor.getAccessorType().isFieldAssignment() ) {
value += "()";
}
return new ValueProvider( value );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java
index 917d1a216..314302a14 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java
@@ -8,9 +8,8 @@ package org.mapstruct.ap.internal.util.accessor;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
/**
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java
index 9348de878..23448a9e9 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java
@@ -7,9 +7,14 @@ package org.mapstruct.ap.internal.util.accessor;
public enum AccessorType {
+ PARAMETER,
FIELD,
GETTER,
SETTER,
ADDER,
PRESENCE_CHECKER;
+
+ public boolean isFieldAssignment() {
+ return this == FIELD || this == PARAMETER;
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/VariableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java
similarity index 83%
rename from processor/src/main/java/org/mapstruct/ap/internal/util/accessor/VariableElementAccessor.java
rename to processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java
index 8b1b01dbd..517490053 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/VariableElementAccessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java
@@ -13,9 +13,9 @@ import javax.lang.model.type.TypeMirror;
*
* @author Filip Hrisafov
*/
-public class VariableElementAccessor extends AbstractAccessor {
+public class FieldElementAccessor extends AbstractAccessor {
- public VariableElementAccessor(VariableElement element) {
+ public FieldElementAccessor(VariableElement element) {
super( element );
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java
new file mode 100644
index 000000000..e1877cb0b
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.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.util.accessor;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * An {@link Accessor} that wraps a {@link VariableElement}.
+ *
+ * @author Filip Hrisafov
+ */
+public class ParameterElementAccessor extends AbstractAccessor {
+
+ protected final String name;
+
+ public ParameterElementAccessor(Element element, String name) {
+ super( element );
+ this.name = name;
+ }
+
+ @Override
+ public String getSimpleName() {
+ return name != null ? name : super.getSimpleName();
+ }
+
+ @Override
+ public TypeMirror getAccessedType() {
+ return element.asType();
+ }
+
+ @Override
+ public String toString() {
+ return element.toString();
+ }
+
+ @Override
+ public AccessorType getAccessorType() {
+ return AccessorType.PARAMETER;
+ }
+
+}
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 b8ffd0a17..45825b4fd 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
@@ -25,7 +25,62 @@
#if>
<#if !existingInstanceMapping>
- <@includeModel object=returnTypeToConstruct/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=returnTypeToConstruct/><#else>new <@includeModel object=returnTypeToConstruct/>()#if>;
+ <#if hasConstructorMappings()>
+ <#if (sourceParameters?size > 1)>
+ <#list sourceParametersExcludingPrimitives as sourceParam>
+ <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)>
+ <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
+ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName};
+ #list>
+ if ( ${sourceParam.name} != null ) {
+ <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
+ <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
+ #list>
+ }
+ else {
+ <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
+ ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null};
+ #list>
+ }
+ #if>
+ #list>
+ <#list sourcePrimitiveParameters as sourceParam>
+ <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)>
+ <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
+ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName};
+ <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
+ #list>
+ #if>
+ #list>
+ <#else>
+ <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
+ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName};
+ #list>
+ <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) {#if>
+ <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
+ <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
+ #list>
+ <#if mapNullToDefault>
+ }
+ else {
+ <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
+ ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null};
+ #list>
+ }
+ #if>
+ #if>
+ <#list constructorConstantMappings as constantMapping>
+
+ <@compress single_line=true>
+ <@includeModel object=constantMapping.targetType /> <@includeModel object=constantMapping existingInstanceMapping=existingInstanceMapping/>
+ @compress>
+ #list>
+
+
+ <@includeModel object=returnTypeToConstruct/> ${resultName} = <@includeModel object=factoryMethod targetType=returnTypeToConstruct/>;
+ <#else >
+ <@includeModel object=returnTypeToConstruct/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=returnTypeToConstruct/><#else>new <@includeModel object=returnTypeToConstruct/>()#if>;
+ #if>
#if>
<#list beforeMappingReferencesWithMappingTarget as callback>
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl
index 30a830bc5..d0ee7bef3 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl
@@ -16,6 +16,8 @@
<#-- method is referenced java8 static method in the mapper to implement (interface) -->
<#elseif static>
<@includeModel object=definingType/>.<@methodCall/>
+ <#elseif constructor>
+ new <@includeModel object=definingType/><#if (parameterBindings?size > 0)>( <@arguments/> )<#else>()#if>
<#else>
<@methodCall/>
#if>
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl
index 361fedaa5..9160a62d9 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl
@@ -11,7 +11,7 @@
<@lib.sourceLocalVarAssignment/>
<@lib.handleSourceReferenceNullCheck>
for ( <@includeModel object=adderType.typeBound/> ${sourceLoopVarName} : <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}#if> ) {
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
}
@lib.handleSourceReferenceNullCheck>
@lib.handleExceptions>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl
index edb0d3cad..2ce8736f9 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl
@@ -10,6 +10,6 @@
<@lib.handleExceptions>
<@lib.sourceLocalVarAssignment/>
<@lib.handleSourceReferenceNullCheck>
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite>Arrays.copyOf( ${sourceLocalVarName}, ${sourceLocalVarName}.length )@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite>Arrays.copyOf( ${sourceLocalVarName}, ${sourceLocalVarName}.length )@lib.handleWrite>;
@lib.handleSourceReferenceNullCheck>
@lib.handleExceptions>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl
index 613e8f6ab..c8f18b1c7 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl
@@ -10,6 +10,6 @@
<@lib.handleExceptions>
<@lib.sourceLocalVarAssignment/>
<@lib.handleSourceReferenceNullCheck>
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
@lib.handleSourceReferenceNullCheck>
@lib.handleExceptions>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl
index 122c76bc6..beb6bb1f3 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl
@@ -9,5 +9,5 @@
<#import "../macro/CommonMacros.ftl" as lib>
<@lib.sourceLocalVarAssignment/>
<@lib.handleExceptions>
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>@lib.handleWrite>;
@lib.handleExceptions>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl
index c26557af9..92e2e9d09 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl
@@ -16,8 +16,12 @@
-->
<#macro callTargetWriteAccessor>
<@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment>
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>#if>@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>#if>@lib.handleWrite>;
@lib.handleLocalVarNullCheck>
+ <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && !ext.targetBeanName?has_content>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck -->
+ ${ext.targetWriteAccessorName}<@lib.handleWrite>null@lib.handleWrite>;
+ }
+ #if>
#macro>
<#--
wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf)
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl
index 96ecd953b..de456ca77 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl
@@ -40,7 +40,7 @@
}
<#elseif setExplicitlyToDefault || setExplicitlyToNull>
else {
- ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null#if>@lib.handleWrite>;
+ <#if ext.targetBeanName?has_content>${ext.targetBeanName}.#if>${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null#if>@lib.handleWrite>;
}
#if>
#macro>
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java
index 6cf0ba401..0cf59e726 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java
@@ -65,12 +65,7 @@ public class Issue1242Test {
".lang.Class clazz), org.mapstruct.ap.test.bugs._1242" +
".TargetB org.mapstruct.ap.test.bugs._1242.TargetFactories.createTargetB(@TargetType java.lang" +
".Class clazz), org.mapstruct.ap.test.bugs._1242" +
- ".TargetB org.mapstruct.ap.test.bugs._1242.TargetFactories.createTargetB()."),
- @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class,
- kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 20,
- message = "org.mapstruct.ap.test.bugs._1242.TargetB does not have an accessible parameterless " +
- "constructor.")
+ ".TargetB org.mapstruct.ap.test.bugs._1242.TargetFactories.createTargetB().")
})
public void ambiguousMethodErrorForTwoFactoryMethodsWithSourceParam() {
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java
index 4d875017f..b4ce1447f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java
@@ -33,8 +33,7 @@ public class Issue1283Test {
@Diagnostic(type = ErroneousInverseTargetHasNoSuitableConstructorMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 22L,
- message = "org.mapstruct.ap.test.bugs._1283.Source does not have an accessible parameterless " +
- "constructor."
+ message = "org.mapstruct.ap.test.bugs._1283.Source does not have an accessible constructor."
)
}
)
@@ -49,8 +48,7 @@ public class Issue1283Test {
@Diagnostic(type = ErroneousTargetHasNoSuitableConstructorMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 18L,
- message = "org.mapstruct.ap.test.bugs._1283.Source does not have an accessible parameterless " +
- "constructor."
+ message = "org.mapstruct.ap.test.bugs._1283.Source does not have an accessible constructor."
)
}
)
diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java
index a34c8d4a5..227296ad2 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java
@@ -12,7 +12,7 @@ public class Source {
private String source;
- public Source(String source) {
+ private Source(String source) {
this.source = source;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.java
new file mode 100644
index 000000000..affbded7f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.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.constructor;
+
+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 ConstructorProperties {
+
+ String[] value();
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/Default.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/Default.java
new file mode 100644
index 000000000..96b30c02e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/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.ap.test.constructor;
+
+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/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.java
new file mode 100644
index 000000000..8fc9e7ce3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.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.constructor;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Person {
+
+ private final String name;
+ private final int age;
+ private final String job;
+ private final String city;
+ private final String address;
+ private final List children;
+
+ public Person(String name, int age, String job, String city, String address,
+ List children) {
+ this.name = name;
+ this.age = age;
+ this.job = job;
+ this.city = city;
+ this.address = address;
+ this.children = children;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getJob() {
+ return job;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java
new file mode 100644
index 000000000..0b2cba09c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class PersonDto {
+
+ private String name;
+ private int age;
+ private String job;
+ private String city;
+ private String address;
+ private List children;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getJob() {
+ return job;
+ }
+
+ public void setJob(String job) {
+ this.job = job;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void setChildren(List children) {
+ this.children = children;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java
new file mode 100644
index 000000000..8419442cf
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.constructorproperties;
+
+import java.util.List;
+
+import org.mapstruct.ap.test.constructor.ConstructorProperties;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class PersonWithConstructorProperties {
+
+ private final String name;
+ private final int age;
+ private final String job;
+ private final String city;
+ private final String address;
+ private final List children;
+
+ @ConstructorProperties({"name", "age", "job", "city", "address", "children"})
+ public PersonWithConstructorProperties(String var1, int var2, String var3, String var4, String var5,
+ List var6) {
+ this.name = var1;
+ this.age = var2;
+ this.job = var3;
+ this.city = var4;
+ this.address = var5;
+ this.children = var6;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getJob() {
+ return job;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.java
new file mode 100644
index 000000000..3d32d8b31
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.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.constructor.constructorproperties;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface SimpleConstructorPropertiesMapper {
+
+ SimpleConstructorPropertiesMapper INSTANCE = Mappers.getMapper( SimpleConstructorPropertiesMapper.class );
+
+ PersonWithConstructorProperties map(PersonDto dto);
+
+ @Mapping(target = "age", constant = "25")
+ @Mapping(target = "job", constant = "Software Developer")
+ PersonWithConstructorProperties mapWithConstants(PersonDto dto);
+
+ @Mapping(target = "age", expression = "java(25 - 5)")
+ @Mapping(target = "job", expression = "java(\"Software Developer\".toLowerCase())")
+ PersonWithConstructorProperties mapWithExpression(PersonDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java
new file mode 100644
index 000000000..b58d55f1e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.constructorproperties;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.ConstructorProperties;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ ConstructorProperties.class,
+ PersonWithConstructorProperties.class,
+ PersonDto.class,
+ SimpleConstructorPropertiesMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class SimpleConstructorPropertiesTest {
+
+ @Test
+ public void mapDefault() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.map( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 30 );
+ assertThat( target.getJob() ).isEqualTo( "Software Engineer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+
+ @Test
+ public void mapWithConstants() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.mapWithConstants( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 25 );
+ assertThat( target.getJob() ).isEqualTo( "Software Developer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+
+ @Test
+ public void mapWithExpressions() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.mapWithExpression( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 20 );
+ assertThat( target.getJob() ).isEqualTo( "software developer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.java
new file mode 100644
index 000000000..190ab818e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.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.constructor.defaultannotated;
+
+import org.mapstruct.ap.test.constructor.Default;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class PersonWithDefaultAnnotatedConstructor {
+
+ private final String name;
+ private final int age;
+
+ public PersonWithDefaultAnnotatedConstructor(String name) {
+ this( name, -1 );
+ }
+
+ @Default
+ public PersonWithDefaultAnnotatedConstructor(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.java
new file mode 100644
index 000000000..6934c34fe
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.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.constructor.defaultannotated;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface SimpleDefaultAnnotatedConstructorMapper {
+
+ SimpleDefaultAnnotatedConstructorMapper INSTANCE =
+ Mappers.getMapper( SimpleDefaultAnnotatedConstructorMapper.class );
+
+ PersonWithDefaultAnnotatedConstructor map(PersonDto dto);
+
+ @Mapping(target = "age", constant = "25")
+ PersonWithDefaultAnnotatedConstructor mapWithConstants(PersonDto dto);
+
+ @Mapping(target = "age", expression = "java(25 - 5)")
+ PersonWithDefaultAnnotatedConstructor mapWithExpression(PersonDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java
new file mode 100644
index 000000000..ac447d787
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.defaultannotated;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.Default;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ Default.class,
+ PersonWithDefaultAnnotatedConstructor.class,
+ PersonDto.class,
+ SimpleDefaultAnnotatedConstructorMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class SimpleDefaultAnnotatedConstructorTest {
+
+ @Test
+ public void mapDefault() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithDefaultAnnotatedConstructor target = SimpleDefaultAnnotatedConstructorMapper.INSTANCE.map( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 30 );
+ }
+
+ @Test
+ public void mapWithConstants() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithDefaultAnnotatedConstructor target =
+ SimpleDefaultAnnotatedConstructorMapper.INSTANCE.mapWithConstants( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 25 );
+ }
+
+ @Test
+ public void mapWithExpressions() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonWithDefaultAnnotatedConstructor target =
+ SimpleDefaultAnnotatedConstructorMapper.INSTANCE.mapWithExpression( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 20 );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.java
new file mode 100644
index 000000000..afa37ea77
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.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.constructor.erroneous;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.constructor.PersonDto;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousAmbiguousConstructorsMapper {
+
+ PersonWithMultipleConstructors map(PersonDto dto);
+
+ class PersonWithMultipleConstructors {
+
+ private final String name;
+ private final int age;
+
+ public PersonWithMultipleConstructors(String name) {
+ this( name, -1 );
+ }
+
+ public PersonWithMultipleConstructors(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.java
new file mode 100644
index 000000000..705421724
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.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.constructor.erroneous;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.ap.test.constructor.ConstructorProperties;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousConstructorPropertiesMapper {
+
+ PersonWithIncorrectConstructorProperties map(PersonDto dto);
+
+ class PersonWithIncorrectConstructorProperties {
+
+ private final String name;
+ private final int age;
+
+ @ConstructorProperties({ "name" })
+ public PersonWithIncorrectConstructorProperties(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java
new file mode 100644
index 000000000..eac6188ab
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.erroneous;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.ConstructorProperties;
+import org.mapstruct.ap.test.constructor.PersonDto;
+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
+ */
+@RunWith(AnnotationProcessorTestRunner.class)
+public class ErroneousConstructorTest {
+
+ @Test
+ @WithClasses({
+ ErroneousAmbiguousConstructorsMapper.class,
+ PersonDto.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = {
+ @Diagnostic(
+ type = ErroneousAmbiguousConstructorsMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 17,
+ message = "Ambiguous constructors found for creating org.mapstruct.ap.test.constructor.erroneous" +
+ ".ErroneousAmbiguousConstructorsMapper.PersonWithMultipleConstructors. Either declare parameterless " +
+ "constructor or annotate the default constructor with an annotation named @Default."
+ )
+ })
+ public void shouldUseMultipleConstructors() {
+ }
+
+ @Test
+ @WithClasses({
+ ConstructorProperties.class,
+ ErroneousConstructorPropertiesMapper.class,
+ PersonDto.class
+ })
+ @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = {
+ @Diagnostic(
+ type = ErroneousConstructorPropertiesMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 18,
+ message = "Incorrect @ConstructorProperties for org.mapstruct.ap.test.constructor.erroneous" +
+ ".ErroneousConstructorPropertiesMapper.PersonWithIncorrectConstructorProperties. The size of the " +
+ "@ConstructorProperties does not match the number of constructor parameters")
+ })
+ public void shouldNotCompileIfConstructorPropertiesDoesNotMatch() {
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.java
new file mode 100644
index 000000000..02a6d9af6
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.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.constructor.mixed;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ConstructorMixedWithSettersMapper {
+
+ ConstructorMixedWithSettersMapper INSTANCE = Mappers.getMapper( ConstructorMixedWithSettersMapper.class );
+
+ PersonMixed map(PersonDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.java
new file mode 100644
index 000000000..2c24e33cb
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.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.constructor.mixed;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ PersonMixed.class,
+ PersonDto.class,
+ ConstructorMixedWithSettersMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class ConstructorMixedWithSettersTest {
+
+ @Test
+ public void mapDefault() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ PersonMixed target = ConstructorMixedWithSettersMapper.INSTANCE.map( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 30 );
+ assertThat( target.getJob() ).isEqualTo( "Software Engineer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.java
new file mode 100644
index 000000000..deb79e84f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.mixed;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class PersonMixed {
+
+ private final String name;
+ private final int age;
+ private final String job;
+ private String city;
+ private String address;
+ private List children;
+
+ public PersonMixed(String name, int age, String job) {
+ this.name = name;
+ this.age = age;
+ this.job = job;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ throw new RuntimeException( "Method is here only to verify that MapStruct won't use it" );
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getJob() {
+ return job;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void setChildren(List children) {
+ this.children = children;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.java
new file mode 100644
index 000000000..02a82ba8d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.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.constructor.nestedsource;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public interface ArtistToChartEntry {
+
+ ArtistToChartEntry MAPPER = Mappers.getMapper( ArtistToChartEntry.class );
+
+ @Mappings({
+ @Mapping(target = "chartName", source = "chart.name"),
+ @Mapping(target = "songTitle", source = "song.title"),
+ @Mapping(target = "artistName", source = "song.artist.name"),
+ @Mapping(target = "recordedAt", source = "song.artist.label.studio.name"),
+ @Mapping(target = "city", source = "song.artist.label.studio.city"),
+ @Mapping(target = "position", source = "position")
+ })
+ ChartEntry map(Chart chart, Song song, Integer position);
+
+ @Mappings({
+ @Mapping(target = "chartName", ignore = true),
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true)
+ })
+ ChartEntry map(Song song);
+
+ @Mappings({
+ @Mapping(target = "chartName", source = "name"),
+ @Mapping(target = "songTitle", ignore = true),
+ @Mapping(target = "artistName", ignore = true),
+ @Mapping(target = "recordedAt", ignore = true),
+ @Mapping(target = "city", ignore = true),
+ @Mapping(target = "position", ignore = true)
+ })
+ ChartEntry map(Chart name);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.java
new file mode 100644
index 000000000..7fd9fbdb5
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.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.constructor.nestedsource;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryComposed;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryLabel;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ArtistToChartEntryComposedReverse {
+
+ public static final ArtistToChartEntryComposedReverse MAPPER =
+ Mappers.getMapper( ArtistToChartEntryComposedReverse.class );
+
+ @Mappings({
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "label", source = "artist.label"),
+ @Mapping(target = "position", ignore = true),
+ @Mapping(target = "chartName", ignore = true )
+ })
+ abstract ChartEntryComposed mapForward(Song song);
+
+ @Mappings({
+ @Mapping(target = "name", source = "name"),
+ @Mapping(target = "recordedAt", source = "studio.name"),
+ @Mapping(target = "city", source = "studio.city")
+ })
+ abstract ChartEntryLabel mapForward(Label label);
+
+ @InheritInverseConfiguration
+ @Mapping(target = "positions", ignore = true)
+ abstract Song mapReverse(ChartEntryComposed ce);
+
+ @InheritInverseConfiguration
+ abstract Label mapReverse(ChartEntryLabel label);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.java
new file mode 100644
index 000000000..c1d0d0522
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.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.constructor.nestedsource;
+
+import org.mapstruct.MapperConfig;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.ap.test.constructor.nestedsource._target.BaseChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+
+/**
+ *
+ * @author Sjaak Derksen
+ */
+@MapperConfig( unmappedTargetPolicy = ReportingPolicy.ERROR )
+public interface ArtistToChartEntryConfig {
+
+ @Mappings({
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "chartName", ignore = true )
+ })
+ BaseChartEntry mapForwardConfig( Song song );
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.java
new file mode 100644
index 000000000..193d4ea6e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.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.constructor.nestedsource;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ArtistToChartEntryReverse {
+
+ public static final ArtistToChartEntryReverse MAPPER = Mappers.getMapper( ArtistToChartEntryReverse.class );
+
+ @Mappings({
+
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true),
+ @Mapping(target = "chartName", ignore = true )
+ })
+ abstract ChartEntry mapForward(Song song);
+
+ @InheritInverseConfiguration
+ @Mapping(target = "positions", ignore = true)
+ abstract Song mapReverse(ChartEntry ce);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.java
new file mode 100644
index 000000000..7c21104a6
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.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.constructor.nestedsource;
+
+import org.mapstruct.InheritConfiguration;
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithBase;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper( config = ArtistToChartEntryConfig.class )
+public abstract class ArtistToChartEntryWithConfigReverse {
+
+ public static final ArtistToChartEntryWithConfigReverse MAPPER =
+ Mappers.getMapper( ArtistToChartEntryWithConfigReverse.class );
+
+ @InheritConfiguration
+ @Mappings({
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true)
+ })
+ abstract ChartEntryWithBase mapForward(Song song);
+
+ @InheritInverseConfiguration( name = "mapForward" )
+ @Mapping(target = "positions", ignore = true)
+ abstract Song mapReverse(ChartEntryWithBase ce);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.java
new file mode 100644
index 000000000..658383867
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ArtistToChartEntryWithFactoryReverse {
+
+ public static final ArtistToChartEntryWithFactoryReverse MAPPER
+ = Mappers.getMapper( ArtistToChartEntryWithFactoryReverse.class );
+
+ @Mappings({
+
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true),
+ @Mapping(target = "chartName", ignore = true)
+ })
+ abstract ChartEntry mapForward(Song song);
+
+ @InheritInverseConfiguration
+ @Mapping(target = "positions", ignore = true)
+ abstract Song mapReverse(ChartEntry ce);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.java
new file mode 100644
index 000000000..4d1d35fbe
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.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.constructor.nestedsource;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ArtistToChartEntryWithIgnoresReverse {
+
+ public static final ArtistToChartEntryWithIgnoresReverse MAPPER =
+ Mappers.getMapper( ArtistToChartEntryWithIgnoresReverse.class );
+
+ @Mappings({
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistName", source = "artist.name"),
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true),
+ @Mapping(target = "chartName", ignore = true)
+ })
+ abstract ChartEntry mapForward(Song song);
+
+ @InheritInverseConfiguration
+ @Mappings({
+ @Mapping(target = "positions", ignore = true),
+ @Mapping(target = "artist", ignore = true)
+ })
+ abstract Song mapReverse(ChartEntry ce);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.java
new file mode 100644
index 000000000..b6b4cac80
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithMapping;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ArtistToChartEntryWithMappingReverse {
+
+ public static final ArtistToChartEntryWithMappingReverse MAPPER =
+ Mappers.getMapper( ArtistToChartEntryWithMappingReverse.class );
+
+ @Mappings({
+ @Mapping(target = "songTitle", source = "title"),
+ @Mapping(target = "artistId", source = "artist.name"),
+ @Mapping(target = "recordedAt", source = "artist.label.studio.name"),
+ @Mapping(target = "city", source = "artist.label.studio.city"),
+ @Mapping(target = "position", ignore = true),
+ @Mapping(target = "chartName", ignore = true)
+ })
+ abstract ChartEntryWithMapping mapForward(Song song);
+
+ @InheritInverseConfiguration
+ @Mapping(target = "positions", ignore = true)
+ abstract Song mapReverse(ChartEntryWithMapping ce);
+
+ int mapArtistToArtistId(String in) {
+
+ if ( "The Beatles".equals( in ) ) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ String mapArtistIdToArtist(int in) {
+
+ if ( in == 1 ) {
+ return "The Beatles";
+ }
+ else {
+ return "Unknown";
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java
new file mode 100644
index 000000000..4fe43506a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource;
+
+import java.util.Collections;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Artist;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Studio;
+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 Sjaak Derksen
+ */
+@WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class })
+@IssueKey("73")
+@RunWith(AnnotationProcessorTestRunner.class)
+public class NestedSourcePropertiesConstructorTest {
+
+ @Rule
+ public final GeneratedSource generatedSource = new GeneratedSource();
+
+ @Test
+ @WithClasses({ ArtistToChartEntry.class })
+ public void shouldGenerateImplementationForPropertyNamesOnly() {
+ generatedSource.addComparisonToFixtureFor( ArtistToChartEntry.class );
+
+ Studio studio = new Studio( "Abbey Road", "London" );
+
+ Label label = new Label( "EMY", studio );
+
+ Artist artist = new Artist( "The Beatles", label );
+
+ Song song = new Song( artist, "A Hard Day's Night", Collections.emptyList() );
+
+ ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( song );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntry.class })
+ public void shouldGenerateImplementationForMultipleParam() {
+
+ Studio studio = new Studio( "Abbey Road", "London" );
+
+ Label label = new Label( "EMY", studio );
+
+ Artist artist = new Artist( "The Beatles", label );
+
+ Song song = new Song( artist, "A Hard Day's Night", Collections.emptyList() );
+
+ Chart chart = new Chart( "record-sales", "Billboard", null );
+
+ ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( chart, song, 1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" );
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 1 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ chartEntry = ArtistToChartEntry.MAPPER.map( null, song, 10 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 10 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ chartEntry = ArtistToChartEntry.MAPPER.map( chart, null, 5 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isNull();
+ assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" );
+ assertThat( chartEntry.getCity() ).isNull();
+ assertThat( chartEntry.getPosition() ).isEqualTo( 5 );
+ assertThat( chartEntry.getRecordedAt() ).isNull();
+ assertThat( chartEntry.getSongTitle() ).isNull();
+
+ chartEntry = ArtistToChartEntry.MAPPER.map( chart, song, null );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" );
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntry.class })
+ public void shouldPickPropertyNameOverParameterName() {
+
+ Chart chart = new Chart( "record-sales", "Billboard", null );
+
+ ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( chart );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isNull();
+ assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" );
+ assertThat( chartEntry.getCity() ).isNull();
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isNull();
+ assertThat( chartEntry.getSongTitle() ).isNull();
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java
new file mode 100644
index 000000000..231a4fcad
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource;
+
+import java.util.Collections;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.nestedsource._target.BaseChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryComposed;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryLabel;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithBase;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithMapping;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Artist;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Studio;
+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("73")
+@WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class })
+@RunWith(AnnotationProcessorTestRunner.class)
+public class ReversingNestedSourcePropertiesConstructorTest {
+
+ @Test
+ @WithClasses({ ArtistToChartEntryReverse.class })
+ public void shouldGenerateNestedReverse() {
+
+ Song song1 = prepareSong();
+
+ ChartEntry chartEntry = ArtistToChartEntryReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNotNull();
+ assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" );
+ assertThat( song2.getArtist().getLabel() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getName() ).isNull();
+ assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" );
+ assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" );
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntryWithIgnoresReverse.class })
+ public void shouldIgnoreEverytingBelowArtist() {
+
+ Song song1 = prepareSong();
+
+ ChartEntry chartEntry = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNull();
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntryWithFactoryReverse.class })
+ public void shouldGenerateNestedReverseWithFactory() {
+
+ Song song1 = prepareSong();
+
+ ChartEntry chartEntry = ArtistToChartEntryWithFactoryReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryWithFactoryReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNotNull();
+ assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" );
+ assertThat( song2.getArtist().getLabel() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getName() ).isNull();
+ assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" );
+ assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" );
+
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntryComposedReverse.class, ChartEntryComposed.class, ChartEntryLabel.class })
+ public void shouldGenerateNestedComposedReverse() {
+
+ Song song1 = prepareSong();
+
+ ChartEntryComposed chartEntry = ArtistToChartEntryComposedReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getLabel().getName() ).isEqualTo( "EMY" );
+ assertThat( chartEntry.getLabel().getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getLabel().getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryComposedReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNotNull();
+ assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" );
+ assertThat( song2.getArtist().getLabel() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getName() ).isEqualTo( "EMY" );
+ assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" );
+ assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" );
+ }
+
+ @Test
+ @WithClasses({ ArtistToChartEntryWithMappingReverse.class, ChartEntryWithMapping.class })
+ public void shouldGenerateNestedWithMappingReverse() {
+
+ Song song1 = prepareSong();
+
+ ChartEntryWithMapping chartEntry = ArtistToChartEntryWithMappingReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistId() ).isEqualTo( 1 );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryWithMappingReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNotNull();
+ assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" );
+ assertThat( song2.getArtist().getLabel() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getName() ).isNull();
+ assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" );
+ assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" );
+ }
+
+ @Test
+ @WithClasses({
+ ArtistToChartEntryWithConfigReverse.class,
+ ArtistToChartEntryConfig.class,
+ BaseChartEntry.class,
+ ChartEntryWithBase.class
+ })
+ public void shouldGenerateNestedWithConfigReverse() {
+
+ Song song1 = prepareSong();
+
+ ChartEntryWithBase chartEntry = ArtistToChartEntryWithConfigReverse.MAPPER.mapForward( song1 );
+
+ assertThat( chartEntry ).isNotNull();
+ assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" );
+ assertThat( chartEntry.getChartName() ).isNull();
+ assertThat( chartEntry.getCity() ).isEqualTo( "London" );
+ assertThat( chartEntry.getPosition() ).isEqualTo( 0 );
+ assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" );
+ assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" );
+
+ // and now in reverse
+ Song song2 = ArtistToChartEntryWithConfigReverse.MAPPER.mapReverse( chartEntry );
+
+ assertThat( song2 ).isNotNull();
+ assertThat( song2.getArtist() ).isNotNull();
+ assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" );
+ assertThat( song2.getArtist().getLabel() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getName() ).isNull();
+ assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" );
+ assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" );
+ }
+
+ private Song prepareSong() {
+ Studio studio = new Studio( "Abbey Road", "London" );
+
+ Label label = new Label( "EMY", studio );
+
+ Artist artist = new Artist( "The Beatles", label );
+
+ return new Song( artist, "A Hard Day's Night", Collections.emptyList() );
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.java
new file mode 100644
index 000000000..ff21870b7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.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.constructor.nestedsource._target;
+
+/**
+ * @author Sjaak Derksen
+ */
+public class AdderUsageObserver {
+
+ private AdderUsageObserver() {
+ }
+
+ private static boolean used = false;
+
+ public static boolean isUsed() {
+ return used;
+ }
+
+ public static void setUsed(boolean used) {
+ AdderUsageObserver.used = used;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.java
new file mode 100644
index 000000000..7b82c77b3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.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.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class BaseChartEntry {
+
+ private final String chartName;
+ private final String songTitle;
+ private final String artistName;
+
+ public BaseChartEntry(String chartName, String songTitle, String artistName) {
+ this.chartName = chartName;
+ this.songTitle = songTitle;
+ this.artistName = artistName;
+ }
+
+ public String getChartName() {
+ return chartName;
+ }
+
+ public String getSongTitle() {
+ return songTitle;
+ }
+
+ public String getArtistName() {
+ return artistName;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.java
new file mode 100644
index 000000000..36d7e9560
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.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.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartEntry {
+
+ private final String chartName;
+ private final String songTitle;
+ private final String artistName;
+ private final String recordedAt;
+ private final String city;
+ private final int position;
+
+ public ChartEntry(String chartName, String songTitle, String artistName, String recordedAt, String city,
+ int position) {
+ this.chartName = chartName;
+ this.songTitle = songTitle;
+ this.artistName = artistName;
+ this.recordedAt = recordedAt;
+ this.city = city;
+ this.position = position;
+ }
+
+ public String getChartName() {
+ return chartName;
+ }
+
+ public String getSongTitle() {
+ return songTitle;
+ }
+
+ public String getArtistName() {
+ return artistName;
+ }
+
+ public String getRecordedAt() {
+ return recordedAt;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java
new file mode 100644
index 000000000..2543c7068
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartEntryComposed {
+
+ private String chartName;
+ private String songTitle;
+ private String artistName;
+ private ChartEntryLabel label;
+ private int position;
+
+ public String getChartName() {
+ return chartName;
+ }
+
+ public void setChartName(String chartName) {
+ this.chartName = chartName;
+ }
+
+ public String getSongTitle() {
+ return songTitle;
+ }
+
+ public void setSongTitle(String songTitle) {
+ this.songTitle = songTitle;
+ }
+
+ public String getArtistName() {
+ return artistName;
+ }
+
+ public void setArtistName(String artistName) {
+ this.artistName = artistName;
+ }
+
+ public ChartEntryLabel getLabel() {
+ return label;
+ }
+
+ public void setLabel(ChartEntryLabel label) {
+ this.label = label;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.java
new file mode 100644
index 000000000..b639b97b3
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.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.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartEntryLabel {
+
+ private final String name;
+ private final String city;
+ private final String recordedAt;
+
+ public ChartEntryLabel(String name, String city, String recordedAt) {
+ this.name = name;
+ this.city = city;
+ this.recordedAt = recordedAt;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public String getRecordedAt() {
+ return recordedAt;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.java
new file mode 100644
index 000000000..4855080fb
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartEntryWithBase extends BaseChartEntry {
+
+ private final String recordedAt;
+ private final String city;
+ private final int position;
+
+ public ChartEntryWithBase(String chartName, String songTitle, String artistName, String recordedAt,
+ String city, int position) {
+ super( chartName, songTitle, artistName );
+ this.recordedAt = recordedAt;
+ this.city = city;
+ this.position = position;
+ }
+
+ public String getRecordedAt() {
+ return recordedAt;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.java
new file mode 100644
index 000000000..f7ba366e1
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.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.constructor.nestedsource._target;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartEntryWithMapping {
+
+ private final String chartName;
+ private final String songTitle;
+ private final int artistId;
+ private final String recordedAt;
+ private final String city;
+ private final int position;
+
+ public ChartEntryWithMapping(String chartName, String songTitle, int artistId, String recordedAt,
+ String city, int position) {
+ this.chartName = chartName;
+ this.songTitle = songTitle;
+ this.artistId = artistId;
+ this.recordedAt = recordedAt;
+ this.city = city;
+ this.position = position;
+ }
+
+ public String getChartName() {
+ return chartName;
+ }
+
+ public String getSongTitle() {
+ return songTitle;
+ }
+
+ public int getArtistId() {
+ return artistId;
+ }
+
+ public String getRecordedAt() {
+ return recordedAt;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.java
new file mode 100644
index 000000000..a14e04ac7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.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.constructor.nestedsource._target;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ChartPositions {
+
+ private final List positions;
+
+ public ChartPositions(List positions) {
+ this.positions = positions;
+ }
+
+ public List getPositions() {
+ return Collections.unmodifiableList( positions );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.java
new file mode 100644
index 000000000..def4ac697
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.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.constructor.nestedsource.source;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Artist {
+
+ private final String name;
+ private final Label label;
+
+ public Artist(String name, Label label) {
+ this.name = name;
+ this.label = label;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.java
new file mode 100644
index 000000000..064129fc0
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.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.constructor.nestedsource.source;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Chart {
+
+ private final String type;
+ private final String name;
+ private final Song song;
+
+ public Chart(String type, String name, Song song) {
+ this.type = type;
+ this.name = name;
+ this.song = song;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Song getSong() {
+ return song;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.java
new file mode 100644
index 000000000..6c2a15bbf
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.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.constructor.nestedsource.source;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Label {
+
+ private final String name;
+ private final Studio studio;
+
+ public Label(String name, Studio studio) {
+ this.name = name;
+ this.studio = studio;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Studio getStudio() {
+ return studio;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.java
new file mode 100644
index 000000000..ac6ce2010
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.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.constructor.nestedsource.source;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Song {
+
+ private final Artist artist;
+ private final String title;
+ private final List positions;
+
+ public Song(Artist artist, String title, List positions) {
+ this.artist = artist;
+ this.title = title;
+ this.positions = positions;
+ }
+
+ public Artist getArtist() {
+ return artist;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public List getPositions() {
+ return positions;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.java
new file mode 100644
index 000000000..db7bfd738
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.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.constructor.nestedsource.source;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Studio {
+
+ private final String name;
+ private final String city;
+
+ public Studio(String name, String city) {
+ this.name = name;
+ this.city = city;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.java
new file mode 100644
index 000000000..9c1946fa2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedtarget;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public abstract class ChartEntryToArtist {
+
+ public static final ChartEntryToArtist MAPPER = Mappers.getMapper( ChartEntryToArtist.class );
+
+ @Mappings({
+ @Mapping(target = "type", ignore = true),
+ @Mapping(target = "name", source = "chartName"),
+ @Mapping(target = "song.title", source = "songTitle"),
+ @Mapping(target = "song.artist.name", source = "artistName"),
+ @Mapping(target = "song.artist.label.studio.name", source = "recordedAt"),
+ @Mapping(target = "song.artist.label.studio.city", source = "city"),
+ @Mapping(target = "song.positions", source = "position")
+ })
+ public abstract Chart map(ChartEntry chartEntry);
+
+ @InheritInverseConfiguration
+ public abstract ChartEntry map(Chart chart);
+
+ protected List mapPosition(Integer in) {
+ if ( in != null ) {
+ return new ArrayList<>( Arrays.asList( in ) );
+ }
+ else {
+ return new ArrayList<>();
+ }
+ }
+
+ protected Integer mapPosition(List in) {
+ if ( in != null && !in.isEmpty() ) {
+ return in.get( 0 );
+ }
+ else {
+ return null;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java
new file mode 100644
index 000000000..78d113a8c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedtarget;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Artist;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Studio;
+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 Sjaak Derksen
+ */
+@WithClasses({
+ Song.class,
+ Artist.class,
+ Chart.class,
+ Label.class,
+ Studio.class,
+ ChartEntry.class,
+ ChartEntryToArtist.class,
+})
+@IssueKey("73")
+@RunWith(AnnotationProcessorTestRunner.class)
+public class NestedProductPropertiesConstructorTest {
+
+ @Rule
+ public GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor(
+ ChartEntryToArtist.class
+ );
+
+ @Test
+ public void shouldMapNestedTarget() {
+
+ ChartEntry chartEntry = new ChartEntry(
+ "US Billboard Hot Rock Songs",
+ "Purple Rain",
+ "Prince",
+ "Live, First Avenue, Minneapolis",
+ "Minneapolis",
+ 1
+ );
+
+ Chart result = ChartEntryToArtist.MAPPER.map( chartEntry );
+
+ assertThat( result.getName() ).isEqualTo( "US Billboard Hot Rock Songs" );
+ assertThat( result.getSong() ).isNotNull();
+ assertThat( result.getSong().getArtist() ).isNotNull();
+ assertThat( result.getSong().getTitle() ).isEqualTo( "Purple Rain" );
+ assertThat( result.getSong().getArtist().getName() ).isEqualTo( "Prince" );
+ assertThat( result.getSong().getArtist().getLabel() ).isNotNull();
+ assertThat( result.getSong().getArtist().getLabel().getStudio() ).isNotNull();
+ assertThat( result.getSong().getArtist().getLabel().getStudio().getName() )
+ .isEqualTo( "Live, First Avenue, Minneapolis" );
+ assertThat( result.getSong().getArtist().getLabel().getStudio().getCity() )
+ .isEqualTo( "Minneapolis" );
+ assertThat( result.getSong().getPositions() ).hasSize( 1 );
+ assertThat( result.getSong().getPositions().get( 0 ) ).isEqualTo( 1 );
+
+ }
+
+ @Test
+ public void shouldReverseNestedTarget() {
+
+ ChartEntry chartEntry = new ChartEntry(
+ "US Billboard Hot Rock Songs",
+ "Purple Rain",
+ "Prince",
+ "Live, First Avenue, Minneapolis",
+ "Minneapolis",
+ 1
+ );
+
+ Chart chart = ChartEntryToArtist.MAPPER.map( chartEntry );
+ ChartEntry result = ChartEntryToArtist.MAPPER.map( chart );
+
+ assertThat( result ).isNotNull();
+ assertThat( result.getArtistName() ).isEqualTo( "Prince" );
+ assertThat( result.getChartName() ).isEqualTo( "US Billboard Hot Rock Songs" );
+ assertThat( result.getCity() ).isEqualTo( "Minneapolis" );
+ assertThat( result.getPosition() ).isEqualTo( 1 );
+ assertThat( result.getRecordedAt() ).isEqualTo( "Live, First Avenue, Minneapolis" );
+ assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.java
new file mode 100644
index 000000000..2e162d2f2
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.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.constructor.simple;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ap.test.constructor.Person;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface SimpleConstructorMapper {
+
+ SimpleConstructorMapper INSTANCE = Mappers.getMapper( SimpleConstructorMapper.class );
+
+ Person map(PersonDto dto);
+
+ @Mapping(target = "age", constant = "25")
+ @Mapping(target = "job", constant = "Software Developer")
+ Person mapWithConstants(PersonDto dto);
+
+ @Mapping(target = "age", expression = "java(25 - 5)")
+ @Mapping(target = "job", expression = "java(\"Software Developer\".toLowerCase())")
+ Person mapWithExpression(PersonDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java
new file mode 100644
index 000000000..e24abb84b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.simple;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mapstruct.ap.test.constructor.Person;
+import org.mapstruct.ap.test.constructor.PersonDto;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ Person.class,
+ PersonDto.class,
+ SimpleConstructorMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class SimpleConstructorTest {
+
+ @Test
+ public void mapDefault() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ Person target = SimpleConstructorMapper.INSTANCE.map( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 30 );
+ assertThat( target.getJob() ).isEqualTo( "Software Engineer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+
+ @Test
+ public void mapWithConstants() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ Person target = SimpleConstructorMapper.INSTANCE.mapWithConstants( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 25 );
+ assertThat( target.getJob() ).isEqualTo( "Software Developer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+
+ @Test
+ public void mapWithExpressions() {
+ PersonDto source = new PersonDto();
+ source.setName( "Bob" );
+ source.setAge( 30 );
+ source.setJob( "Software Engineer" );
+ source.setCity( "Zurich" );
+ source.setAddress( "Plaza 1" );
+ source.setChildren( Arrays.asList( "Alice", "Tom" ) );
+
+ Person target = SimpleConstructorMapper.INSTANCE.mapWithExpression( source );
+
+ assertThat( target.getName() ).isEqualTo( "Bob" );
+ assertThat( target.getAge() ).isEqualTo( 20 );
+ assertThat( target.getJob() ).isEqualTo( "software developer" );
+ assertThat( target.getCity() ).isEqualTo( "Zurich" );
+ assertThat( target.getAddress() ).isEqualTo( "Plaza 1" );
+ assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.java
new file mode 100644
index 000000000..18fe9b101
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.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.constructor.unmapped;
+
+import java.util.Date;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Order {
+
+ private final String name;
+ private final Date time;
+
+ public Order(String name, Date time) {
+ this.name = name;
+ this.time = time;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/OrderDto.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/OrderDto.java
new file mode 100644
index 000000000..259cf8973
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/OrderDto.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.constructor.unmapped;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class OrderDto {
+
+ private final String name;
+
+ public OrderDto(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.java
new file mode 100644
index 000000000..8fcede06f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.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.constructor.unmapped;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface UnmappedConstructorMapper {
+
+ UnmappedConstructorMapper INSTANCE = Mappers.getMapper( UnmappedConstructorMapper.class );
+
+ Order map(OrderDto dto);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.java
new file mode 100644
index 000000000..7a7f85904
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.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.constructor.unmapped;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+@WithClasses({
+ Order.class,
+ OrderDto.class,
+ UnmappedConstructorMapper.class
+})
+@RunWith(AnnotationProcessorTestRunner.class)
+public class UnmappedConstructorTest {
+
+ @Test
+ @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED,
+ diagnostics = {
+ @Diagnostic(type = UnmappedConstructorMapper.class,
+ kind = javax.tools.Diagnostic.Kind.WARNING,
+ line = 19,
+ message = "Unmapped target property: \"time\".")
+ })
+ public void shouldGenerateCompilableCodeForUnmappedConstructorProperties() {
+ OrderDto source = new OrderDto( "truck" );
+
+ Order target = UnmappedConstructorMapper.INSTANCE.map( source );
+
+ assertThat( target.getName() ).isEqualTo( "truck" );
+ assertThat( target.getTime() ).isNull();
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java
index 561b53eff..94993e4a6 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java
@@ -36,12 +36,7 @@ public class AmbiguousAnnotatedFactoryTest {
".ambiguousannotatedfactorymethod.Foo foo), org.mapstruct.ap.test.erroneous" +
".ambiguousannotatedfactorymethod.Bar org.mapstruct.ap.test.erroneous" +
".ambiguousannotatedfactorymethod.AmbiguousBarFactory.createBar(org.mapstruct.ap.test.erroneous" +
- ".ambiguousannotatedfactorymethod.Foo foo)."),
- @Diagnostic(type = SourceTargetMapperAndBarFactory.class,
- kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 22,
- message = "org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod.Bar does not have an " +
- "accessible parameterless constructor.")
+ ".ambiguousannotatedfactorymethod.Foo foo).")
}
)
diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java
index 82886eb03..e4e9ff81c 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java
@@ -37,12 +37,7 @@ public class FactoryTest {
message = "Ambiguous factory methods found for creating org.mapstruct.ap.test.erroneous" +
".ambiguousfactorymethod.Bar: org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar " +
"createBar(), org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar org.mapstruct.ap.test" +
- ".erroneous.ambiguousfactorymethod.a.BarFactory.createBar()."),
- @Diagnostic(type = SourceTargetMapperAndBarFactory.class,
- kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 22,
- message = "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar does not have an accessible " +
- "parameterless constructor.")
+ ".erroneous.ambiguousfactorymethod.a.BarFactory.createBar().")
}
)
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java
index 5ed5ee57d..5f8be2876 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java
@@ -28,9 +28,22 @@ public interface ArtistToChartEntryErroneous {
@Mapping(target = "city", ignore = true),
@Mapping(target = "position", source = "position")
} )
- ChartEntry forward(Integer position);
+ ChartEntry forward(ChartPosition position);
@InheritInverseConfiguration
- Integer reverse(ChartEntry position);
+ ChartPosition reverse(ChartEntry position);
+
+ class ChartPosition {
+
+ private final int position;
+
+ private ChartPosition(int position) {
+ this.position = position;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java
index 1d61b6e69..f9d5b11ee 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java
@@ -164,17 +164,18 @@ public class NestedSourcePropertiesTest {
}
@Test
- @IssueKey( "838" )
+ @IssueKey("838")
@ExpectedCompilationOutcome(
- value = CompilationResult.FAILED,
- diagnostics = {
- @Diagnostic( type = ArtistToChartEntryErroneous.class,
- kind = javax.tools.Diagnostic.Kind.ERROR,
- line = 34,
- message = "java.lang.Integer does not have an accessible parameterless constructor." )
- }
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ArtistToChartEntryErroneous.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 34,
+ message = "org.mapstruct.ap.test.nestedsourceproperties.ArtistToChartEntryErroneous.ChartPosition " +
+ "does not have an accessible constructor.")
+ }
)
@WithClasses({ ArtistToChartEntryErroneous.class })
- public void inverseShouldRaiseErrorForEmptyConstructor() {
+ public void inverseShouldRaiseErrorForNotAccessibleConstructor() {
}
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Citrus.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Citrus.java
new file mode 100644
index 000000000..5dd69a83f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Citrus.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.selection.resulttype;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Citrus extends Fruit implements IsFruit {
+
+ public Citrus(String type) {
+ super( type );
+ }
+
+ @Override
+ public void setType(String type) {
+ throw new RuntimeException( "Not allowed to change citrus type" );
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoEmptyConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoAccessibleConstructorMapper.java
similarity index 71%
rename from processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoEmptyConstructorMapper.java
rename to processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoAccessibleConstructorMapper.java
index 0e8cb1138..40612fcf3 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoEmptyConstructorMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousResultTypeNoAccessibleConstructorMapper.java
@@ -13,9 +13,16 @@ import org.mapstruct.Mapping;
* @author Filip Hrisafov
*/
@Mapper
-public interface ErroneousResultTypeNoEmptyConstructorMapper {
+public interface ErroneousResultTypeNoAccessibleConstructorMapper {
@BeanMapping(resultType = Banana.class)
@Mapping(target = "type", ignore = true)
Fruit map(FruitDto source);
+
+ class Banana extends Fruit {
+
+ private Banana(String type) {
+ super( type );
+ }
+ }
}
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 efdea3c0e..5cd415e56 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
@@ -21,9 +21,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutco
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
- *
* @author Sjaak Derksen
*/
@IssueKey("385")
@@ -48,12 +48,7 @@ public class InheritanceSelectionTest {
message = "Ambiguous factory methods found for creating org.mapstruct.ap.test.selection.resulttype" +
".Fruit: org.mapstruct.ap.test.selection.resulttype.Apple org.mapstruct.ap.test.selection" +
".resulttype.ConflictingFruitFactory.createApple(), org.mapstruct.ap.test.selection.resulttype" +
- ".Banana org.mapstruct.ap.test.selection.resulttype.ConflictingFruitFactory.createBanana()."),
- @Diagnostic(type = ErroneousFruitMapper.class,
- kind = Kind.ERROR,
- line = 23,
- message = "org.mapstruct.ap.test.selection.resulttype.Fruit does not have an accessible parameterless" +
- " constructor.")
+ ".Banana org.mapstruct.ap.test.selection.resulttype.ConflictingFruitFactory.createBanana().")
}
)
public void testForkedInheritanceHierarchyShouldResultInAmbigousMappingMethod() {
@@ -61,22 +56,22 @@ public class InheritanceSelectionTest {
@IssueKey("1283")
@Test
- @WithClasses({ ErroneousResultTypeNoEmptyConstructorMapper.class, Banana.class })
+ @WithClasses({ ErroneousResultTypeNoAccessibleConstructorMapper.class })
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
- @Diagnostic(type = ErroneousResultTypeNoEmptyConstructorMapper.class,
+ @Diagnostic(type = ErroneousResultTypeNoAccessibleConstructorMapper.class,
kind = Kind.ERROR,
line = 18,
- message = "org.mapstruct.ap.test.selection.resulttype.Banana does not have an accessible " +
- "parameterless constructor.")
+ message = "org.mapstruct.ap.test.selection.resulttype" +
+ ".ErroneousResultTypeNoAccessibleConstructorMapper.Banana does not have an accessible constructor.")
}
)
- public void testResultTypeHasNoSuitableEmptyConstructor() {
+ public void testResultTypeHasNoSuitableAccessibleConstructor() {
}
@Test
- @WithClasses( { ConflictingFruitFactory.class, ResultTypeSelectingFruitMapper.class, Banana.class } )
+ @WithClasses({ ConflictingFruitFactory.class, ResultTypeSelectingFruitMapper.class, Banana.class })
public void testResultTypeBasedFactoryMethodSelection() {
FruitDto fruitDto = new FruitDto( null );
@@ -88,7 +83,7 @@ public class InheritanceSelectionTest {
@Test
@IssueKey("434")
- @WithClasses( { ResultTypeConstructingFruitMapper.class } )
+ @WithClasses({ ResultTypeConstructingFruitMapper.class })
public void testResultTypeBasedConstructionOfResult() {
FruitDto fruitDto = new FruitDto( null );
@@ -99,7 +94,7 @@ public class InheritanceSelectionTest {
@Test
@IssueKey("657")
- @WithClasses( { ResultTypeConstructingFruitInterfaceMapper.class } )
+ @WithClasses({ ResultTypeConstructingFruitInterfaceMapper.class })
public void testResultTypeBasedConstructionOfResultForInterface() {
FruitDto fruitDto = new FruitDto( null );
@@ -143,7 +138,7 @@ public class InheritanceSelectionTest {
@Test
@IssueKey("433")
- @WithClasses( {
+ @WithClasses({
FruitFamilyMapper.class,
GoldenDeliciousDto.class,
GoldenDelicious.class,
@@ -151,11 +146,11 @@ public class InheritanceSelectionTest {
AppleFamilyDto.class,
AppleFactory.class,
Banana.class
- } )
+ })
public void testShouldSelectResultTypeInCaseOfAmbiguity() {
AppleFamilyDto appleFamilyDto = new AppleFamilyDto();
- appleFamilyDto.setApple( new AppleDto("AppleDto") );
+ appleFamilyDto.setApple( new AppleDto( "AppleDto" ) );
AppleFamily result = FruitFamilyMapper.INSTANCE.map( appleFamilyDto );
assertThat( result ).isNotNull();
@@ -167,7 +162,7 @@ public class InheritanceSelectionTest {
@Test
@IssueKey("433")
- @WithClasses( {
+ @WithClasses({
FruitFamilyMapper.class,
GoldenDeliciousDto.class,
GoldenDelicious.class,
@@ -175,7 +170,7 @@ public class InheritanceSelectionTest {
AppleFamilyDto.class,
AppleFactory.class,
Banana.class
- } )
+ })
public void testShouldSelectResultTypeInCaseOfAmbiguityForIterable() {
List source = Arrays.asList( new AppleDto( "AppleDto" ) );
@@ -189,7 +184,7 @@ public class InheritanceSelectionTest {
@Test
@IssueKey("433")
- @WithClasses( {
+ @WithClasses({
FruitFamilyMapper.class,
GoldenDeliciousDto.class,
GoldenDelicious.class,
@@ -197,7 +192,7 @@ public class InheritanceSelectionTest {
AppleFamilyDto.class,
AppleFactory.class,
Banana.class
- } )
+ })
public void testShouldSelectResultTypeInCaseOfAmbiguityForMap() {
Map source = new HashMap();
@@ -214,4 +209,38 @@ public class InheritanceSelectionTest {
assertThat( entry.getValue().getType() ).isEqualTo( "AppleDto" );
}
+
+ @Test
+ @IssueKey("73")
+ @WithClasses({
+ Citrus.class,
+ ResultTypeWithConstructorConstructingFruitInterfaceMapper.class
+ })
+ public void testShouldUseConstructorFromResultTypeForInterface() {
+ FruitDto orange = new FruitDto( "orange" );
+ IsFruit citrus = ResultTypeWithConstructorConstructingFruitInterfaceMapper.INSTANCE.map( orange );
+
+ assertThat( citrus ).isInstanceOf( Citrus.class );
+ assertThat( citrus.getType() ).isEqualTo( "orange" );
+ assertThatThrownBy( () -> citrus.setType( "lemon" ) )
+ .hasMessage( "Not allowed to change citrus type" );
+ assertThat( citrus.getType() ).isEqualTo( "orange" );
+ }
+
+ @Test
+ @IssueKey("73")
+ @WithClasses({
+ Citrus.class,
+ ResultTypeWithConstructorConstructingFruitInterfaceMapper.class
+ })
+ public void testShouldUseConstructorFromResultType() {
+ FruitDto lemon = new FruitDto( "lemon" );
+ IsFruit citrus = ResultTypeWithConstructorConstructingFruitInterfaceMapper.INSTANCE.map( lemon );
+
+ assertThat( citrus ).isInstanceOf( Citrus.class );
+ assertThat( citrus.getType() ).isEqualTo( "lemon" );
+ assertThatThrownBy( () -> citrus.setType( "orange" ) )
+ .hasMessage( "Not allowed to change citrus type" );
+ assertThat( citrus.getType() ).isEqualTo( "lemon" );
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitInterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitInterfaceMapper.java
new file mode 100644
index 000000000..b1670772e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitInterfaceMapper.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.resulttype;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ResultTypeWithConstructorConstructingFruitInterfaceMapper {
+
+ ResultTypeWithConstructorConstructingFruitInterfaceMapper INSTANCE = Mappers.getMapper(
+ ResultTypeWithConstructorConstructingFruitInterfaceMapper.class );
+
+ @BeanMapping(resultType = Citrus.class)
+ IsFruit map(FruitDto source);
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitMapper.java
new file mode 100644
index 000000000..7448cbb7a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeWithConstructorConstructingFruitMapper.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.selection.resulttype;
+
+import org.mapstruct.BeanMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ResultTypeWithConstructorConstructingFruitMapper {
+
+ ResultTypeWithConstructorConstructingFruitMapper INSTANCE = Mappers.getMapper(
+ ResultTypeWithConstructorConstructingFruitMapper.class );
+
+ @BeanMapping(resultType = Citrus.class)
+ Fruit map(FruitDto source);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/ErroneousOrganizationMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/ErroneousOrganizationMapper2.java
index 81eecea98..f57e4c95a 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/ErroneousOrganizationMapper2.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/ErroneousOrganizationMapper2.java
@@ -34,4 +34,11 @@ public interface ErroneousOrganizationMapper2 {
})
DepartmentEntity toDepartmentEntity(DepartmentDto dto);
+ class DepartmentEntity extends org.mapstruct.ap.test.updatemethods.DepartmentEntity {
+
+ private DepartmentEntity() {
+ super( null );
+ }
+ }
+
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java
index f04ddf338..1b93d04a5 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/updatemethods/UpdateMethodsTest.java
@@ -175,8 +175,8 @@ public class UpdateMethodsTest {
@Diagnostic(type = ErroneousOrganizationMapper2.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 35,
- message = "org.mapstruct.ap.test.updatemethods.DepartmentEntity does not have an accessible " +
- "parameterless constructor.")
+ message = "org.mapstruct.ap.test.updatemethods.ErroneousOrganizationMapper2.DepartmentEntity does not" +
+ " have an accessible constructor.")
})
public void testShouldFailOnConstantMappingNoPropertyGetter() {
diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java
index e8c09bec9..59916e981 100644
--- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java
+++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java
@@ -308,9 +308,11 @@ abstract class CompilingStatement extends Statement {
assertThat( actual.getLine() ).isIn( expected.getLine(), expected.getAlternativeLine() );
}
else if ( expected.getLine() != null ) {
- assertThat( actual.getLine() ).isEqualTo( expected.getLine() );
+ assertThat( actual.getLine() ).as( actual.getMessage() ).isEqualTo( expected.getLine() );
}
- assertThat( actual.getKind() ).isEqualTo( expected.getKind() );
+ assertThat( actual.getKind() )
+ .as( actual.getMessage() )
+ .isEqualTo( expected.getKind() );
if ( expected.getMessage() != null && !expected.getMessage().isEmpty() ) {
assertThat( actual.getMessage() ).describedAs(
String.format(
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
new file mode 100644
index 000000000..bd8c81a5d
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedsource;
+
+import javax.annotation.processing.Generated;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Artist;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Studio;
+
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2020-04-19T11:28:54+0200",
+ comments = "version: , compiler: javac, environment: Java 14.0.1 (Oracle Corporation)"
+)
+public class ArtistToChartEntryImpl implements ArtistToChartEntry {
+
+ @Override
+ public ChartEntry map(Chart chart, Song song, Integer position) {
+ if ( chart == null && song == null && position == null ) {
+ return null;
+ }
+
+ String chartName;
+ if ( chart != null ) {
+ chartName = chart.getName();
+ }
+ else {
+ chartName = null;
+ }
+ String songTitle;
+ String artistName;
+ String recordedAt;
+ String city;
+ if ( song != null ) {
+ songTitle = song.getTitle();
+ artistName = songArtistName( song );
+ recordedAt = songArtistLabelStudioName( song );
+ city = songArtistLabelStudioCity( song );
+ }
+ else {
+ songTitle = null;
+ artistName = null;
+ recordedAt = null;
+ city = null;
+ }
+ int position1;
+ if ( position != null ) {
+ position1 = position;
+ }
+ else {
+ position1 = 0;
+ }
+
+ ChartEntry chartEntry = new ChartEntry( chartName, songTitle, artistName, recordedAt, city, position1 );
+
+ return chartEntry;
+ }
+
+ @Override
+ public ChartEntry map(Song song) {
+ if ( song == null ) {
+ return null;
+ }
+
+ String songTitle;
+ String artistName;
+ String recordedAt;
+ String city;
+
+ songTitle = song.getTitle();
+ artistName = songArtistName( song );
+ recordedAt = songArtistLabelStudioName( song );
+ city = songArtistLabelStudioCity( song );
+
+ String chartName = null;
+ int position = 0;
+
+ ChartEntry chartEntry = new ChartEntry( chartName, songTitle, artistName, recordedAt, city, position );
+
+ return chartEntry;
+ }
+
+ @Override
+ public ChartEntry map(Chart name) {
+ if ( name == null ) {
+ return null;
+ }
+
+ String chartName;
+
+ chartName = name.getName();
+
+ String songTitle = null;
+ String artistName = null;
+ String recordedAt = null;
+ String city = null;
+ int position = 0;
+
+ ChartEntry chartEntry = new ChartEntry( chartName, songTitle, artistName, recordedAt, city, position );
+
+ return chartEntry;
+ }
+
+ 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;
+ }
+
+ private String songArtistLabelStudioName(Song song) {
+ if ( song == null ) {
+ return null;
+ }
+ Artist artist = song.getArtist();
+ if ( artist == null ) {
+ return null;
+ }
+ Label label = artist.getLabel();
+ if ( label == null ) {
+ return null;
+ }
+ Studio studio = label.getStudio();
+ if ( studio == null ) {
+ return null;
+ }
+ String name = studio.getName();
+ if ( name == null ) {
+ return null;
+ }
+ return name;
+ }
+
+ private String songArtistLabelStudioCity(Song song) {
+ if ( song == null ) {
+ return null;
+ }
+ Artist artist = song.getArtist();
+ if ( artist == null ) {
+ return null;
+ }
+ Label label = artist.getLabel();
+ if ( label == null ) {
+ return null;
+ }
+ Studio studio = label.getStudio();
+ if ( studio == null ) {
+ return null;
+ }
+ String city = studio.getCity();
+ if ( city == null ) {
+ return null;
+ }
+ return city;
+ }
+}
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
new file mode 100644
index 000000000..f5deb649b
--- /dev/null
+++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.constructor.nestedtarget;
+
+import java.util.List;
+import javax.annotation.processing.Generated;
+import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Artist;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Chart;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Label;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Song;
+import org.mapstruct.ap.test.constructor.nestedsource.source.Studio;
+
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2020-04-19T14:54:28+0200",
+ comments = "version: , compiler: javac, environment: Java 14.0.1 (Oracle Corporation)"
+)
+public class ChartEntryToArtistImpl extends ChartEntryToArtist {
+
+ @Override
+ public Chart map(ChartEntry chartEntry) {
+ if ( chartEntry == null ) {
+ return null;
+ }
+
+ Song song;
+ String name;
+
+ song = chartEntryToSong( chartEntry );
+ name = chartEntry.getChartName();
+
+ String type = null;
+
+ Chart chart = new Chart( type, name, song );
+
+ return chart;
+ }
+
+ @Override
+ public ChartEntry map(Chart chart) {
+ if ( chart == null ) {
+ return null;
+ }
+
+ String chartName;
+ String songTitle;
+ String artistName;
+ String recordedAt;
+ String city;
+ int position;
+
+ chartName = chart.getName();
+ songTitle = chartSongTitle( chart );
+ artistName = chartSongArtistName( chart );
+ recordedAt = chartSongArtistLabelStudioName( chart );
+ city = chartSongArtistLabelStudioCity( chart );
+ position = mapPosition( chartSongPositions( chart ) );
+
+ ChartEntry chartEntry = new ChartEntry( chartName, songTitle, artistName, recordedAt, city, position );
+
+ return chartEntry;
+ }
+
+ protected Studio chartEntryToStudio(ChartEntry chartEntry) {
+ if ( chartEntry == null ) {
+ return null;
+ }
+
+ String name;
+ String city;
+
+ name = chartEntry.getRecordedAt();
+ city = chartEntry.getCity();
+
+ Studio studio = new Studio( name, city );
+
+ return studio;
+ }
+
+ protected Label chartEntryToLabel(ChartEntry chartEntry) {
+ if ( chartEntry == null ) {
+ return null;
+ }
+
+ Studio studio;
+
+ studio = chartEntryToStudio( chartEntry );
+
+ String name = null;
+
+ Label label = new Label( name, studio );
+
+ return label;
+ }
+
+ protected Artist chartEntryToArtist(ChartEntry chartEntry) {
+ if ( chartEntry == null ) {
+ return null;
+ }
+
+ Label label;
+ String name;
+
+ label = chartEntryToLabel( chartEntry );
+ name = chartEntry.getArtistName();
+
+ Artist artist = new Artist( name, label );
+
+ return artist;
+ }
+
+ protected Song chartEntryToSong(ChartEntry chartEntry) {
+ if ( chartEntry == null ) {
+ return null;
+ }
+
+ Artist artist;
+ String title;
+ List positions;
+
+ artist = chartEntryToArtist( chartEntry );
+ title = chartEntry.getSongTitle();
+ positions = mapPosition( chartEntry.getPosition() );
+
+ Song song = new Song( artist, title, positions );
+
+ return song;
+ }
+
+ 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;
+ }
+
+ private String chartSongArtistName(Chart chart) {
+ if ( chart == null ) {
+ return null;
+ }
+ Song song = chart.getSong();
+ 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;
+ }
+
+ private String chartSongArtistLabelStudioName(Chart chart) {
+ if ( chart == null ) {
+ return null;
+ }
+ Song song = chart.getSong();
+ if ( song == null ) {
+ return null;
+ }
+ Artist artist = song.getArtist();
+ if ( artist == null ) {
+ return null;
+ }
+ Label label = artist.getLabel();
+ if ( label == null ) {
+ return null;
+ }
+ Studio studio = label.getStudio();
+ if ( studio == null ) {
+ return null;
+ }
+ String name = studio.getName();
+ if ( name == null ) {
+ return null;
+ }
+ return name;
+ }
+
+ private String chartSongArtistLabelStudioCity(Chart chart) {
+ if ( chart == null ) {
+ return null;
+ }
+ Song song = chart.getSong();
+ if ( song == null ) {
+ return null;
+ }
+ Artist artist = song.getArtist();
+ if ( artist == null ) {
+ return null;
+ }
+ Label label = artist.getLabel();
+ if ( label == null ) {
+ return null;
+ }
+ Studio studio = label.getStudio();
+ if ( studio == null ) {
+ return null;
+ }
+ String city = studio.getCity();
+ if ( city == null ) {
+ return null;
+ }
+ return city;
+ }
+
+ 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;
+ }
+}