diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index ac1c59a5e..8d3feb041 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -635,14 +635,24 @@ public class PropertyMapping extends ModelElement { if ( sourceType.isPrimitive() || targetType.isPrimitive() ) { return null; } + String name = getName( sourceType, targetType ); + List parameters = new ArrayList( method.getContextParameters() ); + Type returnType; + if ( method.isUpdateMethod() ) { + parameters.add( Parameter.forForgedMappingTarget( targetType ) ); + returnType = ctx.getTypeFactory().createVoidType(); + } + else { + returnType = targetType; + } ForgedMethod forgedMethod = new ForgedMethod( name, sourceType, - targetType, + returnType, method.getMapperConfiguration(), method.getExecutable(), - method.getContextParameters(), + parameters, getForgedMethodHistory( sourceRHS ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 0e9bb5eb7..e238b58b5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -125,6 +125,15 @@ public class Parameter extends ModelElement { ContextPrism.getInstanceOn( element ) != null ); } + public static Parameter forForgedMappingTarget(Type parameterType) { + return new Parameter( + "mappingTarget", + parameterType, + true, + false, + false); + } + /** * @param parameters the parameters to filter * @return the parameters from the given list that are considered 'source parameters' @@ -156,4 +165,24 @@ public class Parameter extends ModelElement { return contextParameters; } + + public static Parameter getMappingTargetParameter(List parameters) { + for ( Parameter parameter : parameters ) { + if ( parameter.isMappingTarget() ) { + return parameter; + } + } + + return null; + } + + public static Parameter getTargetTypeParameter(List parameters) { + for ( Parameter parameter : parameters ) { + if ( parameter.isTargetType() ) { + return parameter; + } + } + + return null; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index eedf7fb72..8738eea49 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -493,6 +493,15 @@ public class TypeFactory { return collectionOrMap; } + /** + * creates a void return type + * + * @return void type + */ + public Type createVoidType() { + return getType( typeUtils.getNoType( TypeKind.VOID ) ); + } + /** * Establishes the type bound: *
    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java index a09e0e921..3c6c0377c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java @@ -47,20 +47,21 @@ public class ForgedMethod implements Method { private final List sourceParameters; private final List contextParameters; + private final Parameter mappingTargetParameter; /** * Creates a new forged method with the given name. * * @param name the (unique name) for this method * @param sourceType the source type - * @param targetType the target type. + * @param returnType the return type. * @param mapperConfiguration the mapper configuration * @param positionHintElement element used to for reference to the position in the source file. * @param additionalParameters additional parameters to add to the forged method */ - public ForgedMethod(String name, Type sourceType, Type targetType, MapperConfiguration mapperConfiguration, + public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, ExecutableElement positionHintElement, List additionalParameters) { - this( name, sourceType, targetType, mapperConfiguration, positionHintElement, additionalParameters, null ); + this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null ); } /** @@ -68,13 +69,13 @@ public class ForgedMethod implements Method { * * @param name the (unique name) for this method * @param sourceType the source type - * @param targetType the target type. + * @param returnType the return type. * @param mapperConfiguration the mapper configuration * @param positionHintElement element used to for reference to the position in the source file. * @param additionalParameters additional parameters to add to the forged method * @param history a parent forged method if this is a forged method within a forged method */ - public ForgedMethod(String name, Type sourceType, Type targetType, MapperConfiguration mapperConfiguration, + public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, ExecutableElement positionHintElement, List additionalParameters, ForgedMethodHistory history) { String sourceParamName = Strings.decapitalize( sourceType.getName() ); @@ -85,8 +86,9 @@ public class ForgedMethod implements Method { this.parameters.addAll( additionalParameters ); this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); - this.returnType = targetType; + this.returnType = returnType; this.thrownTypes = new ArrayList(); this.name = Strings.sanitizeIdentifierName( name ); this.mapperConfiguration = mapperConfiguration; @@ -109,6 +111,7 @@ public class ForgedMethod implements Method { this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.name = name; } @@ -165,7 +168,7 @@ public class ForgedMethod implements Method { @Override public Parameter getMappingTargetParameter() { - return null; + return mappingTargetParameter; } @Override @@ -203,7 +206,7 @@ public class ForgedMethod implements Method { @Override public Type getResultType() { - return returnType; + return mappingTargetParameter != null ? mappingTargetParameter.getType() : returnType; } @Override @@ -303,4 +306,5 @@ public class ForgedMethod implements Method { result = 31 * result + ( name != null ? name.hashCode() : 0 ); return result; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index a012c119a..bd2da2b46 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -228,8 +228,8 @@ public class SourceMethod implements Method { this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); - this.mappingTargetParameter = determineMappingTargetParameter( parameters ); - this.targetTypeParameter = determineTargetTypeParameter( parameters ); + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); + this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); this.isObjectFactory = determineIfIsObjectFactory( executable ); this.typeUtils = typeUtils; @@ -248,26 +248,6 @@ public class SourceMethod implements Method { && ( hasFactoryAnnotation || hasNoSourceParameters ); } - private Parameter determineMappingTargetParameter(Iterable parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isMappingTarget() ) { - return parameter; - } - } - - return null; - } - - private Parameter determineTargetTypeParameter(Iterable parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isTargetType() ) { - return parameter; - } - } - - return null; - } - @Override public Type getDeclaringMapper() { return declaringMapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java index c26af1432..4c780bc71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java @@ -30,7 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat; Wheel.class, WheelDto.class, Roof.class, RoofDto.class, UserDtoMapperClassic.class, - UserDtoMapperSmart.class + UserDtoMapperSmart.class, + UserDtoUpdateMapperSmart.class }) @RunWith(AnnotationProcessorTestRunner.class) public class NestedSimpleBeansMappingTest { @@ -46,9 +47,30 @@ public class NestedSimpleBeansMappingTest { System.out.println( smartMapping ); System.out.println( classicMapping ); - assertThat( smartMapping ).isNotNull(); assertThat( smartMapping ).isEqualTo( classicMapping ); } + @Test + public void shouldMapUpdateNestedBeans() { + + User user = TestData.createUser(); + user.getCar().setName( null ); + + // create a pre-exsiting smartMapping + UserDto smartMapping = new UserDto(); + smartMapping.setCar( new CarDto() ); + smartMapping.getCar().setName( "Toyota" ); + + // create a classic mapping and adapt expected result to Toyota + UserDto classicMapping = UserDtoMapperClassic.INSTANCE.userToUserDto( TestData.createUser() ); + classicMapping.getCar().setName( "Toyota" ); + + // action + UserDtoUpdateMapperSmart.INSTANCE.userToUserDto( smartMapping, user ); + + // result + assertThat( smartMapping ).isNotNull(); + assertThat( smartMapping ).isEqualTo( classicMapping ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmart.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmart.java new file mode 100644 index 000000000..adc1a43ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmart.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.nestedbeans; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface UserDtoUpdateMapperSmart { + + UserDtoUpdateMapperSmart INSTANCE = Mappers.getMapper( UserDtoUpdateMapperSmart.class ); + + void userToUserDto(@MappingTarget UserDto userDto, User user); + +}