From 9dd8c179e338295482be65176f01baa7054eee56 Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sun, 7 Jul 2013 14:44:41 +0200 Subject: [PATCH] #26 Mapping between String iterables and Date iterables --- .../java/org/mapstruct/IterableMapping.java | 44 +++++++++++++ .../org/mapstruct/ap/MappingProcessor.java | 4 +- .../ap/model/IterableMappingMethod.java | 11 ++-- .../ap/model/source/IterableMapping.java | 62 +++++++++++++++++++ .../org/mapstruct/ap/model/source/Method.java | 21 +++++-- .../ap/processor/MapperCreationProcessor.java | 41 +++++++----- .../processor/MethodRetrievalProcessor.java | 12 +++- ...pstruct.ap.model.IterableMappingMethod.ftl | 15 ++++- .../conversion/date/DateConversionTest.java | 31 ++++++++++ .../conversion/date/SourceTargetMapper.java | 9 +++ 10 files changed, 220 insertions(+), 30 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/IterableMapping.java create mode 100644 processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java diff --git a/core/src/main/java/org/mapstruct/IterableMapping.java b/core/src/main/java/org/mapstruct/IterableMapping.java new file mode 100644 index 000000000..d59adfa3d --- /dev/null +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -0,0 +1,44 @@ +/** + * Copyright 2012-2013 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Configures the mapping between two iterable types, e.g. {@code List} and {@code List}. + * + * @author Gunnar Morling + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface IterableMapping { + + /** + * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from an iterable of + * {@code String} to an iterable {@link Date} or vice-versa. Will be ignored for all other element types. + * + * @return A date format string as processable by {@link SimpleDateFormat}. + */ + String dateFormat(); +} diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index b877bcfb7..212b5b4ec 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -39,6 +39,7 @@ import javax.lang.model.util.ElementKindVisitor6; import net.java.dev.hickory.prism.GeneratePrism; import net.java.dev.hickory.prism.GeneratePrisms; +import org.mapstruct.IterableMapping; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.ap.model.Mapper; @@ -72,7 +73,8 @@ import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext; @GeneratePrisms({ @GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true), @GeneratePrism(value = Mapping.class, publicAccess = true), - @GeneratePrism(value = Mappings.class, publicAccess = true) + @GeneratePrism(value = Mappings.class, publicAccess = true), + @GeneratePrism(value = IterableMapping.class, publicAccess = true) }) @SupportedOptions({ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.UNMAPPED_TARGET_POLICY }) public class MappingProcessor extends AbstractProcessor { diff --git a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java index 938fc1ccb..2cbe4f2af 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -28,22 +28,21 @@ package org.mapstruct.ap.model; public class IterableMappingMethod extends MappingMethod { private final MappingMethodReference elementMappingMethod; - private final String toConversion; + private final TypeConversion conversion; public IterableMappingMethod(String name, String parameterName, Type sourceType, Type targetType, - MappingMethodReference elementMappingMethod, - String toConversion) { + MappingMethodReference elementMappingMethod, TypeConversion conversion) { super( name, parameterName, sourceType, targetType ); this.elementMappingMethod = elementMappingMethod; - this.toConversion = toConversion; + this.conversion = conversion; } public MappingMethodReference getElementMappingMethod() { return elementMappingMethod; } - public String getToConversion() { - return toConversion; + public TypeConversion getConversion() { + return conversion; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java new file mode 100644 index 000000000..e2cdcb460 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java @@ -0,0 +1,62 @@ +/** + * Copyright 2012-2013 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.model.source; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; + +import org.mapstruct.ap.IterableMappingPrism; + +/** + * Represents an iterable mapping as configured via {@code @IterableMapping}. + * + * @author Gunnar Morling + */ +public class IterableMapping { + + private final String dateFormat; + private final AnnotationMirror mirror; + private final AnnotationValue dateFormatAnnotationValue; + + public static IterableMapping fromIterableMappingPrism(IterableMappingPrism iterableMapping) { + return new IterableMapping( + iterableMapping.dateFormat(), + iterableMapping.mirror, + iterableMapping.values.dateFormat() + ); + } + + private IterableMapping(String dateFormat, AnnotationMirror mirror, AnnotationValue dateFormatAnnotationValue) { + this.dateFormat = dateFormat; + this.mirror = mirror; + this.dateFormatAnnotationValue = dateFormatAnnotationValue; + } + + public String getDateFormat() { + return dateFormat; + } + + public AnnotationMirror getMirror() { + return mirror; + } + + public AnnotationValue getDateFormatAnnotationValue() { + return dateFormatAnnotationValue; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java index 087e06146..fdf466c90 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java @@ -38,10 +38,12 @@ public class Method { private final Type sourceType; private final Type targetType; private Map mappings; + private IterableMapping iterableMapping; public static Method forMethodRequiringImplementation(ExecutableElement executable, String parameterName, Type sourceType, Type targetType, - Map mappings) { + Map mappings, + IterableMapping iterableMapping) { return new Method( null, @@ -49,7 +51,8 @@ public class Method { parameterName, sourceType, targetType, - mappings + mappings, + iterableMapping ); } @@ -62,18 +65,20 @@ public class Method { parameterName, sourceType, targetType, - Collections.emptyMap() + Collections.emptyMap(), + null ); } private Method(Type declaringMapper, ExecutableElement executable, String parameterName, Type sourceType, - Type targetType, Map mappings) { + Type targetType, Map mappings, IterableMapping iterableMapping) { this.declaringMapper = declaringMapper; this.executable = executable; this.parameterName = parameterName; this.sourceType = sourceType; this.targetType = targetType; this.mappings = mappings; + this.iterableMapping = iterableMapping; } /** @@ -115,6 +120,14 @@ public class Method { this.mappings = mappings; } + public IterableMapping getIterableMapping() { + return iterableMapping; + } + + public void setIterableMapping(IterableMapping iterableMapping) { + this.iterableMapping = iterableMapping; + } + public boolean reverses(Method method) { return equals( sourceType, method.getTargetType() ) && 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 ed2ad78bf..3a434e140 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -50,6 +50,8 @@ import org.mapstruct.ap.model.Options; import org.mapstruct.ap.model.PropertyMapping; import org.mapstruct.ap.model.ReportingPolicy; import org.mapstruct.ap.model.Type; +import org.mapstruct.ap.model.TypeConversion; +import org.mapstruct.ap.model.source.IterableMapping; import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.util.Executables; @@ -154,17 +156,21 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Method method) { - String toConversionString = getIterableConversionString( + TypeConversion conversion = getIterableConversion( conversions, method.getSourceType().getElementType(), method.getTargetType().getElementType(), - true + method.getIterableMapping() ); return new IterableMappingMethod( @@ -365,22 +371,25 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Type parameterType, diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index c5b432f6d..7df517aed 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -30,10 +30,12 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; +import org.mapstruct.ap.IterableMappingPrism; import org.mapstruct.ap.MapperPrism; import org.mapstruct.ap.MappingPrism; import org.mapstruct.ap.MappingsPrism; import org.mapstruct.ap.model.Type; +import org.mapstruct.ap.model.source.IterableMapping; import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.Parameter; @@ -125,7 +127,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor${targetType.iterableImplementationType.name}<#else>${targetType.name}<${targetType.elementType.name}> ${targetType.name?uncap_first} = new <#if targetType.iterableImplementationType??>${targetType.iterableImplementationType.name}<#else>${targetType.name}<${targetType.elementType.name}>(); for ( ${sourceType.elementType.name} ${sourceType.elementType.name?uncap_first} : ${parameterName} ) { - <#if toConversion??> - ${targetType.name?uncap_first}.add( ${toConversion} ); + <#if conversion??> + <#if (conversion.exceptionTypes?size == 0) > + ${targetType.name?uncap_first}.add( <@includeModel object=conversion/> ); + <#else> + try { + ${targetType.name?uncap_first}.add( <@includeModel object=conversion/> ); + } + <#list conversion.exceptionTypes as exceptionType> + catch( ${exceptionType.name} e ) { + throw new RuntimeException( e ); + } + + <#else> ${targetType.name?uncap_first}.add( ${elementMappingMethod.name}( ${sourceType.elementType.name?uncap_first} ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 9b6d5caa6..d1bd94524 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -18,7 +18,10 @@ */ package org.mapstruct.ap.test.conversion.date; +import java.util.Arrays; +import java.util.Date; import java.util.GregorianCalendar; +import java.util.List; import java.util.Locale; import org.mapstruct.ap.testutil.IssueKey; @@ -72,4 +75,32 @@ public class DateConversionTest extends MapperTestBase { assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, 6, 6 ).getTime() ); assertThat( source.getAnotherDate() ).isEqualTo( new GregorianCalendar( 2013, 1, 14, 8, 30 ).getTime() ); } + + @Test + public void shouldApplyStringConversionForIterableMethod() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + ); + + List stringDates = SourceTargetMapper.INSTANCE.stringListToDateList( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).containsExactly( "06.07.2013", "14.02.2013", "11.04.2013" ); + } + + @Test + public void shouldApplyStringConversionForReverseIterableMethod() { + List stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" ); + + List dates = SourceTargetMapper.INSTANCE.dateListToStringList( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java index 1e0bb1a8b..cdcc52739 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java @@ -18,6 +18,10 @@ */ package org.mapstruct.ap.test.conversion.date; +import java.util.Date; +import java.util.List; + +import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; import org.mapstruct.Mappers; import org.mapstruct.Mapping; @@ -31,4 +35,9 @@ public interface SourceTargetMapper { Target sourceToTarget(Source source); Source targetToSource(Target target); + + @IterableMapping(dateFormat = "dd.MM.yyyy") + List stringListToDateList(List dates); + + List dateListToStringList(List strings); }