#44 Adding support for specification of date formats in Map mapping methods

This commit is contained in:
Gunnar Morling 2013-07-14 22:31:47 +02:00
parent 346512cc82
commit ea436e061f
10 changed files with 179 additions and 26 deletions

View File

@ -0,0 +1,53 @@
/**
* 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 map types, e.g. {@code Map<String, String>} and {@code Map<Long, Date>}.
*
* @author Gunnar Morling
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MapMapping {
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type
* {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
*/
String keyDateFormat() default "";
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value
* type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value
* types.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
*/
String valueDateFormat() default "";
}

View File

@ -40,9 +40,10 @@ 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.IterableMapping;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;
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.Options; import org.mapstruct.ap.model.Options;
import org.mapstruct.ap.model.ReportingPolicy; import org.mapstruct.ap.model.ReportingPolicy;
import org.mapstruct.ap.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.processor.DefaultModelElementProcessorContext;
@ -71,10 +72,11 @@ import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext;
*/ */
@SupportedAnnotationTypes("org.mapstruct.Mapper") @SupportedAnnotationTypes("org.mapstruct.Mapper")
@GeneratePrisms({ @GeneratePrisms({
@GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true), @GeneratePrism(value = 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) @GeneratePrism(value = IterableMapping.class, publicAccess = true),
@GeneratePrism(value = MapMapping.class, publicAccess = true)
}) })
@SupportedOptions({ @SupportedOptions({
MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,

View File

@ -30,6 +30,8 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.mapstruct.ap.util.Strings;
/** /**
* Represents the type of a bean property, parameter etc. * Represents the type of a bean property, parameter etc.
* *
@ -244,4 +246,14 @@ public class Type extends AbstractModelElement implements Comparable<Type> {
public int compareTo(Type o) { public int compareTo(Type o) {
return getFullyQualifiedName().compareTo( o.getFullyQualifiedName() ); return getFullyQualifiedName().compareTo( o.getFullyQualifiedName() );
} }
@Override
public String toString() {
if ( !typeParameters.isEmpty() ) {
return name + "<" + Strings.join( typeParameters, ", " ) + ">";
}
else {
return name;
}
}
} }

View File

@ -0,0 +1,65 @@
/**
* 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 org.mapstruct.ap.MapMappingPrism;
/**
* Represents a map mapping as configured via {@code @MapMapping}.
*
* @author Gunnar Morling
*/
public class MapMapping {
private final String keyFormat;
private final String valueFormat;
private final AnnotationMirror mirror;
public static MapMapping fromPrism(MapMappingPrism mapMapping) {
if ( mapMapping == null ) {
return null;
}
return new MapMapping(
mapMapping.keyDateFormat(),
mapMapping.valueDateFormat(),
mapMapping.mirror
);
}
private MapMapping(String keyFormat, String valueFormat, AnnotationMirror mirror) {
this.keyFormat = keyFormat;
this.valueFormat = valueFormat;
this.mirror = mirror;
}
public String getKeyFormat() {
return keyFormat;
}
public String getValueFormat() {
return valueFormat;
}
public AnnotationMirror getMirror() {
return mirror;
}
}

View File

@ -39,11 +39,12 @@ public class Method {
private final Type targetType; private final Type targetType;
private Map<String, Mapping> mappings; private Map<String, Mapping> mappings;
private IterableMapping iterableMapping; private IterableMapping iterableMapping;
private MapMapping mapMapping;
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) { IterableMapping iterableMapping, MapMapping mapMapping) {
return new Method( return new Method(
null, null,
@ -52,7 +53,8 @@ public class Method {
sourceType, sourceType,
targetType, targetType,
mappings, mappings,
iterableMapping iterableMapping,
mapMapping
); );
} }
@ -66,12 +68,14 @@ public class Method {
sourceType, sourceType,
targetType, targetType,
Collections.<String, Mapping>emptyMap(), Collections.<String, Mapping>emptyMap(),
null,
null 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, IterableMapping iterableMapping) { Type targetType, Map<String, Mapping> mappings, IterableMapping iterableMapping,
MapMapping mapMapping) {
this.declaringMapper = declaringMapper; this.declaringMapper = declaringMapper;
this.executable = executable; this.executable = executable;
this.parameterName = parameterName; this.parameterName = parameterName;
@ -79,6 +83,7 @@ public class Method {
this.targetType = targetType; this.targetType = targetType;
this.mappings = mappings; this.mappings = mappings;
this.iterableMapping = iterableMapping; this.iterableMapping = iterableMapping;
this.mapMapping = mapMapping;
} }
/** /**
@ -128,6 +133,14 @@ public class Method {
this.iterableMapping = iterableMapping; this.iterableMapping = iterableMapping;
} }
public MapMapping getMapMapping() {
return mapMapping;
}
public void setMapMapping(MapMapping mapMapping) {
this.mapMapping = mapMapping;
}
public boolean reverses(Method method) { public boolean reverses(Method method) {
return return
equals( sourceType, method.getTargetType() ) && equals( sourceType, method.getTargetType() ) &&

View File

@ -166,6 +166,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
mappingMethods.add( getIterableMappingMethod( methods, method ) ); mappingMethods.add( getIterableMappingMethod( methods, method ) );
} }
else if ( method.isMapMapping() ) { else if ( method.isMapMapping() ) {
if ( method.getMapMapping() == null && reverseMappingMethod != null &&
reverseMappingMethod.getMapMapping() != null ) {
method.setMapMapping( reverseMappingMethod.getMapMapping() );
}
mappingMethods.add( getMapMappingMethod( methods, method ) ); mappingMethods.add( getMapMappingMethod( methods, method ) );
} }
else { else {
@ -382,8 +386,17 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
Type targetKeyType = method.getTargetType().getTypeParameters().get( 0 ); Type targetKeyType = method.getTargetType().getTypeParameters().get( 0 );
Type targetValueType = method.getTargetType().getTypeParameters().get( 1 ); Type targetValueType = method.getTargetType().getTypeParameters().get( 1 );
TypeConversion valueConversion = getConversion( sourceValueType, targetValueType, null, "entry.getValue()" ); String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null;
TypeConversion keyConversion = getConversion( sourceKeyType, targetKeyType, null, "entry.getKey()" ); String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null;
TypeConversion keyConversion = getConversion( sourceKeyType, targetKeyType, keyDateFormat, "entry.getKey()" );
TypeConversion valueConversion = getConversion(
sourceValueType,
targetValueType,
valueDateFormat,
"entry.getValue()"
);
MappingMethodReference keyMappingMethod = getMappingMethodReference( methods, sourceKeyType, targetKeyType ); MappingMethodReference keyMappingMethod = getMappingMethodReference( methods, sourceKeyType, targetKeyType );
MappingMethodReference valueMappingMethod = getMappingMethodReference( MappingMethodReference valueMappingMethod = getMappingMethodReference(
methods, methods,

View File

@ -31,11 +31,13 @@ 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.IterableMappingPrism;
import org.mapstruct.ap.MapMappingPrism;
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.IterableMapping;
import org.mapstruct.ap.model.source.MapMapping;
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;
@ -128,7 +130,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
parameter.getType(), parameter.getType(),
returnType, returnType,
getMappings( method ), getMappings( method ),
getIterableMapping( method ) IterableMapping.fromPrism( IterableMappingPrism.getInstanceOn( method ) ),
MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ) )
); );
} }
else { else {
@ -204,11 +207,4 @@ 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

@ -49,4 +49,8 @@ public class Strings {
return sb.toString(); return sb.toString();
} }
public static boolean isEmpty(String string) {
return string == null || string.isEmpty();
}
} }

View File

@ -21,13 +21,11 @@ package org.mapstruct.ap.test.collection.map;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase; import org.mapstruct.ap.testutil.MapperTestBase;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Assertions.assertThat;
@ -42,11 +40,6 @@ import static org.fest.assertions.MapAssert.entry;
@IssueKey("44") @IssueKey("44")
public class MapMappingTest extends MapperTestBase { public class MapMappingTest extends MapperTestBase {
@BeforeMethod
public void setDefaultLocale() {
Locale.setDefault( Locale.GERMAN );
}
@Test @Test
public void shouldCreateMapMethodImplementation() { public void shouldCreateMapMethodImplementation() {
Map<Long, Date> values = new HashMap<Long, Date>(); Map<Long, Date> values = new HashMap<Long, Date>();
@ -57,14 +50,14 @@ public class MapMappingTest extends MapperTestBase {
assertThat( target ).isNotNull(); assertThat( target ).isNotNull();
assertThat( target ).hasSize( 2 ); assertThat( target ).hasSize( 2 );
assertThat( target ).includes( entry( "42", "01.01.80 00:00" ), entry( "121", "20.07.13 00:00" ) ); assertThat( target ).includes( entry( "42", "01.01.1980" ), entry( "121", "20.07.2013" ) );
} }
@Test @Test
public void shouldCreateReverseMapMethodImplementation() { public void shouldCreateReverseMapMethodImplementation() {
Map<String, String> values = new HashMap<String, String>(); Map<String, String> values = new HashMap<String, String>();
values.put( "42", "01.01.80 00:00" ); values.put( "42", "01.01.1980" );
values.put( "121", "20.07.13 00:00" ); values.put( "121", "20.07.2013" );
Map<Long, Date> target = SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap( values ); Map<Long, Date> target = SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap( values );

View File

@ -21,6 +21,7 @@ package org.mapstruct.ap.test.collection.map;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mappers; import org.mapstruct.Mappers;
@ -29,6 +30,7 @@ public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@MapMapping( valueDateFormat = "dd.MM.yyyy" )
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source); Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source); Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source);