Skip to content

Commit fef4cd0

Browse files
committed
Default conversion support for EnumSet / EnumMap
Issue: SPR-12483
1 parent 717b2af commit fef4cd0

File tree

9 files changed

+315
-197
lines changed

9 files changed

+315
-197
lines changed

spring-core/src/main/java/org/springframework/core/CollectionFactory.java

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collection;
21+
import java.util.EnumMap;
22+
import java.util.EnumSet;
2123
import java.util.HashMap;
2224
import java.util.HashSet;
2325
import java.util.LinkedHashMap;
@@ -33,6 +35,7 @@
3335
import java.util.TreeMap;
3436
import java.util.TreeSet;
3537

38+
import org.springframework.util.Assert;
3639
import org.springframework.util.LinkedMultiValueMap;
3740
import org.springframework.util.MultiValueMap;
3841

@@ -72,9 +75,11 @@ public abstract class CollectionFactory {
7275
approximableCollectionTypes.add(HashSet.class);
7376
approximableCollectionTypes.add(LinkedHashSet.class);
7477
approximableCollectionTypes.add(TreeSet.class);
78+
approximableCollectionTypes.add(EnumSet.class);
7579
approximableMapTypes.add(HashMap.class);
7680
approximableMapTypes.add(LinkedHashMap.class);
7781
approximableMapTypes.add(TreeMap.class);
82+
approximableMapTypes.add(EnumMap.class);
7883
}
7984

8085

@@ -91,68 +96,87 @@ public static boolean isApproximableCollectionType(Class<?> collectionType) {
9196

9297
/**
9398
* Create the most approximate collection for the given collection.
94-
* <p>Creates an ArrayList, TreeSet or linked Set for a List, SortedSet
95-
* or Set, respectively.
9699
* @param collection the original Collection object
97-
* @param initialCapacity the initial capacity
100+
* @param capacity the initial capacity
98101
* @return the new Collection instance
99-
* @see java.util.ArrayList
100-
* @see java.util.TreeSet
101102
* @see java.util.LinkedHashSet
103+
* @see java.util.TreeSet
104+
* @see java.util.EnumSet
105+
* @see java.util.ArrayList
106+
* @see java.util.LinkedList
102107
*/
103108
@SuppressWarnings("unchecked")
104-
public static <E> Collection<E> createApproximateCollection(Object collection, int initialCapacity) {
109+
public static <E> Collection<E> createApproximateCollection(Object collection, int capacity) {
105110
if (collection instanceof LinkedList) {
106111
return new LinkedList<E>();
107112
}
108113
else if (collection instanceof List) {
109-
return new ArrayList<E>(initialCapacity);
114+
return new ArrayList<E>(capacity);
115+
}
116+
else if (collection instanceof EnumSet) {
117+
return EnumSet.copyOf((Collection) collection);
110118
}
111119
else if (collection instanceof SortedSet) {
112120
return new TreeSet<E>(((SortedSet<E>) collection).comparator());
113121
}
114122
else {
115-
return new LinkedHashSet<E>(initialCapacity);
123+
return new LinkedHashSet<E>(capacity);
116124
}
117125
}
118126

119127
/**
120128
* Create the most appropriate collection for the given collection type.
121-
* <p>Creates an ArrayList, TreeSet or linked Set for a List, SortedSet
122-
* or Set, respectively.
123-
* @param collectionType the desired type of the target Collection
124-
* @param initialCapacity the initial capacity
129+
* <p>Delegates to {@link #createCollection(Class, Class, int)} with a
130+
* {@code null} element type.
131+
* @param collectionClass the desired type of the target Collection
132+
* @param capacity the initial capacity
133+
* @return the new Collection instance
134+
*/
135+
public static <E> Collection<E> createCollection(Class<?> collectionClass, int capacity) {
136+
return createCollection(collectionClass, null, capacity);
137+
}
138+
139+
/**
140+
* Create the most appropriate collection for the given collection type.
141+
* @param collectionClass the desired type of the target Collection
142+
* @param elementType the collection's element type, or {@code null} if not known
143+
* @param capacity the initial capacity
125144
* @return the new Collection instance
126-
* @see java.util.ArrayList
127-
* @see java.util.TreeSet
128145
* @see java.util.LinkedHashSet
146+
* @see java.util.TreeSet
147+
* @see java.util.EnumSet
148+
* @see java.util.ArrayList
129149
*/
130150
@SuppressWarnings("unchecked")
131-
public static <E> Collection<E> createCollection(Class<?> collectionType, int initialCapacity) {
132-
if (collectionType.isInterface()) {
133-
if (List.class.equals(collectionType)) {
134-
return new ArrayList<E>(initialCapacity);
151+
public static <E> Collection<E> createCollection(Class<?> collectionClass, Class<?> elementType, int capacity) {
152+
if (collectionClass.isInterface()) {
153+
if (Set.class.equals(collectionClass) || Collection.class.equals(collectionClass)) {
154+
return new LinkedHashSet<E>(capacity);
135155
}
136-
else if (SortedSet.class.equals(collectionType) || NavigableSet.class.equals(collectionType)) {
137-
return new TreeSet<E>();
156+
else if (List.class.equals(collectionClass)) {
157+
return new ArrayList<E>(capacity);
138158
}
139-
else if (Set.class.equals(collectionType) || Collection.class.equals(collectionType)) {
140-
return new LinkedHashSet<E>(initialCapacity);
159+
else if (SortedSet.class.equals(collectionClass) || NavigableSet.class.equals(collectionClass)) {
160+
return new TreeSet<E>();
141161
}
142162
else {
143-
throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
163+
throw new IllegalArgumentException("Unsupported Collection interface: " + collectionClass.getName());
144164
}
145165
}
166+
else if (EnumSet.class.equals(collectionClass)) {
167+
Assert.notNull(elementType, "Cannot create EnumSet for unknown element type");
168+
return EnumSet.noneOf((Class) elementType);
169+
}
146170
else {
147-
if (!Collection.class.isAssignableFrom(collectionType)) {
148-
throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
171+
if (!Collection.class.isAssignableFrom(collectionClass)) {
172+
throw new IllegalArgumentException("Unsupported Collection type: " + collectionClass.getName());
149173
}
150174
try {
151-
return (Collection<E>) collectionType.newInstance();
175+
return (Collection<E>) collectionClass.newInstance();
152176
}
153177
catch (Exception ex) {
154-
throw new IllegalArgumentException("Could not instantiate Collection type: " +
155-
collectionType.getName(), ex);
178+
throw new IllegalArgumentException(
179+
"Could not instantiate Collection type: " + collectionClass.getName(), ex);
156180
}
157181
}
158182
}
@@ -170,58 +194,78 @@ public static boolean isApproximableMapType(Class<?> mapType) {
170194

171195
/**
172196
* Create the most approximate map for the given map.
173-
* <p>Creates a TreeMap or linked Map for a SortedMap or Map, respectively.
174197
* @param map the original Map object
175-
* @param initialCapacity the initial capacity
198+
* @param capacity the initial capacity
176199
* @return the new Map instance
177200
* @see java.util.TreeMap
178201
* @see java.util.LinkedHashMap
179202
*/
180-
@SuppressWarnings("unchecked")
181-
public static <K, V> Map<K, V> createApproximateMap(Object map, int initialCapacity) {
182-
if (map instanceof SortedMap) {
203+
@SuppressWarnings({"unchecked", "rawtypes"})
204+
public static <K, V> Map<K, V> createApproximateMap(Object map, int capacity) {
205+
if (map instanceof EnumMap) {
206+
return new EnumMap((Map) map);
207+
}
208+
else if (map instanceof SortedMap) {
183209
return new TreeMap<K, V>(((SortedMap<K, V>) map).comparator());
184210
}
185211
else {
186-
return new LinkedHashMap<K, V>(initialCapacity);
212+
return new LinkedHashMap<K, V>(capacity);
187213
}
188214
}
189215

190216
/**
191217
* Create the most approximate map for the given map.
192-
* <p>Creates a TreeMap or linked Map for a SortedMap or Map, respectively.
193-
* @param mapType the desired type of the target Map
194-
* @param initialCapacity the initial capacity
218+
* <p>Delegates to {@link #createMap(Class, Class, int)} with a
219+
* {@code null} key type.
220+
* @param mapClass the desired type of the target Map
221+
* @param capacity the initial capacity
222+
* @return the new Map instance
223+
*/
224+
public static <K, V> Map<K, V> createMap(Class<?> mapClass, int capacity) {
225+
return createMap(mapClass, null, capacity);
226+
}
227+
228+
/**
229+
* Create the most approximate map for the given map.
230+
* @param mapClass the desired type of the target Map
231+
* @param keyType the map's key type, or {@code null} if not known
232+
* @param capacity the initial capacity
195233
* @return the new Map instance
196-
* @see java.util.TreeMap
197234
* @see java.util.LinkedHashMap
235+
* @see java.util.TreeMap
236+
* @see java.util.EnumMap
237+
* @see org.springframework.util.LinkedMultiValueMap
198238
*/
199-
@SuppressWarnings({ "unchecked", "rawtypes" })
200-
public static <K, V> Map<K, V> createMap(Class<?> mapType, int initialCapacity) {
201-
if (mapType.isInterface()) {
202-
if (Map.class.equals(mapType)) {
203-
return new LinkedHashMap<K, V>(initialCapacity);
239+
@SuppressWarnings({"unchecked", "rawtypes"})
240+
public static <K, V> Map<K, V> createMap(Class<?> mapClass, Class<?> keyType, int capacity) {
241+
if (mapClass.isInterface()) {
242+
if (Map.class.equals(mapClass)) {
243+
return new LinkedHashMap<K, V>(capacity);
204244
}
205-
else if (SortedMap.class.equals(mapType) || NavigableMap.class.equals(mapType)) {
245+
else if (SortedMap.class.equals(mapClass) || NavigableMap.class.equals(mapClass)) {
206246
return new TreeMap<K, V>();
207247
}
208-
else if (MultiValueMap.class.equals(mapType)) {
248+
else if (MultiValueMap.class.equals(mapClass)) {
209249
return new LinkedMultiValueMap();
210250
}
211251
else {
212-
throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
252+
throw new IllegalArgumentException("Unsupported Map interface: " + mapClass.getName());
213253
}
214254
}
255+
else if (EnumMap.class.equals(mapClass)) {
256+
Assert.notNull(keyType, "Cannot create EnumMap for unknown key type");
257+
return new EnumMap(keyType);
258+
}
215259
else {
216-
if (!Map.class.isAssignableFrom(mapType)) {
217-
throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
260+
if (!Map.class.isAssignableFrom(mapClass)) {
261+
throw new IllegalArgumentException("Unsupported Map type: " + mapClass.getName());
218262
}
219263
try {
220-
return (Map<K, V>) mapType.newInstance();
264+
return (Map<K, V>) mapClass.newInstance();
221265
}
222266
catch (Exception ex) {
223-
throw new IllegalArgumentException("Could not instantiate Map type: " +
224-
mapType.getName(), ex);
267+
throw new IllegalArgumentException(
268+
"Could not instantiate Map type: " + mapClass.getName(), ex);
225269
}
226270
}
227271
}

spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,23 +27,27 @@
2727
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2828

2929
/**
30-
* Converts an Array to a Collection.
30+
* Converts an array to a Collection.
3131
*
32-
* <p>First, creates a new Collection of the requested targetType.
32+
* <p>First, creates a new Collection of the requested target type.
3333
* Then adds each array element to the target collection.
34-
* Will perform an element conversion from the source component type to the collection's parameterized type if necessary.
34+
* Will perform an element conversion from the source component type
35+
* to the collection's parameterized type if necessary.
3536
*
3637
* @author Keith Donald
38+
* @author Juergen Hoeller
3739
* @since 3.0
3840
*/
3941
final class ArrayToCollectionConverter implements ConditionalGenericConverter {
4042

4143
private final ConversionService conversionService;
4244

45+
4346
public ArrayToCollectionConverter(ConversionService conversionService) {
4447
this.conversionService = conversionService;
4548
}
4649

50+
4751
@Override
4852
public Set<ConvertiblePair> getConvertibleTypes() {
4953
return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class));
@@ -60,9 +64,13 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
6064
if (source == null) {
6165
return null;
6266
}
67+
6368
int length = Array.getLength(source);
64-
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), length);
65-
if (targetType.getElementTypeDescriptor() == null) {
69+
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
70+
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
71+
(elementDesc != null ? elementDesc.getType() : null), length);
72+
73+
if (elementDesc == null) {
6674
for (int i = 0; i < length; i++) {
6775
Object sourceElement = Array.get(source, i);
6876
target.add(sourceElement);
@@ -72,7 +80,7 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
7280
for (int i = 0; i < length; i++) {
7381
Object sourceElement = Array.get(source, i);
7482
Object targetElement = this.conversionService.convert(sourceElement,
75-
sourceType.elementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor());
83+
sourceType.elementTypeDescriptor(sourceElement), elementDesc);
7684
target.add(targetElement);
7785
}
7886
}

spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
7676
}
7777

7878
// At this point, we need a collection copy in any case, even if just for finding out about element copies...
79-
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size());
79+
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
80+
(elementDesc != null ? elementDesc.getType() : null), sourceCollection.size());
81+
8082
if (elementDesc == null) {
8183
target.addAll(sourceCollection);
8284
}

spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* map's parameterized types K,V if necessary.
3737
*
3838
* @author Keith Donald
39+
* @author Juergen Hoeller
3940
* @since 3.0
4041
*/
4142
final class MapToMapConverter implements ConditionalGenericConverter {
@@ -64,17 +65,22 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
6465
if (source == null) {
6566
return null;
6667
}
67-
boolean copyRequired = !targetType.getType().isInstance(source);
6868
Map<Object, Object> sourceMap = (Map<Object, Object>) source;
69+
70+
// Shortcut if possible...
71+
boolean copyRequired = !targetType.getType().isInstance(source);
6972
if (!copyRequired && sourceMap.isEmpty()) {
7073
return sourceMap;
7174
}
75+
TypeDescriptor keyDesc = targetType.getMapKeyTypeDescriptor();
76+
TypeDescriptor valueDesc = targetType.getMapValueTypeDescriptor();
77+
7278
List<MapEntry> targetEntries = new ArrayList<MapEntry>(sourceMap.size());
7379
for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
7480
Object sourceKey = entry.getKey();
7581
Object sourceValue = entry.getValue();
76-
Object targetKey = convertKey(sourceKey, sourceType, targetType.getMapKeyTypeDescriptor());
77-
Object targetValue = convertValue(sourceValue, sourceType, targetType.getMapValueTypeDescriptor());
82+
Object targetKey = convertKey(sourceKey, sourceType, keyDesc);
83+
Object targetValue = convertValue(sourceValue, sourceType, valueDesc);
7884
targetEntries.add(new MapEntry(targetKey, targetValue));
7985
if (sourceKey != targetKey || sourceValue != targetValue) {
8086
copyRequired = true;
@@ -83,7 +89,10 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
8389
if (!copyRequired) {
8490
return sourceMap;
8591
}
86-
Map<Object, Object> targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
92+
93+
Map<Object, Object> targetMap = CollectionFactory.createMap(targetType.getType(),
94+
(keyDesc != null ? keyDesc.getType() : null), sourceMap.size());
95+
8796
for (MapEntry entry : targetEntries) {
8897
entry.addToMap(targetMap);
8998
}

0 commit comments

Comments
 (0)