Merge branch '1.1-master' into master

This commit is contained in:
sjaakd 2016-11-14 23:31:33 +01:00
commit dc61079793
9 changed files with 215 additions and 227 deletions

View File

@ -35,9 +35,7 @@ import org.mapstruct.ap.internal.model.assignment.AdderWrapper;
import org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper;
import org.mapstruct.ap.internal.model.assignment.Assignment;
import org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper;
import org.mapstruct.ap.internal.model.assignment.EnumSetCopyWrapper;
import org.mapstruct.ap.internal.model.assignment.GetterWrapperForCollectionsAndMaps;
import org.mapstruct.ap.internal.model.assignment.NewCollectionOrMapWrapper;
import org.mapstruct.ap.internal.model.assignment.NullCheckWrapper;
import org.mapstruct.ap.internal.model.assignment.SetterWrapper;
import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps;
@ -419,52 +417,30 @@ public class PropertyMapping extends ModelElement {
Assignment result = rhs;
// wrap the setter in the collection / map initializers
if ( targetAccessorType == TargetWriteAccessorType.SETTER ) {
// wrap the assignment in a new Map or Collection implementation if this is not done in a
// mapping method. Note, typeconversons do not apply to collections or maps
Assignment newCollectionOrMap = null;
if ( result.getType() == DIRECT ) {
Set<Type> implementationTypes;
if ( targetType.getImplementationType() != null ) {
implementationTypes = targetType.getImplementationType().getImportTypes();
}
else {
implementationTypes = targetType.getImportTypes();
}
if ( "java.util.EnumSet".equals( targetType.getFullyQualifiedName() ) ) {
newCollectionOrMap = new EnumSetCopyWrapper( ctx.getTypeFactory(), result );
}
else {
newCollectionOrMap = new NewCollectionOrMapWrapper( result, implementationTypes );
}
newCollectionOrMap = new SetterWrapper( newCollectionOrMap, method.getThrownTypes() );
}
if ( result.isUpdateMethod() ) {
// call to an update method
if ( targetReadAccessor == null ) {
ctx.getMessager().printMessage( method.getExecutable(),
Message.PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE,
targetPropertyName );
}
Assignment factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, targetType, null );
result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod,
targetType );
result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod, targetType );
}
else {
// target accessor is setter, so wrap the setter in setter map/ collection handling
result = new SetterWrapperForCollectionsAndMaps(
result,
newCollectionOrMap,
method.getThrownTypes(),
getSourcePresenceCheckerRef(),
existingVariableNames,
targetType
targetType,
ALWAYS == method.getMapperConfiguration().getNullValueCheckStrategy(),
ctx.getTypeFactory()
);
}
}
else {
// target accessor is getter, so wrap the setter in getter map/ collection handling

View File

@ -1,55 +0,0 @@
/**
* Copyright 2012-2016 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.internal.model.assignment;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
/**
* Invokes the copy method for enum sets.
*
* @author Gunnar Morling
*/
public class EnumSetCopyWrapper extends AssignmentWrapper {
private Type enumSetType;
public EnumSetCopyWrapper(TypeFactory typeFactory, Assignment decoratedAssignment) {
super( decoratedAssignment );
enumSetType = typeFactory.getType( EnumSet.class );
}
@Override
public Set<Type> getImportTypes() {
Set<Type> imported = new HashSet<Type>( getAssignment().getImportTypes().size() + 1 );
imported.addAll( getAssignment().getImportTypes() );
imported.add( enumSetType );
return imported;
}
@Override
public String toString() {
return "EnumSet.copyOf( " + getAssignment() + " )";
}
}

View File

@ -1,47 +0,0 @@
/**
* Copyright 2012-2016 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.internal.model.assignment;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Type;
/**
* Decorates the assignment as a Map or Collection constructor
*
* @author Sjaak Derksen
*/
public class NewCollectionOrMapWrapper extends AssignmentWrapper {
private final Set<Type> implementationTypes;
public NewCollectionOrMapWrapper(Assignment decoratedAssignment, Set<Type> implementationTypes) {
super( decoratedAssignment );
this.implementationTypes = implementationTypes;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> imported = new HashSet<Type>();
imported.addAll( getAssignment().getImportTypes() );
imported.addAll( implementationTypes );
return imported;
}
}

View File

@ -18,10 +18,13 @@
*/
package org.mapstruct.ap.internal.model.assignment;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
/**
* This wrapper handles the situation were an assignment is done via the setter.
@ -37,31 +40,52 @@ import org.mapstruct.ap.internal.model.common.Type;
*/
public class SetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps {
private final Assignment newCollectionOrMapAssignment;
private final boolean allwaysIncludeNullCheck;
private final Type targetType;
private final TypeFactory typeFactory;
public SetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment,
Assignment newCollectionOrMapAssignment,
List<Type> thrownTypesToExclude,
String sourcePresenceChecker,
Set<String> existingVariableNames,
Type targetType ) {
Type targetType,
boolean allwaysIncludeNullCheck,
TypeFactory typeFactory ) {
super( decoratedAssignment, thrownTypesToExclude, sourcePresenceChecker, existingVariableNames, targetType );
this.newCollectionOrMapAssignment = newCollectionOrMapAssignment;
this.allwaysIncludeNullCheck = allwaysIncludeNullCheck;
this.targetType = targetType;
this.typeFactory = typeFactory;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> imported = super.getImportTypes();
if ( newCollectionOrMapAssignment != null ) {
imported.addAll( newCollectionOrMapAssignment.getImportTypes() );
if ( isDirectAssignment() ) {
if ( targetType.getImplementationType() != null ) {
imported.addAll( targetType.getImplementationType().getImportTypes() );
}
else {
imported.addAll( targetType.getImportTypes() );
}
if ( isEnumSet() ) {
imported.add( typeFactory.getType( EnumSet.class ) );
}
}
return imported;
}
public Assignment getNewCollectionOrMapAssignment() {
return newCollectionOrMapAssignment;
public boolean isAllwaysIncludeNullCheck() {
return allwaysIncludeNullCheck;
}
public boolean isDirectAssignment() {
return getType() == DIRECT;
}
public boolean isEnumSet() {
return "java.util.EnumSet".equals( targetType.getFullyQualifiedName() );
}
}

View File

@ -1,21 +0,0 @@
<#--
Copyright 2012-2016 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.
-->
EnumSet.copyOf( <@includeModel object=assignment targetBeanName=ext.targetBeanName targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName targetType=ext.targetType/> )

View File

@ -1,28 +0,0 @@
<#--
Copyright 2012-2016 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.
-->
<@compress single_line=true>
new <#if ext.targetType.implementationType??>
<@includeModel object=ext.targetType.implementationType/>
<#else>
<@includeModel object=ext.targetType/>
</#if>
( <@includeModel object=assignment targetBeanName=ext.targetBeanName targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName targetType=ext.targetType/> )
</@compress>

View File

@ -20,43 +20,43 @@
-->
<#import "../macro/CommonMacros.ftl" as lib>
<@lib.handleExceptions>
<#if ( ext.existingInstanceMapping ) >
<#if ext.existingInstanceMapping>
if ( ${ext.targetBeanName}.${ext.targetReadAccessorName}() != null ) {
<@lib.handleNullCheck>
${ext.targetBeanName}.${ext.targetReadAccessorName}().clear();
${ext.targetBeanName}.${ext.targetReadAccessorName}().<#if ext.targetType.collectionType>addAll<#else>putAll</#if>( ${localVarName} );
</@lib.handleNullCheck>
<#if !ext.defaultValueAssignment??> <#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleNullCheck -->
else {
<#if !ext.defaultValueAssignment?? && !sourcePresenceChecker?? && !allwaysIncludeNullCheck>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleNullCheck -->
${ext.targetBeanName}.${ext.targetWriteAccessorName}( null );
}
</#if>
}
else {
<@lib.handleNullCheck>
<#if newCollectionOrMapAssignment??>
<@_newCollectionOrMapAssignment/>
<#else>
${ext.targetBeanName}.${ext.targetWriteAccessorName}( ${localVarName} );
</#if>
</@lib.handleNullCheck>
<@callTargetWriteAccessor/>
}
<#else>
<@lib.handleNullCheck>
<#if newCollectionOrMapAssignment??>
<@_newCollectionOrMapAssignment/>
<#else>
${ext.targetBeanName}.${ext.targetWriteAccessorName}( ${localVarName} );
</#if>
</@lib.handleNullCheck>
</#if>
<#else>
<@callTargetWriteAccessor/>
</#if>
</@lib.handleExceptions>
<#macro _newCollectionOrMapAssignment>
<@includeModel object=newCollectionOrMapAssignment
targetBeanName=ext.targetBeanName
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
targetType=ext.targetType/>
<#--
assigns the target via the regular target write accessor (usually the setter)
-->
<#macro callTargetWriteAccessor>
<@lib.handleNullCheck>
<#if directAssignment>
${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@wrapLocalVarInCollectionInitializer/> );
<#else>
${ext.targetBeanName}.${ext.targetWriteAccessorName}( ${localVarName} );
</#if>
</@lib.handleNullCheck>
</#macro>
<#--
wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf)
-->
<#macro wrapLocalVarInCollectionInitializer>
<#if enumSet>
EnumSet.copyOf( ${localVarName} )
<#else>
new <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/></#if>( ${localVarName} )
</#if>
</#macro>

View File

@ -0,0 +1,52 @@
/**
* Copyright 2012-2016 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.bugs._913;
import org.mapstruct.InheritConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.factory.Mappers;
/**
*
* @author Sjaak Derksen
*/
@Mapper(uses = Helper.class, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS )
public interface DomainDtoWithNcvsAlwaysMapper {
DomainDtoWithNcvsAlwaysMapper INSTANCE = Mappers.getMapper( DomainDtoWithNcvsAlwaysMapper.class );
@Mappings({
@Mapping(target = "strings", source = "strings"),
@Mapping(target = "longs", source = "strings"),
@Mapping(target = "stringsInitialized", source = "stringsInitialized"),
@Mapping(target = "longsInitialized", source = "stringsInitialized"),
@Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3")
})
Domain create(DtoWithPresenceCheck source);
@InheritConfiguration( name = "create" )
void update(DtoWithPresenceCheck source, @MappingTarget Domain target);
@InheritConfiguration( name = "create" )
Domain updateWithReturn(DtoWithPresenceCheck source, @MappingTarget Domain target);
}

View File

@ -18,6 +18,8 @@
*/
package org.mapstruct.ap.test.bugs._913;
import java.util.HashSet;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -40,11 +42,12 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
DomainDtoWithNvmsNullMapper.class,
DomainDtoWithNvmsDefaultMapper.class,
DomainDtoWithPresenceCheckMapper.class,
DomainDtoWithNcvsAlwaysMapper.class,
Helper.class})
@IssueKey( "913" )
public class Issue913SetterMapperForCollectionsTest {
/**
/**
* The null value mapping strategy on type level (Mapper) should generate forged methods for the
* conversion from string to long that return null in the entire mapper, so also for the forged
* mapper. Note the default NVMS is RETURN_NULL.
@ -70,6 +73,8 @@ public class Issue913SetterMapperForCollectionsTest {
Dto dto = new Dto();
Domain domain = new Domain();
domain.setLongs( new HashSet<Long>() );
domain.setStrings( new HashSet<String>() );
DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain );
doControlAsserts( domain );
@ -92,6 +97,8 @@ public class Issue913SetterMapperForCollectionsTest {
Dto dto = new Dto();
dto.setStringsInitialized( null );
Domain domain = new Domain();
domain.setLongs( new HashSet<Long>() );
domain.setStrings( new HashSet<String>() );
DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain );
assertThat( domain.getStringsInitialized() ).isNull();
@ -112,6 +119,8 @@ public class Issue913SetterMapperForCollectionsTest {
Dto dto = new Dto();
Domain domain1 = new Domain();
domain1.setLongs( new HashSet<Long>() );
domain1.setStrings( new HashSet<String>() );
Domain domain2 = DomainDtoWithNvmsNullMapper.INSTANCE.updateWithReturn( dto, domain1 );
doControlAsserts( domain1, domain2 );
@ -121,7 +130,7 @@ public class Issue913SetterMapperForCollectionsTest {
assertThat( domain2.getLongs() ).isNull();
}
/**
/**
* The null value mapping strategy on type level (Mapper) should generate forged methods for the
* conversion from string to long that return default in the entire mapper, so also for the forged
* mapper. Note the default NVMS is RETURN_NULL.
@ -151,11 +160,15 @@ public class Issue913SetterMapperForCollectionsTest {
Dto dto = new Dto();
Domain domain = new Domain();
Set<Long> longIn = new HashSet<Long>();
domain.setLongs( longIn );
domain.setStrings( new HashSet<String>() );
DomainDtoWithNvmsDefaultMapper.INSTANCE.update( dto, domain );
doControlAsserts( domain );
assertThat( domain.getStrings() ).isNull();
assertThat( domain.getLongs() ).isEmpty();
assertThat( domain.getLongs() ).isSameAs( longIn ); // make sure add all is used.
}
/**
@ -172,6 +185,9 @@ public class Issue913SetterMapperForCollectionsTest {
Dto dto = new Dto();
Domain domain1 = new Domain();
Set<Long> longIn = new HashSet<Long>();
domain1.setLongs( longIn );
domain1.setStrings( new HashSet<String>() );
Domain domain2 = DomainDtoWithNvmsDefaultMapper.INSTANCE.updateWithReturn( dto, domain1 );
doControlAsserts( domain1, domain2 );
@ -180,10 +196,12 @@ public class Issue913SetterMapperForCollectionsTest {
assertThat( domain1.getLongs() ).isEmpty();
assertThat( domain2.getStrings() ).isNull();
assertThat( domain2.getLongs() ).isEmpty();
assertThat( domain1.getLongs() ).isSameAs( longIn ); // make sure that add all is used
assertThat( domain2.getLongs() ).isSameAs( longIn ); // make sure that add all is used
}
/**
* Test create method ICW presence checker
* Test create method ICW presence checker. The presence checker is responsible for the null check.
*
*/
@Test
@ -200,40 +218,109 @@ public class Issue913SetterMapperForCollectionsTest {
/**
* Test update method ICW presence checker
*
* Similar as in regular mappings, the target property should be left as-is.
*
*/
@IssueKey( "#954")
@Test
public void shouldReturnNullForUpdateWithPresenceChecker() {
DtoWithPresenceCheck dto = new DtoWithPresenceCheck();
Domain domain = new Domain();
domain.setLongs( new HashSet<Long>() );
domain.setStrings( new HashSet<String>() );
DomainDtoWithPresenceCheckMapper.INSTANCE.update( dto, domain );
doControlAsserts( domain );
assertThat( domain.getStrings() ).isEmpty();
assertThat( domain.getLongs() ).isEmpty();
}
/**
* Test update with return method ICW presence checker
*
* Similar as in regular mappings, the target property should be left as-is.
*
*/
@IssueKey( "#954")
@Test
public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() {
DtoWithPresenceCheck dto = new DtoWithPresenceCheck();
Domain domain1 = new Domain();
domain1.setLongs( new HashSet<Long>() );
domain1.setStrings( new HashSet<String>() );
Domain domain2 = DomainDtoWithPresenceCheckMapper.INSTANCE.updateWithReturn( dto, domain1 );
doControlAsserts( domain1, domain2 );
assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() );
assertThat( domain1.getStrings() ).isEmpty();
assertThat( domain1.getLongs() ).isEmpty();
assertThat( domain2.getStrings() ).isEmpty();
assertThat( domain2.getLongs() ).isEmpty();
}
/**
* Test create method ICW NullValueCheckStrategy.ALWAYS.
*
*/
@IssueKey( "#954")
@Test
public void shouldReturnNullForCreateWithNcvsAlways() {
DtoWithPresenceCheck dto = new DtoWithPresenceCheck();
Domain domain = DomainDtoWithNcvsAlwaysMapper.INSTANCE.create( dto );
doControlAsserts( domain );
assertThat( domain.getStrings() ).isNull();
assertThat( domain.getLongs() ).isNull();
}
/**
* Test update with return method ICW presence checker
* Test update method ICW presence checker
*
* Similar as in regular mappings, the target property should be left as-is.
*
*/
@IssueKey( "#954")
@Test
public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() {
public void shouldReturnNullForUpdateWithNcvsAlways() {
DtoWithPresenceCheck dto = new DtoWithPresenceCheck();
Domain domain = new Domain();
domain.setLongs( new HashSet<Long>() );
domain.setStrings( new HashSet<String>() );
DomainDtoWithNcvsAlwaysMapper.INSTANCE.update( dto, domain );
doControlAsserts( domain );
assertThat( domain.getStrings() ).isEmpty();
assertThat( domain.getLongs() ).isEmpty();
}
/**
* Test update with return method ICW presence checker
*
* Similar as in regular mappings, the target property should be left as-is.
*
*/
@IssueKey( "#954")
@Test
public void shouldReturnNullForUpdateWithReturnWithNcvsAlways() {
DtoWithPresenceCheck dto = new DtoWithPresenceCheck();
Domain domain1 = new Domain();
Domain domain2 = DomainDtoWithPresenceCheckMapper.INSTANCE.updateWithReturn( dto, domain1 );
domain1.setLongs( new HashSet<Long>() );
domain1.setStrings( new HashSet<String>() );
Domain domain2 = DomainDtoWithNcvsAlwaysMapper.INSTANCE.updateWithReturn( dto, domain1 );
doControlAsserts( domain1, domain2 );
assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() );
assertThat( domain1.getStrings() ).isNull();
assertThat( domain1.getLongs() ).isNull();
assertThat( domain2.getStrings() ).isNull();
assertThat( domain2.getLongs() ).isNull();
assertThat( domain1.getStrings() ).isEmpty();
assertThat( domain1.getLongs() ).isEmpty();
assertThat( domain2.getStrings() ).isEmpty();
assertThat( domain2.getLongs() ).isEmpty();
}
/**
* These assert check if non-null and default mapping is working as expected.
*