#73 Add support for using constructor arguments when instantiating mapping targets

By default the constructor argument names are used to extract the target properties.
If a constructor is annotated with an annotation named `@ConstructorProperties` (from any package) then it would be used to extract the target properties.

If a mapping target has a parameterless empty constructor it would be used to instantiate the target.
When there are multiple constructors then an annotation named `@Default` (from any package) can be used to mark a constructor that should be used by default when instantiating the target.

Supports mapping into Java 14 Records and Kotlin data classes out of the box
This commit is contained in:
Filip Hrisafov 2020-04-26 12:08:57 +02:00
parent d6ff5204d7
commit 2b2299a730
105 changed files with 3906 additions and 174 deletions

View File

@ -122,9 +122,6 @@
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="MethodLength">
<property name="max" value="200"/>
</module>
<module name="ParameterNumber">
<property name="max" value="10"/>
</module>

View File

@ -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;
}
}
----
====

View File

@ -174,6 +174,12 @@ During the generation of automatic sub-mapping methods <<shared-configurations>>
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 <<mapping-with-constructors>>
====
[[controlling-nested-bean-mappings]]
=== Controlling nested bean mappings

View File

@ -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() {
}

View File

@ -71,6 +71,9 @@ public class ProcessorInvocationInterceptor implements InvocationInterceptor {
}
else {
verifier = new Verifier( destination.getCanonicalPath() );
if ( processorTestContext.isForkJvm() ) {
verifier.setForkJvm( true );
}
}
List<String> goals = new ArrayList<>( 3 );

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -34,7 +34,8 @@ public class ProcessorTestTemplateInvocationContextProvider implements TestTempl
.map( processorType -> new ProcessorTestTemplateInvocationContext( new ProcessorTestContext(
processorTest.baseDir(),
processorType,
processorTest.commandLineEnhancer()
processorTest.commandLineEnhancer(),
processorTest.forkJvm()
) ) );
}
}

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-it-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>kotlinDataTest</artifactId>
<packaging>jar</packaging>
<properties>
<kotlin.version>1.3.70</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>generate-via-compiler-plugin</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals> <goal>compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>\${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>\${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals> <goal>test-compile</goal> </goals>
<configuration>
<sourceDirs>
<sourceDir>\${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>\${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals> <goal>testCompile</goal> </goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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?) {
}

View File

@ -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" );
}
}

View File

@ -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);
}

View File

@ -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" );
}
}

View File

@ -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<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final Map<String, List<PropertyMapping>> constructorMappingsByParameter;
private final List<PropertyMapping> constantMappings;
private final List<PropertyMapping> 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<String, Accessor> unprocessedConstructorProperties;
private Map<String, Accessor> unprocessedTargetProperties;
private Map<String, Accessor> unprocessedSourceProperties;
private Set<String> targetProperties;
@ -82,6 +99,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Map<String, Set<MappingReference>> 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<String, Accessor> 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<String, Accessor> 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<SelectedMethod<SourceMethod>> 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<Element> recordComponents = type.getRecordComponents();
List<ParameterBinding> parameterBindings = new ArrayList<>( recordComponents.size() );
Map<String, Accessor> 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<ExecutableElement> constructors = ElementFilter.constructorsIn( type.getTypeElement()
.getEnclosedElements() );
ExecutableElement defaultConstructor = null;
List<ExecutableElement> 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<Parameter> constructorParameters = ctx.getTypeFactory()
.getParameters( (DeclaredType) type.getTypeMirror(), constructor );
List<String> 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<String, Accessor> constructorAccessors = new LinkedHashMap<>();
List<ParameterBinding> 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<String, Accessor> constructorAccessors = new LinkedHashMap<>();
List<ParameterBinding> 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<String> getArrayValues(AnnotationValue av) {
if ( av.getValue() instanceof List ) {
List<String> 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<AnnotationValue> getValueAsList(AnnotationValue av) {
return (List<AnnotationValue>) 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<ParameterBinding> parameterBindings;
private final Map<String, Accessor> constructorAccessors;
private ConstructorAccessor(
List<ParameterBinding> parameterBindings,
Map<String, Accessor> constructorAccessors) {
this.parameterBindings = parameterBindings;
this.constructorAccessors = constructorAccessors;
}
}
private BeanMappingMethod(Method method,
Collection<String> existingVariableNames,
List<PropertyMapping> 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<PropertyMapping> 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<String> 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<PropertyMapping> getConstructorConstantMappings() {
return constructorConstantMappings;
}
public List<PropertyMapping> 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<PropertyMapping> 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;
}

View File

@ -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()
);
}

View File

@ -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<ParameterBinding> 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<ParameterBinding> 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<Type> 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<ParameterBinding> 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<ParameterBinding> parameterBindings) {
return new MethodReference( type, parameterBindings );
}
@Override
public String toString() {
String mapper = declaringMapper != null ? declaringMapper.getType().getName() : "";

View File

@ -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;
}

View File

@ -67,16 +67,12 @@ public class ObjectFactoryMethodResolver {
MappingBuilderContext ctx) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
selectors.getMatchingMethods(
method,
getAllAvailableMethods( method, ctx.getSourceModel() ),
java.util.Collections.emptyList(),
alternativeTarget,
SelectionCriteria.forFactoryMethods( selectionParameters ) );
List<SelectedMethod<SourceMethod>> matchingFactoryMethods = getMatchingFactoryMethods(
method,
alternativeTarget,
selectionParameters,
ctx
);
if (matchingFactoryMethods.isEmpty()) {
return null;
@ -94,6 +90,11 @@ public class ObjectFactoryMethodResolver {
SelectedMethod<SourceMethod> matchingFactoryMethod = first( matchingFactoryMethods );
return getFactoryMethodReference( method, matchingFactoryMethod, ctx );
}
public static MethodReference getFactoryMethodReference(Method method,
SelectedMethod<SourceMethod> matchingFactoryMethod, MappingBuilderContext ctx) {
Parameter providingParameter =
method.getContextProvidedMethods().getParameterForProvidedMethod( matchingFactoryMethod.getMethod() );
@ -115,6 +116,22 @@ public class ObjectFactoryMethodResolver {
}
}
public static List<SelectedMethod<SourceMethod>> 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;

View File

@ -70,6 +70,7 @@ public class PropertyMapping extends ModelElement {
private final Assignment assignment;
private final Set<String> dependsOn;
private final Assignment defaultValueAssignment;
private final boolean constructorMapping;
@SuppressWarnings("unchecked")
private static class MappingBuilderBase<T extends MappingBuilderBase<T>> extends AbstractBaseBuilder<T> {
@ -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<String> dependsOn, Assignment defaultValueAssignment ) {
ValueProvider targetReadAccessorProvider,
Type targetType, Assignment propertyAssignment,
Set<String> 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<String> dependsOn, Assignment defaultValueAssignment) {
ValueProvider targetReadAccessorProvider, Type targetType,
Assignment assignment,
Set<String> 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.<String>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<Type> getImportTypes() {
if ( defaultValueAssignment == null ) {

View File

@ -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
);
}

View File

@ -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<X>} for the target type {@code X}
* @return a parameter binding representing a target type parameter

View File

@ -90,14 +90,16 @@ public class Type extends ModelElement implements Comparable<Type> {
private List<ExecutableElement> allMethods = null;
private List<VariableElement> allFields = null;
private List<Element> recordComponents = null;
private List<Accessor> setters = null;
private List<Accessor> adders = null;
private List<Accessor> alternativeTargetAccessors = null;
private Map<String, Accessor> 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<Type> {
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<Type> {
}
}
Map<String, Accessor> recordAccessors = filters.recordsIn( typeElement );
Map<String, Accessor> recordAccessors = filters.recordAccessorsIn( getRecordComponents() );
for ( Map.Entry<String, Accessor> recordEntry : recordAccessors.entrySet() ) {
modifiableGetters.putIfAbsent( recordEntry.getKey(), recordEntry.getValue() );
}
@ -600,6 +606,14 @@ public class Type extends ModelElement implements Comparable<Type> {
return result;
}
public List<Element> 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<Type> {
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<Type> {
}
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<Type> {
return boundingBase;
}
public boolean hasEmptyAccessibleConstructor() {
if ( this.hasEmptyAccessibleConstructor == null ) {
hasEmptyAccessibleConstructor = false;
public boolean hasAccessibleConstructor() {
if ( hasAccessibleConstructor == null ) {
hasAccessibleConstructor = false;
List<ExecutableElement> 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;
}
/**

View File

@ -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<Parameter> getParameters(DeclaredType includingType, Accessor accessor) {
ExecutableElement method = (ExecutableElement) accessor.getElement();
TypeMirror methodType = getMethodType( includingType, accessor.getElement() );
return getParameters( includingType, method );
}
public List<Parameter> 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<Parameter> getParameters(ExecutableType methodType, ExecutableElement method) {
@ -433,7 +436,7 @@ public class TypeFactory {
}
public List<Type> getThrownTypes(Accessor accessor) {
if (accessor.getAccessorType() == AccessorType.FIELD) {
if (accessor.getAccessorType().isFieldAssignment()) {
return new ArrayList<>();
}
return extractTypes( ( (ExecutableElement) accessor.getElement() ).getThrownTypes() );

View File

@ -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
*

View File

@ -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<String, Accessor> recordsIn(TypeElement typeElement) {
if ( RECORD_COMPONENTS_METHOD == null || RECORD_COMPONENT_ACCESSOR_METHOD == null ) {
@SuppressWarnings("unchecked")
public List<Element> recordComponentsIn(TypeElement typeElement) {
if ( RECORD_COMPONENTS_METHOD == null ) {
return java.util.Collections.emptyList();
}
try {
return (List<Element>) RECORD_COMPONENTS_METHOD.invoke( typeElement );
}
catch ( IllegalAccessException | InvocationTargetException e ) {
return java.util.Collections.emptyList();
}
}
public Map<String, Accessor> recordAccessorsIn(Collection<Element> recordComponents) {
if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) {
return java.util.Collections.emptyMap();
}
try {
@SuppressWarnings("unchecked")
List<Element> recordComponents = (List<Element>) RECORD_COMPONENTS_METHOD.invoke( typeElement );
Map<String, Accessor> recordAccessors = new LinkedHashMap<>();
for ( Element recordComponent : recordComponents ) {
ExecutableElement recordExecutableElement =
@ -109,7 +122,7 @@ public class Filters {
public List<Accessor> fieldsIn(List<VariableElement> accessors) {
return accessors.stream()
.filter( Fields::isFieldAccessor )
.map( VariableElementAccessor::new )
.map( FieldElementAccessor::new )
.collect( Collectors.toCollection( LinkedList::new ) );
}

View File

@ -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 ),

View File

@ -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 );

View File

@ -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;
/**

View File

@ -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;
}
}

View File

@ -13,9 +13,9 @@ import javax.lang.model.type.TypeMirror;
*
* @author Filip Hrisafov
*/
public class VariableElementAccessor extends AbstractAccessor<VariableElement> {
public class FieldElementAccessor extends AbstractAccessor<VariableElement> {
public VariableElementAccessor(VariableElement element) {
public FieldElementAccessor(VariableElement element) {
super( element );
}

View File

@ -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<Element> {
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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -65,12 +65,7 @@ public class Issue1242Test {
".lang.Class<org.mapstruct.ap.test.bugs._1242.TargetB> clazz), org.mapstruct.ap.test.bugs._1242" +
".TargetB org.mapstruct.ap.test.bugs._1242.TargetFactories.createTargetB(@TargetType java.lang" +
".Class<org.mapstruct.ap.test.bugs._1242.TargetB> 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() {
}

View File

@ -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."
)
}
)

View File

@ -12,7 +12,7 @@ public class Source {
private String source;
public Source(String source) {
private Source(String source) {
this.source = source;
}

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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<String> children;
public Person(String name, int age, String job, String city, String address,
List<String> 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<String> getChildren() {
return children;
}
}

View File

@ -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<String> 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<String> getChildren() {
return children;
}
public void setChildren(List<String> children) {
this.children = children;
}
}

View File

@ -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<String> children;
@ConstructorProperties({"name", "age", "job", "city", "address", "children"})
public PersonWithConstructorProperties(String var1, int var2, String var3, String var4, String var5,
List<String> 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<String> getChildren() {
return children;
}
}

View File

@ -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);
}

View File

@ -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" );
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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 );
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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() {
}
}

View File

@ -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);
}

View File

@ -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" );
}
}

View File

@ -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<String> 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<String> getChildren() {
return children;
}
public void setChildren(List<String> children) {
this.children = children;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 );
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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";
}
}
}

View File

@ -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();
}
}

View File

@ -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() );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Long> positions;
public ChartPositions(List<Long> positions) {
this.positions = positions;
}
public List<Long> getPositions() {
return Collections.unmodifiableList( positions );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Integer> positions;
public Song(Artist artist, String title, List<Integer> positions) {
this.artist = artist;
this.title = title;
this.positions = positions;
}
public Artist getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public List<Integer> getPositions() {
return positions;
}
}

View File

@ -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;
}
}

View File

@ -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<Integer> mapPosition(Integer in) {
if ( in != null ) {
return new ArrayList<>( Arrays.asList( in ) );
}
else {
return new ArrayList<>();
}
}
protected Integer mapPosition(List<Integer> in) {
if ( in != null && !in.isEmpty() ) {
return in.get( 0 );
}
else {
return null;
}
}
}

View File

@ -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" );
}
}

View File

@ -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);
}

View File

@ -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" );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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).")
}
)

View File

@ -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().")
}
)

View File

@ -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;
}
}
}

View File

@ -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() {
}
}

View File

@ -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" );
}
}

View File

@ -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 );
}
}
}

View File

@ -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<AppleDto> 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<AppleDto, AppleDto> source = new HashMap<AppleDto, AppleDto>();
@ -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" );
}
}

View File

@ -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);
}

View File

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More