Skip to content

Commit 3731fed

Browse files
committed
Revise MultiValueMapCollector implementation and tests
See spring-projects/spring-data-commons#3420 Closes gh-35958
1 parent bfc02cd commit 3731fed

File tree

2 files changed

+80
-19
lines changed

2 files changed

+80
-19
lines changed

spring-core/src/main/java/org/springframework/util/MultiValueMapCollector.java

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.springframework.util;
1818

1919
import java.util.EnumSet;
20-
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
2121
import java.util.List;
2222
import java.util.Map.Entry;
2323
import java.util.Set;
@@ -28,32 +28,74 @@
2828
import java.util.stream.Collector;
2929

3030
/**
31-
* A {@link Collector} for building a {@link MultiValueMap} from a {@link java.util.stream.Stream}.
32-
* <br/>
33-
* Moved from {@code org.springframework.data.util.MultiValueMapCollector}.
31+
* A {@link Collector} for building a {@link MultiValueMap} from a
32+
* {@link java.util.stream.Stream Stream}.
3433
*
35-
* @author Jens Schauder
34+
* <p>Copied from the Spring Data Commons project.
3635
*
37-
* @param <T> – the type of input elements to the reduction operation
38-
* @param <K> – the type of the key elements
39-
* @param <V> – the type of the value elements
36+
* @author Jens Schauder
37+
* @author Florian Hof
38+
* @author Sam Brannen
39+
* @since 7.0.2
40+
* @param <T> the type of input elements to the reduction operation
41+
* @param <K> the key type
42+
* @param <V> the value element type
4043
*/
41-
public class MultiValueMapCollector<T, K, V> implements Collector<T, MultiValueMap<K, V>, MultiValueMap<K, V>> {
44+
public final class MultiValueMapCollector<T, K, V> implements Collector<T, MultiValueMap<K, V>, MultiValueMap<K, V>> {
45+
4246
private final Function<T, K> keyFunction;
47+
4348
private final Function<T, V> valueFunction;
4449

45-
public MultiValueMapCollector(Function<T, K> keyFunction, Function<T, V> valueFunction) {
50+
51+
private MultiValueMapCollector(Function<T, K> keyFunction, Function<T, V> valueFunction) {
4652
this.keyFunction = keyFunction;
4753
this.valueFunction = valueFunction;
4854
}
4955

56+
57+
/**
58+
* Create a new {@code MultiValueMapCollector} from the given key and value
59+
* functions.
60+
* @param <T> the type of input elements to the reduction operation
61+
* @param <K> the key type
62+
* @param <V> the value element type
63+
* @param keyFunction a {@code Function} which converts an element of type
64+
* {@code T} to a key of type {@code K}
65+
* @param valueFunction a {@code Function} which converts an element of type
66+
* {@code T} to an element of type {@code V}; supply {@link Function#identity()}
67+
* if no conversion should be performed
68+
* @return a new {@code MultiValueMapCollector}
69+
* @see #indexingBy(Function)
70+
*/
71+
public static <T, K, V> MultiValueMapCollector<T, K, V> of(Function<T, K> keyFunction, Function<T, V> valueFunction) {
72+
return new MultiValueMapCollector<>(keyFunction, valueFunction);
73+
}
74+
75+
/**
76+
* Create a new {@code MultiValueMapCollector} using the given {@code indexer}.
77+
* <p>Delegates to {@link #of(Function, Function)}, supplying the given
78+
* {@code indexer} as the key function and {@link Function#identity()}
79+
* as the value function.
80+
* <p>For example, if you would like to collect the elements of a {@code Stream}
81+
* of strings into a {@link MultiValueMap} keyed by the lengths of the strings,
82+
* you could create such a {@link Collector} via
83+
* {@code MultiValueMapCollector.indexingBy(String::length)}.
84+
* @param <K> the key type
85+
* @param <V> the value element type
86+
* @param indexer a {@code Function} which converts a value of type {@code V}
87+
* to a key of type {@code K}
88+
* @return a new {@code MultiValueMapCollector} based on an {@code indexer}
89+
* @see #of(Function, Function)
90+
*/
5091
public static <K, V> MultiValueMapCollector<V, K, V> indexingBy(Function<V, K> indexer) {
5192
return new MultiValueMapCollector<>(indexer, Function.identity());
5293
}
5394

95+
5496
@Override
5597
public Supplier<MultiValueMap<K, V>> supplier() {
56-
return () -> CollectionUtils.toMultiValueMap(new HashMap<K, List<V>>());
98+
return () -> CollectionUtils.toMultiValueMap(new LinkedHashMap<K, List<V>>());
5799
}
58100

59101
@Override
@@ -80,4 +122,5 @@ public Function<MultiValueMap<K, V>, MultiValueMap<K, V>> finisher() {
80122
public Set<Characteristics> characteristics() {
81123
return EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED);
82124
}
125+
83126
}

spring-core/src/test/java/org/springframework/util/MultiValueMapCollectorTests.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,44 @@
1616

1717
package org.springframework.util;
1818

19+
import java.util.function.Function;
1920
import java.util.stream.Stream;
2021

21-
import org.junit.Test;
22+
import org.junit.jupiter.api.Test;
2223

2324
import static org.assertj.core.api.Assertions.assertThat;
2425

2526
/**
2627
* Tests for {@link MultiValueMapCollector}.
2728
*
2829
* @author Florian Hof
30+
* @author Sam Brannen
31+
* @since 7.0.2
2932
*/
3033
class MultiValueMapCollectorTests {
3134

3235
@Test
33-
void indexingBy() {
34-
MultiValueMapCollector<String, Integer, String> collector = MultiValueMapCollector.indexingBy(String::length);
35-
MultiValueMap<Integer, String> content = Stream.of("abc", "ABC", "123", "1234", "abcdef", "ABCDEF").collect(collector);
36-
assertThat(content.get(3)).containsOnly("abc", "ABC", "123");
37-
assertThat(content.get(4)).containsOnly("abcdef", "ABCDEF");
38-
assertThat(content.get(6)).containsOnly("1234");
39-
assertThat(content.get(1)).isNull();
36+
void ofFactoryMethod() {
37+
Function<Integer, String> keyFunction = i -> (i % 2 == 0 ? "even" :"odd");
38+
Function<Integer, Integer> valueFunction = i -> -i;
39+
40+
var collector = MultiValueMapCollector.of(keyFunction, valueFunction);
41+
var multiValueMap = Stream.of(1, 2, 3, 4, 5).collect(collector);
42+
43+
assertThat(multiValueMap).containsOnlyKeys("even", "odd");
44+
assertThat(multiValueMap.get("odd")).containsOnly(-1, -3, -5);
45+
assertThat(multiValueMap.get("even")).containsOnly(-2, -4);
4046
}
47+
48+
@Test
49+
void indexingByFactoryMethod() {
50+
var collector = MultiValueMapCollector.indexingBy(String::length);
51+
var multiValueMap = Stream.of("abc", "ABC", "123", "1234", "cat", "abcdef", "ABCDEF").collect(collector);
52+
53+
assertThat(multiValueMap).containsOnlyKeys(3, 4, 6);
54+
assertThat(multiValueMap.get(3)).containsOnly("abc", "ABC", "123", "cat");
55+
assertThat(multiValueMap.get(4)).containsOnly("1234");
56+
assertThat(multiValueMap.get(6)).containsOnly("abcdef", "ABCDEF");
57+
}
58+
4159
}

0 commit comments

Comments
 (0)