Skip to content

Commit ea436e0

Browse files
committed
mapstruct#44 Adding support for specification of date formats in Map mapping methods
1 parent 346512c commit ea436e0

10 files changed

Lines changed: 179 additions & 26 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
3+
* and/or other contributors as indicated by the @authors tag. See the
4+
* copyright.txt file in the distribution for a full listing of all
5+
* contributors.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.mapstruct;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
import java.text.SimpleDateFormat;
26+
import java.util.Date;
27+
28+
/**
29+
* Configures the mapping between two map types, e.g. {@code Map<String, String>} and {@code Map<Long, Date>}.
30+
*
31+
* @author Gunnar Morling
32+
*/
33+
@Target(ElementType.METHOD)
34+
@Retention(RetentionPolicy.SOURCE)
35+
public @interface MapMapping {
36+
37+
/**
38+
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type
39+
* {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types.
40+
*
41+
* @return A date format string as processable by {@link SimpleDateFormat}.
42+
*/
43+
String keyDateFormat() default "";
44+
45+
/**
46+
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value
47+
* type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value
48+
* types.
49+
*
50+
* @return A date format string as processable by {@link SimpleDateFormat}.
51+
*/
52+
String valueDateFormat() default "";
53+
}

processor/src/main/java/org/mapstruct/ap/MappingProcessor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
import net.java.dev.hickory.prism.GeneratePrism;
4141
import net.java.dev.hickory.prism.GeneratePrisms;
4242
import org.mapstruct.IterableMapping;
43+
import org.mapstruct.MapMapping;
44+
import org.mapstruct.Mapper;
4345
import org.mapstruct.Mapping;
4446
import org.mapstruct.Mappings;
45-
import org.mapstruct.ap.model.Mapper;
4647
import org.mapstruct.ap.model.Options;
4748
import org.mapstruct.ap.model.ReportingPolicy;
4849
import org.mapstruct.ap.processor.DefaultModelElementProcessorContext;
@@ -71,10 +72,11 @@
7172
*/
7273
@SupportedAnnotationTypes("org.mapstruct.Mapper")
7374
@GeneratePrisms({
74-
@GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true),
75+
@GeneratePrism(value = Mapper.class, publicAccess = true),
7576
@GeneratePrism(value = Mapping.class, publicAccess = true),
7677
@GeneratePrism(value = Mappings.class, publicAccess = true),
77-
@GeneratePrism(value = IterableMapping.class, publicAccess = true)
78+
@GeneratePrism(value = IterableMapping.class, publicAccess = true),
79+
@GeneratePrism(value = MapMapping.class, publicAccess = true)
7880
})
7981
@SupportedOptions({
8082
MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,

processor/src/main/java/org/mapstruct/ap/model/Type.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.util.concurrent.ConcurrentHashMap;
3131
import java.util.concurrent.ConcurrentMap;
3232

33+
import org.mapstruct.ap.util.Strings;
34+
3335
/**
3436
* Represents the type of a bean property, parameter etc.
3537
*
@@ -244,4 +246,14 @@ else if ( !typeParameters.equals( other.typeParameters ) ) {
244246
public int compareTo(Type o) {
245247
return getFullyQualifiedName().compareTo( o.getFullyQualifiedName() );
246248
}
249+
250+
@Override
251+
public String toString() {
252+
if ( !typeParameters.isEmpty() ) {
253+
return name + "<" + Strings.join( typeParameters, ", " ) + ">";
254+
}
255+
else {
256+
return name;
257+
}
258+
}
247259
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
3+
* and/or other contributors as indicated by the @authors tag. See the
4+
* copyright.txt file in the distribution for a full listing of all
5+
* contributors.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.mapstruct.ap.model.source;
20+
21+
import javax.lang.model.element.AnnotationMirror;
22+
23+
import org.mapstruct.ap.MapMappingPrism;
24+
25+
/**
26+
* Represents a map mapping as configured via {@code @MapMapping}.
27+
*
28+
* @author Gunnar Morling
29+
*/
30+
public class MapMapping {
31+
32+
private final String keyFormat;
33+
private final String valueFormat;
34+
private final AnnotationMirror mirror;
35+
36+
public static MapMapping fromPrism(MapMappingPrism mapMapping) {
37+
if ( mapMapping == null ) {
38+
return null;
39+
}
40+
41+
return new MapMapping(
42+
mapMapping.keyDateFormat(),
43+
mapMapping.valueDateFormat(),
44+
mapMapping.mirror
45+
);
46+
}
47+
48+
private MapMapping(String keyFormat, String valueFormat, AnnotationMirror mirror) {
49+
this.keyFormat = keyFormat;
50+
this.valueFormat = valueFormat;
51+
this.mirror = mirror;
52+
}
53+
54+
public String getKeyFormat() {
55+
return keyFormat;
56+
}
57+
58+
public String getValueFormat() {
59+
return valueFormat;
60+
}
61+
62+
public AnnotationMirror getMirror() {
63+
return mirror;
64+
}
65+
}

processor/src/main/java/org/mapstruct/ap/model/source/Method.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ public class Method {
3939
private final Type targetType;
4040
private Map<String, Mapping> mappings;
4141
private IterableMapping iterableMapping;
42+
private MapMapping mapMapping;
4243

4344
public static Method forMethodRequiringImplementation(ExecutableElement executable, String parameterName,
4445
Type sourceType, Type targetType,
4546
Map<String, Mapping> mappings,
46-
IterableMapping iterableMapping) {
47+
IterableMapping iterableMapping, MapMapping mapMapping) {
4748

4849
return new Method(
4950
null,
@@ -52,7 +53,8 @@ public static Method forMethodRequiringImplementation(ExecutableElement executab
5253
sourceType,
5354
targetType,
5455
mappings,
55-
iterableMapping
56+
iterableMapping,
57+
mapMapping
5658
);
5759
}
5860

@@ -66,19 +68,22 @@ public static Method forReferencedMethod(Type declaringMapper, ExecutableElement
6668
sourceType,
6769
targetType,
6870
Collections.<String, Mapping>emptyMap(),
71+
null,
6972
null
7073
);
7174
}
7275

7376
private Method(Type declaringMapper, ExecutableElement executable, String parameterName, Type sourceType,
74-
Type targetType, Map<String, Mapping> mappings, IterableMapping iterableMapping) {
77+
Type targetType, Map<String, Mapping> mappings, IterableMapping iterableMapping,
78+
MapMapping mapMapping) {
7579
this.declaringMapper = declaringMapper;
7680
this.executable = executable;
7781
this.parameterName = parameterName;
7882
this.sourceType = sourceType;
7983
this.targetType = targetType;
8084
this.mappings = mappings;
8185
this.iterableMapping = iterableMapping;
86+
this.mapMapping = mapMapping;
8287
}
8388

8489
/**
@@ -128,6 +133,14 @@ public void setIterableMapping(IterableMapping iterableMapping) {
128133
this.iterableMapping = iterableMapping;
129134
}
130135

136+
public MapMapping getMapMapping() {
137+
return mapMapping;
138+
}
139+
140+
public void setMapMapping(MapMapping mapMapping) {
141+
this.mapMapping = mapMapping;
142+
}
143+
131144
public boolean reverses(Method method) {
132145
return
133146
equals( sourceType, method.getTargetType() ) &&

processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ private List<MappingMethod> getMappingMethods(List<Method> methods, ReportingPol
166166
mappingMethods.add( getIterableMappingMethod( methods, method ) );
167167
}
168168
else if ( method.isMapMapping() ) {
169+
if ( method.getMapMapping() == null && reverseMappingMethod != null &&
170+
reverseMappingMethod.getMapMapping() != null ) {
171+
method.setMapMapping( reverseMappingMethod.getMapMapping() );
172+
}
169173
mappingMethods.add( getMapMappingMethod( methods, method ) );
170174
}
171175
else {
@@ -382,8 +386,17 @@ private MappingMethod getMapMappingMethod(List<Method> methods, Method method) {
382386
Type targetKeyType = method.getTargetType().getTypeParameters().get( 0 );
383387
Type targetValueType = method.getTargetType().getTypeParameters().get( 1 );
384388

385-
TypeConversion valueConversion = getConversion( sourceValueType, targetValueType, null, "entry.getValue()" );
386-
TypeConversion keyConversion = getConversion( sourceKeyType, targetKeyType, null, "entry.getKey()" );
389+
String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null;
390+
String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null;
391+
392+
TypeConversion keyConversion = getConversion( sourceKeyType, targetKeyType, keyDateFormat, "entry.getKey()" );
393+
TypeConversion valueConversion = getConversion(
394+
sourceValueType,
395+
targetValueType,
396+
valueDateFormat,
397+
"entry.getValue()"
398+
);
399+
387400
MappingMethodReference keyMappingMethod = getMappingMethodReference( methods, sourceKeyType, targetKeyType );
388401
MappingMethodReference valueMappingMethod = getMappingMethodReference(
389402
methods,

processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
import javax.tools.Diagnostic.Kind;
3232

3333
import org.mapstruct.ap.IterableMappingPrism;
34+
import org.mapstruct.ap.MapMappingPrism;
3435
import org.mapstruct.ap.MapperPrism;
3536
import org.mapstruct.ap.MappingPrism;
3637
import org.mapstruct.ap.MappingsPrism;
3738
import org.mapstruct.ap.model.Type;
3839
import org.mapstruct.ap.model.source.IterableMapping;
40+
import org.mapstruct.ap.model.source.MapMapping;
3941
import org.mapstruct.ap.model.source.Mapping;
4042
import org.mapstruct.ap.model.source.Method;
4143
import org.mapstruct.ap.model.source.Parameter;
@@ -128,7 +130,8 @@ private Method getMethod(TypeElement element, ExecutableElement method, boolean
128130
parameter.getType(),
129131
returnType,
130132
getMappings( method ),
131-
getIterableMapping( method )
133+
IterableMapping.fromPrism( IterableMappingPrism.getInstanceOn( method ) ),
134+
MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ) )
132135
);
133136
}
134137
else {
@@ -204,11 +207,4 @@ private Map<String, Mapping> getMappings(ExecutableElement method) {
204207

205208
return mappings;
206209
}
207-
208-
private IterableMapping getIterableMapping(ExecutableElement method) {
209-
IterableMappingPrism iterableMappingAnnotation = IterableMappingPrism.getInstanceOn( method );
210-
return iterableMappingAnnotation != null ?
211-
IterableMapping.fromIterableMappingPrism( iterableMappingAnnotation ) : null;
212-
}
213-
214210
}

processor/src/main/java/org/mapstruct/ap/util/Strings.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,8 @@ public static String join(Iterable<?> iterable, String separator) {
4949

5050
return sb.toString();
5151
}
52+
53+
public static boolean isEmpty(String string) {
54+
return string == null || string.isEmpty();
55+
}
5256
}

processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@
2121
import java.util.Date;
2222
import java.util.GregorianCalendar;
2323
import java.util.HashMap;
24-
import java.util.Locale;
2524
import java.util.Map;
2625

2726
import org.mapstruct.ap.testutil.IssueKey;
2827
import org.mapstruct.ap.testutil.MapperTestBase;
2928
import org.mapstruct.ap.testutil.WithClasses;
30-
import org.testng.annotations.BeforeMethod;
3129
import org.testng.annotations.Test;
3230

3331
import static org.fest.assertions.Assertions.assertThat;
@@ -42,11 +40,6 @@
4240
@IssueKey("44")
4341
public class MapMappingTest extends MapperTestBase {
4442

45-
@BeforeMethod
46-
public void setDefaultLocale() {
47-
Locale.setDefault( Locale.GERMAN );
48-
}
49-
5043
@Test
5144
public void shouldCreateMapMethodImplementation() {
5245
Map<Long, Date> values = new HashMap<Long, Date>();
@@ -57,14 +50,14 @@ public void shouldCreateMapMethodImplementation() {
5750

5851
assertThat( target ).isNotNull();
5952
assertThat( target ).hasSize( 2 );
60-
assertThat( target ).includes( entry( "42", "01.01.80 00:00" ), entry( "121", "20.07.13 00:00" ) );
53+
assertThat( target ).includes( entry( "42", "01.01.1980" ), entry( "121", "20.07.2013" ) );
6154
}
6255

6356
@Test
6457
public void shouldCreateReverseMapMethodImplementation() {
6558
Map<String, String> values = new HashMap<String, String>();
66-
values.put( "42", "01.01.80 00:00" );
67-
values.put( "121", "20.07.13 00:00" );
59+
values.put( "42", "01.01.1980" );
60+
values.put( "121", "20.07.2013" );
6861

6962
Map<Long, Date> target = SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap( values );
7063

processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Date;
2222
import java.util.Map;
2323

24+
import org.mapstruct.MapMapping;
2425
import org.mapstruct.Mapper;
2526
import org.mapstruct.Mappers;
2627

@@ -29,6 +30,7 @@ public interface SourceTargetMapper {
2930

3031
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
3132

33+
@MapMapping( valueDateFormat = "dd.MM.yyyy" )
3234
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
3335

3436
Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source);

0 commit comments

Comments
 (0)