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

View File

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

View File

@ -159,6 +159,21 @@ public class MappingResolver {
if ( mappingMethodReference != null ) { if ( mappingMethodReference != null ) {
assignment = new TargetAssignment( mappingMethodReference ); 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; 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. * 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, private MethodReference resolveViaMethod( SourceMethod mappingMethod,
String mappedElement, String mappedElement,
@ -241,17 +246,6 @@ public class MappingResolver {
* <li>there is a method from B to C, methodY</li> * <li>there is a method from B to C, methodY</li>
* </ul> * </ul>
* then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) ) * 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, private MethodReference resolveViaMethodAndMethod( SourceMethod mappingMethod,
String mappedElement, String mappedElement,
@ -311,6 +305,63 @@ public class MappingResolver {
return methodRefY; 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, private <T extends Method> T getBestMatch( SourceMethod mappingMethod,
String mappedElement, String mappedElement,
List<T> methods, List<T> methods,

View File

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

View File

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