#26 Mapping between String iterables and Date iterables

This commit is contained in:
Gunnar Morling 2013-07-07 14:44:41 +02:00
parent abba4fb716
commit 9dd8c179e3
10 changed files with 220 additions and 30 deletions

View File

@ -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<String>} and {@code List<Date>}.
*
* @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();
}

View File

@ -39,6 +39,7 @@ import javax.lang.model.util.ElementKindVisitor6;
import net.java.dev.hickory.prism.GeneratePrism; import net.java.dev.hickory.prism.GeneratePrism;
import net.java.dev.hickory.prism.GeneratePrisms; import net.java.dev.hickory.prism.GeneratePrisms;
import org.mapstruct.IterableMapping;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.ap.model.Mapper; import org.mapstruct.ap.model.Mapper;
@ -72,7 +73,8 @@ import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext;
@GeneratePrisms({ @GeneratePrisms({
@GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true), @GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true),
@GeneratePrism(value = Mapping.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 }) @SupportedOptions({ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.UNMAPPED_TARGET_POLICY })
public class MappingProcessor extends AbstractProcessor { public class MappingProcessor extends AbstractProcessor {

View File

@ -28,22 +28,21 @@ package org.mapstruct.ap.model;
public class IterableMappingMethod extends MappingMethod { public class IterableMappingMethod extends MappingMethod {
private final MappingMethodReference elementMappingMethod; private final MappingMethodReference elementMappingMethod;
private final String toConversion; private final TypeConversion conversion;
public IterableMappingMethod(String name, String parameterName, Type sourceType, Type targetType, public IterableMappingMethod(String name, String parameterName, Type sourceType, Type targetType,
MappingMethodReference elementMappingMethod, MappingMethodReference elementMappingMethod, TypeConversion conversion) {
String toConversion) {
super( name, parameterName, sourceType, targetType ); super( name, parameterName, sourceType, targetType );
this.elementMappingMethod = elementMappingMethod; this.elementMappingMethod = elementMappingMethod;
this.toConversion = toConversion; this.conversion = conversion;
} }
public MappingMethodReference getElementMappingMethod() { public MappingMethodReference getElementMappingMethod() {
return elementMappingMethod; return elementMappingMethod;
} }
public String getToConversion() { public TypeConversion getConversion() {
return toConversion; return conversion;
} }
@Override @Override

View File

@ -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;
}
}

View File

@ -38,10 +38,12 @@ public class Method {
private final Type sourceType; private final Type sourceType;
private final Type targetType; private final Type targetType;
private Map<String, Mapping> mappings; private Map<String, Mapping> mappings;
private IterableMapping iterableMapping;
public static Method forMethodRequiringImplementation(ExecutableElement executable, String parameterName, public static Method forMethodRequiringImplementation(ExecutableElement executable, String parameterName,
Type sourceType, Type targetType, Type sourceType, Type targetType,
Map<String, Mapping> mappings) { Map<String, Mapping> mappings,
IterableMapping iterableMapping) {
return new Method( return new Method(
null, null,
@ -49,7 +51,8 @@ public class Method {
parameterName, parameterName,
sourceType, sourceType,
targetType, targetType,
mappings mappings,
iterableMapping
); );
} }
@ -62,18 +65,20 @@ public class Method {
parameterName, parameterName,
sourceType, sourceType,
targetType, targetType,
Collections.<String, Mapping>emptyMap() Collections.<String, Mapping>emptyMap(),
null
); );
} }
private Method(Type declaringMapper, ExecutableElement executable, String parameterName, Type sourceType, private Method(Type declaringMapper, ExecutableElement executable, String parameterName, Type sourceType,
Type targetType, Map<String, Mapping> mappings) { Type targetType, Map<String, Mapping> mappings, IterableMapping iterableMapping) {
this.declaringMapper = declaringMapper; this.declaringMapper = declaringMapper;
this.executable = executable; this.executable = executable;
this.parameterName = parameterName; this.parameterName = parameterName;
this.sourceType = sourceType; this.sourceType = sourceType;
this.targetType = targetType; this.targetType = targetType;
this.mappings = mappings; this.mappings = mappings;
this.iterableMapping = iterableMapping;
} }
/** /**
@ -115,6 +120,14 @@ public class Method {
this.mappings = mappings; this.mappings = mappings;
} }
public IterableMapping getIterableMapping() {
return iterableMapping;
}
public void setIterableMapping(IterableMapping iterableMapping) {
this.iterableMapping = iterableMapping;
}
public boolean reverses(Method method) { public boolean reverses(Method method) {
return return
equals( sourceType, method.getTargetType() ) && equals( sourceType, method.getTargetType() ) &&

View File

@ -50,6 +50,8 @@ import org.mapstruct.ap.model.Options;
import org.mapstruct.ap.model.PropertyMapping; import org.mapstruct.ap.model.PropertyMapping;
import org.mapstruct.ap.model.ReportingPolicy; import org.mapstruct.ap.model.ReportingPolicy;
import org.mapstruct.ap.model.Type; 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.Mapping;
import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.Executables;
@ -154,17 +156,21 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
continue; continue;
} }
if ( method.getMappings().isEmpty() ) { Method reverseMappingMethod = getReverseMappingMethod( methods, method );
Method reverseMappingMethod = getReverseMappingMethod( methods, method );
if ( reverseMappingMethod != null && !reverseMappingMethod.getMappings().isEmpty() ) {
method.setMappings( reverse( reverseMappingMethod.getMappings() ) );
}
}
if ( method.isIterableMapping() ) { if ( method.isIterableMapping() ) {
if ( method.getIterableMapping() == null && reverseMappingMethod != null &&
reverseMappingMethod.getIterableMapping() != null ) {
method.setIterableMapping( reverseMappingMethod.getIterableMapping() );
}
mappingMethods.add( getIterableMappingMethod( methods, method ) ); mappingMethods.add( getIterableMappingMethod( methods, method ) );
} }
else { else {
if ( method.getMappings().isEmpty() ) {
if ( reverseMappingMethod != null && !reverseMappingMethod.getMappings().isEmpty() ) {
method.setMappings( reverse( reverseMappingMethod.getMappings() ) );
}
}
mappingMethods.add( getBeanMappingMethod( methods, method, unmappedTargetPolicy ) ); mappingMethods.add( getBeanMappingMethod( methods, method, unmappedTargetPolicy ) );
} }
} }
@ -348,11 +354,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
} }
private MappingMethod getIterableMappingMethod(List<Method> methods, Method method) { private MappingMethod getIterableMappingMethod(List<Method> methods, Method method) {
String toConversionString = getIterableConversionString( TypeConversion conversion = getIterableConversion(
conversions, conversions,
method.getSourceType().getElementType(), method.getSourceType().getElementType(),
method.getTargetType().getElementType(), method.getTargetType().getElementType(),
true method.getIterableMapping()
); );
return new IterableMappingMethod( return new IterableMappingMethod(
@ -365,22 +371,25 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
method.getSourceType().getElementType(), method.getSourceType().getElementType(),
method.getTargetType().getElementType() method.getTargetType().getElementType()
), ),
toConversionString conversion
); );
} }
private String getIterableConversionString(Conversions conversions, Type sourceElementType, Type targetElementType, private TypeConversion getIterableConversion(Conversions conversions, Type sourceElementType,
boolean isToConversion) { Type targetElementType, IterableMapping iterableMapping) {
ConversionProvider conversion = conversions.getConversion( sourceElementType, targetElementType ); ConversionProvider conversionProvider = conversions.getConversion( sourceElementType, targetElementType );
if ( conversion == null ) { if ( conversionProvider == null ) {
return null; return null;
} }
return conversion.to( return conversionProvider.to(
Introspector.decapitalize( sourceElementType.getName() ), Introspector.decapitalize( sourceElementType.getName() ),
new DefaultConversionContext( targetElementType, null ) new DefaultConversionContext(
).getConversionString(); targetElementType,
iterableMapping != null ? iterableMapping.getDateFormat() : null
)
);
} }
private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType, private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType,

View File

@ -30,10 +30,12 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.IterableMappingPrism;
import org.mapstruct.ap.MapperPrism; import org.mapstruct.ap.MapperPrism;
import org.mapstruct.ap.MappingPrism; import org.mapstruct.ap.MappingPrism;
import org.mapstruct.ap.MappingsPrism; import org.mapstruct.ap.MappingsPrism;
import org.mapstruct.ap.model.Type; 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.Mapping;
import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.Parameter; import org.mapstruct.ap.model.source.Parameter;
@ -125,7 +127,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
parameter.getName(), parameter.getName(),
parameter.getType(), parameter.getType(),
returnType, returnType,
getMappings( method ) getMappings( method ),
getIterableMapping( method )
); );
} }
else { else {
@ -201,4 +204,11 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return mappings; return mappings;
} }
private IterableMapping getIterableMapping(ExecutableElement method) {
IterableMappingPrism iterableMappingAnnotation = IterableMappingPrism.getInstanceOn( method );
return iterableMappingAnnotation != null ?
IterableMapping.fromIterableMappingPrism( iterableMappingAnnotation ) : null;
}
} }

View File

@ -28,8 +28,19 @@
<#if targetType.name == "Iterable" && targetType.packageName == "java.lang">${targetType.iterableImplementationType.name}<#else>${targetType.name}</#if><${targetType.elementType.name}> ${targetType.name?uncap_first} = new <#if targetType.iterableImplementationType??>${targetType.iterableImplementationType.name}<#else>${targetType.name}</#if><${targetType.elementType.name}>(); <#if targetType.name == "Iterable" && targetType.packageName == "java.lang">${targetType.iterableImplementationType.name}<#else>${targetType.name}</#if><${targetType.elementType.name}> ${targetType.name?uncap_first} = new <#if targetType.iterableImplementationType??>${targetType.iterableImplementationType.name}<#else>${targetType.name}</#if><${targetType.elementType.name}>();
for ( ${sourceType.elementType.name} ${sourceType.elementType.name?uncap_first} : ${parameterName} ) { for ( ${sourceType.elementType.name} ${sourceType.elementType.name?uncap_first} : ${parameterName} ) {
<#if toConversion??> <#if conversion??>
${targetType.name?uncap_first}.add( ${toConversion} ); <#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 );
}
</#list>
</#if>
<#else> <#else>
${targetType.name?uncap_first}.add( ${elementMappingMethod.name}( ${sourceType.elementType.name?uncap_first} ) ); ${targetType.name?uncap_first}.add( ${elementMappingMethod.name}( ${sourceType.elementType.name?uncap_first} ) );
</#if> </#if>

View File

@ -18,7 +18,10 @@
*/ */
package org.mapstruct.ap.test.conversion.date; package org.mapstruct.ap.test.conversion.date;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.mapstruct.ap.testutil.IssueKey; 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.getDate() ).isEqualTo( new GregorianCalendar( 2013, 6, 6 ).getTime() );
assertThat( source.getAnotherDate() ).isEqualTo( new GregorianCalendar( 2013, 1, 14, 8, 30 ).getTime() ); assertThat( source.getAnotherDate() ).isEqualTo( new GregorianCalendar( 2013, 1, 14, 8, 30 ).getTime() );
} }
@Test
public void shouldApplyStringConversionForIterableMethod() {
List<Date> dates = Arrays.asList(
new GregorianCalendar( 2013, 6, 6 ).getTime(),
new GregorianCalendar( 2013, 1, 14 ).getTime(),
new GregorianCalendar( 2013, 3, 11 ).getTime()
);
List<String> 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<String> stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" );
List<Date> 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()
);
}
} }

View File

@ -18,6 +18,10 @@
*/ */
package org.mapstruct.ap.test.conversion.date; 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.Mapper;
import org.mapstruct.Mappers; import org.mapstruct.Mappers;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@ -31,4 +35,9 @@ public interface SourceTargetMapper {
Target sourceToTarget(Source source); Target sourceToTarget(Source source);
Source targetToSource(Target target); Source targetToSource(Target target);
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
List<Date> dateListToStringList(List<String> strings);
} }