diff --git a/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java index fda7f6fc7..57f5ea683 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java @@ -38,11 +38,12 @@ public class MethodReference extends MappingMethod { private final Set importTypes; /** - * A reference to another mapping method in case this is a two-step mapping, e.g. from {@code JAXBElement} 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} 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 getImportTypes() { Set 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; } } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index ec1204f2f..cf7e49944 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -643,7 +643,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences, List methods, @@ -728,7 +728,7 @@ public class MapperCreationProcessor implements ModelElementProcessorthere is a method from B to C, methodY * * 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: + *
    + *
  • there is a conversion from A to B, conversionX
  • + *
  • there is a method from B to C, methodY
  • + *
+ * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) + */ + private MethodReference resolveViaConversionAndMethod( SourceMethod mappingMethod, + String mappedElement, + List mapperReferences, + List methods, + Type sourceType, + Type targetType, + String targetPropertyName, + String dateFormat, + String sourceReference ) { + + List methodYCandidates = new ArrayList( 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 getBestMatch( SourceMethod mappingMethod, String mappedElement, List methods, diff --git a/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl b/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl index 0cf63f5ef..e089f00c6 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl @@ -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} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java index 16f4498b5..b42407e97 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java @@ -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> dates = new ArrayList>(); dates.add( - new JAXBElement( - QNAME, - XMLGregorianCalendar.class, - createXmlCal( 1999, 3, 2 ) - ) + new JAXBElement( + QNAME, + XMLGregorianCalendar.class, + createXmlCal( 1999, 3, 2 ) + ) ); dates.add( - new JAXBElement( - QNAME, - XMLGregorianCalendar.class, - createXmlCal( 2004, 7, 28 ) - ) + new JAXBElement( + QNAME, + XMLGregorianCalendar.class, + createXmlCal( 2004, 7, 28 ) + ) ); List> description = new ArrayList>(); @@ -92,11 +124,11 @@ public class NestedMappingMethodInvocationTest { OrderType orderType = new OrderType(); orderType.setOrderNumber( new JAXBElement( QNAME, Long.class, 5L ) ); orderType.setOrderDetails( - new JAXBElement( - QNAME, - OrderDetailsType.class, - new OrderDetailsType() - ) + new JAXBElement( + QNAME, + OrderDetailsType.class, + new OrderDetailsType() + ) ); orderType.getOrderDetails().getValue().setName( new JAXBElement( 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; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java new file mode 100644 index 000000000..5757e813c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java @@ -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 createDate(String date) { + return new JAXBElement( new QName( "dont-care" ), String.class, "06.07.2013" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/OrderTypeToOrderDtoMapper.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTargetMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/OrderTypeToOrderDtoMapper.java index 5299a7e71..091db7d30 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/OrderTypeToOrderDtoMapper.java @@ -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); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceType.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceType.java new file mode 100644 index 000000000..1ddd7f436 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceType.java @@ -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 date; + + public JAXBElement getDate() { + return date; + } + + public void setDate( JAXBElement date ) { + this.date = date; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java new file mode 100644 index 000000000..0245dd1e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java @@ -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); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/TargetDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/TargetDto.java new file mode 100644 index 000000000..27811da50 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/TargetDto.java @@ -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; + } +}