#157 implementing mapping methodY( conversionX ( parameter ) )

This commit is contained in:
sjaakd 2014-03-22 20:11:56 +01:00
parent 9c17909fd6
commit 3c58551649
10 changed files with 315 additions and 64 deletions

View File

@ -38,11 +38,12 @@ public class MethodReference extends MappingMethod {
private final Set<Type> importTypes;
/**
* A reference to another mapping method in case this is a two-step mapping, e.g. from {@code JAXBElement<Bar>} to
* {@code Foo} to for which a nested method call will be generated:
* A reference to another mapping method or typeConversion in case this is a two-step mapping, e.g. from
* {@code JAXBElement<Bar>} to {@code Foo} to for which a nested method call will be generated:
* {@code setFoo(barToFoo( jaxbElemToValue( bar) ) )}
*/
private MethodReference methodRefChild;
private TypeConversion typeConversion;
/**
* In case this reference targets a built-in method, allows to pass specific context information to the invoked
@ -107,6 +108,14 @@ public class MethodReference extends MappingMethod {
return methodRefChild;
}
public void setTypeConversionChild( TypeConversion typeConversion ) {
this.typeConversion = typeConversion;
}
public TypeConversion getTypeConversion() {
return typeConversion;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> imported = super.getImportTypes();
@ -114,6 +123,9 @@ public class MethodReference extends MappingMethod {
if ( methodRefChild != null ) {
imported.addAll( methodRefChild.getImportTypes() );
}
else if ( typeConversion != null ) {
imported.addAll( typeConversion.getImportTypes() );
}
return imported;
}
}

View File

@ -643,7 +643,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String mappedElement = "property '" + Executables.getPropertyName( sourceAccessor ) + "'";
TargetAssignment parameterAssignment = mappingResolver.getTargetAssignment(
TargetAssignment assignment = mappingResolver.getTargetAssignment(
method,
mappedElement,
mapperReferences,
@ -663,7 +663,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
Executables.getPropertyName( targetAcessor ),
targetAcessor.getSimpleName().toString(),
targetType,
parameterAssignment
assignment
);
if ( !isPropertyMappable( property ) ) {
@ -690,7 +690,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String dateFormat = method.getIterableMapping() != null ? method.getIterableMapping().getDateFormat() : null;
String conversionStr = Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() );
TargetAssignment parameterAssignment = mappingResolver.getTargetAssignment(
TargetAssignment assignment = mappingResolver.getTargetAssignment(
method,
"collection element",
mapperReferences,
@ -702,7 +702,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
conversionStr
);
if ( parameterAssignment == null ) {
if ( assignment == null ) {
messager.printMessage(
Kind.ERROR,
String.format(
@ -715,7 +715,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
}
MethodReference factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() );
return new IterableMappingMethod( method, parameterAssignment, factoryMethod );
return new IterableMappingMethod( method, assignment, factoryMethod );
}
private MapMappingMethod getMapMappingMethod(List<MapperReference> mapperReferences, List<SourceMethod> methods,
@ -728,7 +728,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
Type keyTargetType = resultTypeParams.get( 0 );
String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null;
TargetAssignment parameterAssignmentKey = mappingResolver.getTargetAssignment(
TargetAssignment keyAssignment = mappingResolver.getTargetAssignment(
method,
"map key",
mapperReferences,
@ -740,7 +740,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
"entry.getKey()"
);
if ( parameterAssignmentKey == null ) {
if ( keyAssignment == null ) {
messager.printMessage(
Kind.ERROR,
String.format(
@ -757,7 +757,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
Type valueTargetType = resultTypeParams.get( 1 );
String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null;
TargetAssignment parameterAssignmentValue = mappingResolver.getTargetAssignment(
TargetAssignment valueAssignment = mappingResolver.getTargetAssignment(
method,
"map value",
mapperReferences,
@ -769,7 +769,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
"entry.getValue()"
);
if ( parameterAssignmentValue == null ) {
if ( valueAssignment == null ) {
messager.printMessage(
Kind.ERROR,
String.format(
@ -782,7 +782,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
}
MethodReference factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() );
return new MapMappingMethod( method, parameterAssignmentKey, parameterAssignmentValue, factoryMethod );
return new MapMappingMethod( method, keyAssignment, valueAssignment, factoryMethod );
}
private EnumMappingMethod getEnumMappingMethod(SourceMethod method) {

View File

@ -159,6 +159,21 @@ public class MappingResolver {
if ( mappingMethodReference != null ) {
assignment = new TargetAssignment( mappingMethodReference );
}
else {
mappingMethodReference = resolveViaConversionAndMethod(
mappingMethod,
mappedElement,
mapperReferences,
methods,
sourceType,
targetType,
targetPropertyName,
dateFormat,
sourceReference );
}
if ( mappingMethodReference != null ) {
assignment = new TargetAssignment( mappingMethodReference );
}
}
}
return assignment;
@ -184,16 +199,6 @@ public class MappingResolver {
/**
* Returns a reference to a method mapping the given source type to the given target type, if such a method exists.
*
* @param mappingMethod target mapping method
* @param mappedElement used for error messages
* @param mapperReferences list of references to mapper
* @param methods list of candidate methods
* @param sourceType parameter to match
* @param targetType return type to match
* @param targetPropertyName name of the target property
* @param dateFormat used for formatting dates in build in methods that need context information
*
* @return a method reference.
*/
private MethodReference resolveViaMethod( SourceMethod mappingMethod,
String mappedElement,
@ -241,17 +246,6 @@ public class MappingResolver {
* <li>there is a method from B to C, methodY</li>
* </ul>
* then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) )
*
* @param mappingMethod target mapping method
* @param mappedElement used for error messages
* @param mapperReferences list of references to mapper
* @param methods list of candidate methods
* @param sourceType parameter to match
* @param targetType return type to match
* @param targetPropertyName name of the target property
* @param dateFormat used for formatting dates in build in methods that need context information
*
* @return a method reference.
*/
private MethodReference resolveViaMethodAndMethod( SourceMethod mappingMethod,
String mappedElement,
@ -311,6 +305,63 @@ public class MappingResolver {
return methodRefY;
}
/**
* Suppose mapping required from A to C and:
* <ul>
* <li>there is a conversion from A to B, conversionX</li>
* <li>there is a method from B to C, methodY</li>
* </ul>
* then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) )
*/
private MethodReference resolveViaConversionAndMethod( SourceMethod mappingMethod,
String mappedElement,
List<MapperReference> mapperReferences,
List<SourceMethod> methods,
Type sourceType,
Type targetType,
String targetPropertyName,
String dateFormat,
String sourceReference ) {
List<Method> methodYCandidates = new ArrayList<Method>( methods );
methodYCandidates.addAll( builtInMethods.getBuiltInMethods() );
MethodReference methodRefY = null;
for ( Method methodYCandidate : methodYCandidates ) {
if ( methodYCandidate.getSourceParameters().size() == 1 ) {
methodRefY = resolveViaMethod(
mappingMethod,
mappedElement,
mapperReferences,
methods,
methodYCandidate.getSourceParameters().get( 0 ).getType(),
targetType,
targetPropertyName,
dateFormat
);
if ( methodRefY != null ) {
TypeConversion conversionXRef = resolveViaConversion(
sourceType,
methodYCandidate.getSourceParameters().get( 0 ).getType(),
dateFormat,
sourceReference
);
if ( conversionXRef != null ) {
methodRefY.setTypeConversionChild( conversionXRef );
break;
}
else {
// both should match;
methodRefY = null;
}
}
}
}
return methodRefY;
}
private <T extends Method> T getBestMatch( SourceMethod mappingMethod,
String mappedElement,
List<T> methods,

View File

@ -28,8 +28,11 @@
${ext.targetType}.class
<#else>
<#if methodRefChild??>
<#-- the nested case -->
<#-- the nested case: another method -->
<@includeModel object=methodRefChild source=ext.source targetType=singleSourceParameterType.name/>
<#elseif typeConversion??>
<#-- the nested case: a type conversion -->
<@includeModel object=typeConversion/>
<#else>
<#-- the non nested case -->
${ext.source}

View File

@ -21,7 +21,9 @@ package org.mapstruct.ap.test.nestedmethodcall;
import static org.fest.assertions.Assertions.assertThat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import javax.xml.bind.JAXBElement;
import javax.xml.datatype.DatatypeConfigurationException;
@ -29,6 +31,7 @@ import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -42,21 +45,33 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
* @author Sjaak Derksen
*/
@IssueKey("134")
@WithClasses({
SourceTargetMapper.class,
OrderDto.class,
OrderDetailsDto.class,
OrderDetailsType.class,
OrderType.class
})
//@WithClasses({
// SourceTypeTargetDtoMapper.class,
// OrderDto.class,
// OrderDetailsDto.class,
// OrderDetailsType.class,
// OrderType.class
//})
@RunWith(AnnotationProcessorTestRunner.class)
public class NestedMappingMethodInvocationTest {
private static final QName QNAME = new QName( "dont-care" );
public static final QName QNAME = new QName( "dont-care" );
@Before
public void setDefaultLocale() {
Locale.setDefault( Locale.GERMAN );
}
@Test
public void shouldGeneratedNestedMappingMethodCalls() throws DatatypeConfigurationException {
SourceTargetMapper instance = SourceTargetMapper.INSTANCE;
@WithClasses( {
OrderTypeToOrderDtoMapper.class,
OrderDto.class,
OrderDetailsDto.class,
OrderDetailsType.class,
OrderType.class
} )
public void shouldMapViaMethodAndMethod() throws DatatypeConfigurationException {
OrderTypeToOrderDtoMapper instance = OrderTypeToOrderDtoMapper.INSTANCE;
OrderDto target = instance.sourceToTarget( createOrderType() );
assertThat( target ).isNotNull();
@ -68,21 +83,38 @@ public class NestedMappingMethodInvocationTest {
assertThat( target.getOrderDetails().getDescription() ).containsExactly( "elem1", "elem2" );
}
@Test
@WithClasses( {
SourceTypeTargetDtoMapper.class,
SourceType.class,
ObjectFactory.class,
TargetDto.class
} )
public void shouldMapViaConversionAndMethod() throws DatatypeConfigurationException {
SourceTypeTargetDtoMapper instance = SourceTypeTargetDtoMapper.INSTANCE;
SourceType source = instance.targetToSource( createTarget() );
assertThat( source ).isNotNull();
assertThat( source.getDate().getValue() ).isEqualTo( "06.07.2013" );
assertThat( source.getDate().getName()).isEqualTo( QNAME );
}
private OrderType createOrderType() throws DatatypeConfigurationException {
List<JAXBElement<XMLGregorianCalendar>> dates = new ArrayList<JAXBElement<XMLGregorianCalendar>>();
dates.add(
new JAXBElement<XMLGregorianCalendar>(
QNAME,
XMLGregorianCalendar.class,
createXmlCal( 1999, 3, 2 )
)
new JAXBElement<XMLGregorianCalendar>(
QNAME,
XMLGregorianCalendar.class,
createXmlCal( 1999, 3, 2 )
)
);
dates.add(
new JAXBElement<XMLGregorianCalendar>(
QNAME,
XMLGregorianCalendar.class,
createXmlCal( 2004, 7, 28 )
)
new JAXBElement<XMLGregorianCalendar>(
QNAME,
XMLGregorianCalendar.class,
createXmlCal( 2004, 7, 28 )
)
);
List<JAXBElement<String>> description = new ArrayList<JAXBElement<String>>();
@ -92,11 +124,11 @@ public class NestedMappingMethodInvocationTest {
OrderType orderType = new OrderType();
orderType.setOrderNumber( new JAXBElement<Long>( QNAME, Long.class, 5L ) );
orderType.setOrderDetails(
new JAXBElement<OrderDetailsType>(
QNAME,
OrderDetailsType.class,
new OrderDetailsType()
)
new JAXBElement<OrderDetailsType>(
QNAME,
OrderDetailsType.class,
new OrderDetailsType()
)
);
orderType.getOrderDetails().getValue().setName( new JAXBElement<String>( QNAME, String.class, "test" ) );
orderType.getOrderDetails().getValue().setDescription( description );
@ -105,9 +137,15 @@ public class NestedMappingMethodInvocationTest {
return orderType;
}
private XMLGregorianCalendar createXmlCal(int year, int month, int day)
throws DatatypeConfigurationException {
private XMLGregorianCalendar createXmlCal( int year, int month, int day )
throws DatatypeConfigurationException {
return DatatypeFactory.newInstance()
.newXMLGregorianCalendarDate( year, month, day, DatatypeConstants.FIELD_UNDEFINED );
.newXMLGregorianCalendarDate( year, month, day, DatatypeConstants.FIELD_UNDEFINED );
}
private TargetDto createTarget() {
TargetDto target = new TargetDto();
target.setDate( new GregorianCalendar( 2013, 6, 6 ).getTime() );
return target;
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright 2012-2014 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.nestedmethodcall;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
/**
*
* @author Sjaak Derksen
*/
public class ObjectFactory {
public JAXBElement<String> createDate(String date) {
return new JAXBElement<String>( new QName( "dont-care" ), String.class, "06.07.2013" );
}
}

View File

@ -32,9 +32,9 @@ import org.mapstruct.factory.Mappers;
* @author Sjaak Derksen
*/
@Mapper
public interface SourceTargetMapper {
public interface OrderTypeToOrderDtoMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
OrderTypeToOrderDtoMapper INSTANCE = Mappers.getMapper( OrderTypeToOrderDtoMapper.class );
OrderDto sourceToTarget(OrderType source);

View File

@ -0,0 +1,38 @@
/**
* Copyright 2012-2014 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.nestedmethodcall;
import javax.xml.bind.JAXBElement;
/**
* @author Sjaak Derksen
*/
public class SourceType {
private JAXBElement<String> date;
public JAXBElement<String> getDate() {
return date;
}
public void setDate( JAXBElement<String> date ) {
this.date = date;
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2012-2014 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.nestedmethodcall;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper( uses = ObjectFactory.class )
public interface SourceTypeTargetDtoMapper {
SourceTypeTargetDtoMapper INSTANCE = Mappers.getMapper( SourceTypeTargetDtoMapper.class );
@Mapping(source = "date", target = "date", dateFormat = "dd.MM.yyyy")
SourceType targetToSource( TargetDto source );
// TargetDto sourceToTarget(SourceType source);
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2012-2014 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.nestedmethodcall;
import java.util.Date;
/**
* @author Sjaak Derksen
*/
public class TargetDto {
private Date date;
public Date getDate() {
return date;
}
public void setDate( Date date ) {
this.date = date;
}
}