Skip to content

Commit 87942ed

Browse files
committed
Support for SequencedCollection/Set/Map (on JDK 21)
Includes consistent support for MultiValueMap. Closes gh-30239
1 parent dd871e0 commit 87942ed

File tree

2 files changed

+65
-52
lines changed

2 files changed

+65
-52
lines changed

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

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -73,11 +73,13 @@ public final class CollectionFactory {
7373
private static final Set<Class<?>> approximableMapTypes = Set.of(
7474
// Standard map interfaces
7575
Map.class,
76+
MultiValueMap.class,
7677
SortedMap.class,
7778
NavigableMap.class,
7879
// Common concrete map classes
7980
HashMap.class,
8081
LinkedHashMap.class,
82+
LinkedMultiValueMap.class,
8183
TreeMap.class,
8284
EnumMap.class);
8385

@@ -93,7 +95,9 @@ private CollectionFactory() {
9395
* @return {@code true} if the type is <em>approximable</em>
9496
*/
9597
public static boolean isApproximableCollectionType(@Nullable Class<?> collectionType) {
96-
return (collectionType != null && approximableCollectionTypes.contains(collectionType));
98+
return (collectionType != null && (approximableCollectionTypes.contains(collectionType) ||
99+
collectionType.getName().equals("java.util.SequencedSet") ||
100+
collectionType.getName().equals("java.util.SequencedCollection")));
97101
}
98102

99103
/**
@@ -118,20 +122,20 @@ public static boolean isApproximableCollectionType(@Nullable Class<?> collection
118122
*/
119123
@SuppressWarnings({"rawtypes", "unchecked"})
120124
public static <E> Collection<E> createApproximateCollection(@Nullable Object collection, int capacity) {
121-
if (collection instanceof LinkedList) {
122-
return new LinkedList<>();
123-
}
124-
else if (collection instanceof List) {
125-
return new ArrayList<>(capacity);
126-
}
127-
else if (collection instanceof EnumSet enumSet) {
125+
if (collection instanceof EnumSet enumSet) {
128126
Collection<E> copy = EnumSet.copyOf(enumSet);
129127
copy.clear();
130128
return copy;
131129
}
132130
else if (collection instanceof SortedSet sortedSet) {
133131
return new TreeSet<>(sortedSet.comparator());
134132
}
133+
else if (collection instanceof LinkedList) {
134+
return new LinkedList<>();
135+
}
136+
else if (collection instanceof List) {
137+
return new ArrayList<>(capacity);
138+
}
135139
else {
136140
return new LinkedHashSet<>(capacity);
137141
}
@@ -178,7 +182,9 @@ public static <E> Collection<E> createCollection(Class<?> collectionType, int ca
178182
public static <E> Collection<E> createCollection(Class<?> collectionType, @Nullable Class<?> elementType, int capacity) {
179183
Assert.notNull(collectionType, "Collection type must not be null");
180184
if (LinkedHashSet.class == collectionType || HashSet.class == collectionType ||
181-
Set.class == collectionType || Collection.class == collectionType) {
185+
Set.class == collectionType || Collection.class == collectionType ||
186+
collectionType.getName().equals("java.util.SequencedSet") ||
187+
collectionType.getName().equals("java.util.SequencedCollection")) {
182188
return new LinkedHashSet<>(capacity);
183189
}
184190
else if (ArrayList.class == collectionType || List.class == collectionType) {
@@ -187,8 +193,8 @@ else if (ArrayList.class == collectionType || List.class == collectionType) {
187193
else if (LinkedList.class == collectionType) {
188194
return new LinkedList<>();
189195
}
190-
else if (TreeSet.class == collectionType || NavigableSet.class == collectionType
191-
|| SortedSet.class == collectionType) {
196+
else if (TreeSet.class == collectionType || NavigableSet.class == collectionType ||
197+
SortedSet.class == collectionType) {
192198
return new TreeSet<>();
193199
}
194200
else if (EnumSet.class.isAssignableFrom(collectionType)) {
@@ -216,7 +222,8 @@ else if (EnumSet.class.isAssignableFrom(collectionType)) {
216222
* @return {@code true} if the type is <em>approximable</em>
217223
*/
218224
public static boolean isApproximableMapType(@Nullable Class<?> mapType) {
219-
return (mapType != null && approximableMapTypes.contains(mapType));
225+
return (mapType != null && (approximableMapTypes.contains(mapType) ||
226+
mapType.getName().equals("java.util.SequencedMap")));
220227
}
221228

222229
/**
@@ -246,6 +253,9 @@ public static <K, V> Map<K, V> createApproximateMap(@Nullable Object map, int ca
246253
else if (map instanceof SortedMap sortedMap) {
247254
return new TreeMap<>(sortedMap.comparator());
248255
}
256+
else if (map instanceof MultiValueMap) {
257+
return new LinkedMultiValueMap(capacity);
258+
}
249259
else {
250260
return new LinkedHashMap<>(capacity);
251261
}
@@ -292,26 +302,22 @@ public static <K, V> Map<K, V> createMap(Class<?> mapType, int capacity) {
292302
@SuppressWarnings({"rawtypes", "unchecked"})
293303
public static <K, V> Map<K, V> createMap(Class<?> mapType, @Nullable Class<?> keyType, int capacity) {
294304
Assert.notNull(mapType, "Map type must not be null");
295-
if (mapType.isInterface()) {
296-
if (Map.class == mapType) {
297-
return new LinkedHashMap<>(capacity);
298-
}
299-
else if (SortedMap.class == mapType || NavigableMap.class == mapType) {
300-
return new TreeMap<>();
301-
}
302-
else if (MultiValueMap.class == mapType) {
303-
return new LinkedMultiValueMap();
304-
}
305-
else {
306-
throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
307-
}
305+
if (LinkedHashMap.class == mapType || HashMap.class == mapType || Map.class == mapType ||
306+
mapType.getName().equals("java.util.SequencedMap")) {
307+
return new LinkedHashMap<>(capacity);
308+
}
309+
else if (LinkedMultiValueMap.class == mapType || MultiValueMap.class == mapType) {
310+
return new LinkedMultiValueMap();
311+
}
312+
else if (TreeMap.class == mapType || SortedMap.class == mapType || NavigableMap.class == mapType) {
313+
return new TreeMap<>();
308314
}
309315
else if (EnumMap.class == mapType) {
310316
Assert.notNull(keyType, "Cannot create EnumMap for unknown key type");
311317
return new EnumMap(asEnumType(keyType));
312318
}
313319
else {
314-
if (!Map.class.isAssignableFrom(mapType)) {
320+
if (mapType.isInterface() || !Map.class.isAssignableFrom(mapType)) {
315321
throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
316322
}
317323
try {

spring-core/src/test/java/org/springframework/core/CollectionFactoryTests.java

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -209,21 +209,25 @@ void createApproximateMapFromNonEmptyEnumMap() {
209209
@Test
210210
void createsCollectionsCorrectly() {
211211
// interfaces
212-
assertThat(createCollection(List.class, 0)).isInstanceOf(ArrayList.class);
213-
assertThat(createCollection(Set.class, 0)).isInstanceOf(LinkedHashSet.class);
214-
assertThat(createCollection(Collection.class, 0)).isInstanceOf(LinkedHashSet.class);
215-
assertThat(createCollection(SortedSet.class, 0)).isInstanceOf(TreeSet.class);
216-
assertThat(createCollection(NavigableSet.class, 0)).isInstanceOf(TreeSet.class);
217-
218-
assertThat(createCollection(List.class, String.class, 0)).isInstanceOf(ArrayList.class);
219-
assertThat(createCollection(Set.class, String.class, 0)).isInstanceOf(LinkedHashSet.class);
220-
assertThat(createCollection(Collection.class, String.class, 0)).isInstanceOf(LinkedHashSet.class);
221-
assertThat(createCollection(SortedSet.class, String.class, 0)).isInstanceOf(TreeSet.class);
222-
assertThat(createCollection(NavigableSet.class, String.class, 0)).isInstanceOf(TreeSet.class);
212+
testCollection(List.class, ArrayList.class);
213+
testCollection(Set.class, LinkedHashSet.class);
214+
testCollection(Collection.class, LinkedHashSet.class);
215+
// on JDK 21: testCollection(SequencedSet.class, LinkedHashSet.class);
216+
// on JDK 21: testCollection(SequencedCollection.class, LinkedHashSet.class);
217+
testCollection(SortedSet.class, TreeSet.class);
218+
testCollection(NavigableSet.class, TreeSet.class);
223219

224220
// concrete types
225-
assertThat(createCollection(HashSet.class, 0)).isInstanceOf(HashSet.class);
226-
assertThat(createCollection(HashSet.class, String.class, 0)).isInstanceOf(HashSet.class);
221+
testCollection(ArrayList.class, ArrayList.class);
222+
testCollection(HashSet.class, LinkedHashSet.class);
223+
testCollection(LinkedHashSet.class, LinkedHashSet.class);
224+
testCollection(TreeSet.class, TreeSet.class);
225+
}
226+
227+
private void testCollection(Class<?> collectionType, Class<?> resultType) {
228+
assertThat(CollectionFactory.isApproximableCollectionType(collectionType)).isTrue();
229+
assertThat(createCollection(collectionType, 0)).isInstanceOf(resultType);
230+
assertThat(createCollection(collectionType, String.class, 0)).isInstanceOf(resultType);
227231
}
228232

229233
@Test
@@ -258,20 +262,23 @@ void rejectsNullCollectionType() {
258262
@Test
259263
void createsMapsCorrectly() {
260264
// interfaces
261-
assertThat(createMap(Map.class, 0)).isInstanceOf(LinkedHashMap.class);
262-
assertThat(createMap(SortedMap.class, 0)).isInstanceOf(TreeMap.class);
263-
assertThat(createMap(NavigableMap.class, 0)).isInstanceOf(TreeMap.class);
264-
assertThat(createMap(MultiValueMap.class, 0)).isInstanceOf(LinkedMultiValueMap.class);
265-
266-
assertThat(createMap(Map.class, String.class, 0)).isInstanceOf(LinkedHashMap.class);
267-
assertThat(createMap(SortedMap.class, String.class, 0)).isInstanceOf(TreeMap.class);
268-
assertThat(createMap(NavigableMap.class, String.class, 0)).isInstanceOf(TreeMap.class);
269-
assertThat(createMap(MultiValueMap.class, String.class, 0)).isInstanceOf(LinkedMultiValueMap.class);
265+
testMap(Map.class, LinkedHashMap.class);
266+
// on JDK 21: testMap(SequencedMap.class, LinkedHashMap.class);
267+
testMap(SortedMap.class, TreeMap.class);
268+
testMap(NavigableMap.class, TreeMap.class);
269+
testMap(MultiValueMap.class, LinkedMultiValueMap.class);
270270

271271
// concrete types
272-
assertThat(createMap(HashMap.class, 0)).isInstanceOf(HashMap.class);
272+
testMap(HashMap.class, LinkedHashMap.class);
273+
testMap(LinkedHashMap.class, LinkedHashMap.class);
274+
testMap(TreeMap.class, TreeMap.class);
275+
testMap(LinkedMultiValueMap.class, LinkedMultiValueMap.class);
276+
}
273277

274-
assertThat(createMap(HashMap.class, String.class, 0)).isInstanceOf(HashMap.class);
278+
private void testMap(Class<?> mapType, Class<?> resultType) {
279+
assertThat(CollectionFactory.isApproximableMapType(mapType)).isTrue();
280+
assertThat(createMap(mapType, 0)).isInstanceOf(resultType);
281+
assertThat(createMap(mapType, String.class, 0)).isInstanceOf(resultType);
275282
}
276283

277284
@Test

0 commit comments

Comments
 (0)