Skip to content

Commit 562967a

Browse files
Fix bug in LinkedHashMap#ofEntries(): list of tuples in some case can contains more elements that contains map itself (#2226)
* Fix bug in LinkedHashMap#ofEntries(): list of tuples in some case can contains more elements that contains map itself * Fix construct of LinkedHashMap in case where initial sequence contains duplicate keys
1 parent cb24064 commit 562967a

File tree

5 files changed

+155
-16
lines changed

5 files changed

+155
-16
lines changed

vavr/src/main/java/io/vavr/collection/LinkedHashMap.java

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static <K, V> LinkedHashMap<K, V> narrow(LinkedHashMap<? extends K, ? ext
100100
public static <K, V> LinkedHashMap<K, V> of(Tuple2<? extends K, ? extends V> entry) {
101101
final HashMap<K, V> map = HashMap.of(entry);
102102
final Queue<Tuple2<K, V>> list = Queue.of((Tuple2<K, V>) entry);
103-
return new LinkedHashMap<>(list, map);
103+
return wrap(list, map);
104104
}
105105

106106
/**
@@ -164,7 +164,7 @@ public static <T, K, V> LinkedHashMap<K, V> ofAll(java.util.stream.Stream<? exte
164164
public static <K, V> LinkedHashMap<K, V> of(K key, V value) {
165165
final HashMap<K, V> map = HashMap.of(key, value);
166166
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(key, value));
167-
return new LinkedHashMap<>(list, map);
167+
return wrap(list, map);
168168
}
169169

170170
/**
@@ -181,7 +181,7 @@ public static <K, V> LinkedHashMap<K, V> of(K key, V value) {
181181
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2) {
182182
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2);
183183
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2));
184-
return new LinkedHashMap<>(list, map);
184+
return wrapNonUnique(list, map);
185185
}
186186

187187
/**
@@ -200,7 +200,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2) {
200200
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
201201
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3);
202202
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3));
203-
return new LinkedHashMap<>(list, map);
203+
return wrapNonUnique(list, map);
204204
}
205205

206206
/**
@@ -221,7 +221,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3)
221221
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
222222
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4);
223223
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4));
224-
return new LinkedHashMap<>(list, map);
224+
return wrapNonUnique(list, map);
225225
}
226226

227227
/**
@@ -244,7 +244,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
244244
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
245245
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
246246
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5));
247-
return new LinkedHashMap<>(list, map);
247+
return wrapNonUnique(list, map);
248248
}
249249

250250
/**
@@ -269,7 +269,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
269269
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) {
270270
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6);
271271
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5), Tuple.of(k6, v6));
272-
return new LinkedHashMap<>(list, map);
272+
return wrapNonUnique(list, map);
273273
}
274274

275275
/**
@@ -296,7 +296,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
296296
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
297297
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7);
298298
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5), Tuple.of(k6, v6), Tuple.of(k7, v7));
299-
return new LinkedHashMap<>(list, map);
299+
return wrapNonUnique(list, map);
300300
}
301301

302302
/**
@@ -325,7 +325,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
325325
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
326326
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8);
327327
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5), Tuple.of(k6, v6), Tuple.of(k7, v7), Tuple.of(k8, v8));
328-
return new LinkedHashMap<>(list, map);
328+
return wrapNonUnique(list, map);
329329
}
330330

331331
/**
@@ -356,7 +356,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
356356
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
357357
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9);
358358
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5), Tuple.of(k6, v6), Tuple.of(k7, v7), Tuple.of(k8, v8), Tuple.of(k9, v9));
359-
return new LinkedHashMap<>(list, map);
359+
return wrapNonUnique(list, map);
360360
}
361361

362362
/**
@@ -389,7 +389,7 @@ public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3,
389389
public static <K, V> LinkedHashMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
390390
final HashMap<K, V> map = HashMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
391391
final Queue<Tuple2<K, V>> list = Queue.of(Tuple.of(k1, v1), Tuple.of(k2, v2), Tuple.of(k3, v3), Tuple.of(k4, v4), Tuple.of(k5, v5), Tuple.of(k6, v6), Tuple.of(k7, v7), Tuple.of(k8, v8), Tuple.of(k9, v9), Tuple.of(k10, v10));
392-
return new LinkedHashMap<>(list, map);
392+
return wrapNonUnique(list, map);
393393
}
394394

395395
/**
@@ -442,7 +442,7 @@ public static <K, V> LinkedHashMap<K, V> ofEntries(java.util.Map.Entry<? extends
442442
map = map.put(tuple);
443443
list = list.append(tuple);
444444
}
445-
return wrap(list, map);
445+
return wrapNonUnique(list, map);
446446
}
447447

448448
/**
@@ -457,7 +457,7 @@ public static <K, V> LinkedHashMap<K, V> ofEntries(java.util.Map.Entry<? extends
457457
public static <K, V> LinkedHashMap<K, V> ofEntries(Tuple2<? extends K, ? extends V>... entries) {
458458
final HashMap<K, V> map = HashMap.ofEntries(entries);
459459
final Queue<Tuple2<K, V>> list = Queue.of((Tuple2<K, V>[]) entries);
460-
return wrap(list, map);
460+
return wrapNonUnique(list, map);
461461
}
462462

463463
/**
@@ -480,7 +480,7 @@ public static <K, V> LinkedHashMap<K, V> ofEntries(Iterable<? extends Tuple2<? e
480480
map = map.put(entry);
481481
list = list.append((Tuple2<K, V>) entry);
482482
}
483-
return wrap(list, map);
483+
return wrapNonUnique(list, map);
484484
}
485485
}
486486

@@ -738,7 +738,7 @@ public LinkedHashMap<K, V> put(K key, V value) {
738738
newList = list.append(Tuple.of(key, value));
739739
}
740740
final HashMap<K, V> newMap = map.put(key, value);
741-
return new LinkedHashMap<>(newList, newMap);
741+
return wrap(newList, newMap);
742742
}
743743

744744
@Override
@@ -952,10 +952,32 @@ public String toString() {
952952
return mkString(stringPrefix() + "(", ", ", ")");
953953
}
954954

955+
/**
956+
* Construct Map with given values and key order.
957+
*
958+
* @param list The list of key-value tuples with unique keys.
959+
* @param map The map of key-value tuples.
960+
* @param <K> The key type
961+
* @param <V> The value type
962+
* @return A new Map containing the given map with given key order
963+
*/
955964
private static <K, V> LinkedHashMap<K, V> wrap(Queue<Tuple2<K, V>> list, HashMap<K, V> map) {
956965
return list.isEmpty() ? empty() : new LinkedHashMap<>(list, map);
957966
}
958967

968+
/**
969+
* Construct Map with given values and key order.
970+
*
971+
* @param list The list of key-value tuples with non-unique keys.
972+
* @param map The map of key-value tuples.
973+
* @param <K> The key type
974+
* @param <V> The value type
975+
* @return A new Map containing the given map with given key order
976+
*/
977+
private static <K, V> LinkedHashMap<K, V> wrapNonUnique(Queue<Tuple2<K, V>> list, HashMap<K, V> map) {
978+
return list.isEmpty() ? empty() : new LinkedHashMap<>(list.reverse().distinctBy(Tuple2::_1).reverse().toQueue(), map);
979+
}
980+
959981
// We need this method to narrow the argument of `ofEntries`.
960982
// If this method is static with type args <K, V>, the jdk fails to infer types at the call site.
961983
private LinkedHashMap<K, V> createFromEntries(Iterable<Tuple2<K, V>> tuples) {

vavr/src/test/java/io/vavr/collection/AbstractMapTest.java

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.security.MessageDigest;
3232
import java.util.*;
3333
import java.util.Set;
34+
import java.util.concurrent.atomic.AtomicInteger;
3435
import java.util.function.BiConsumer;
3536
import java.util.function.BinaryOperator;
3637
import java.util.function.Function;
@@ -152,6 +153,8 @@ protected boolean emptyMapShouldBeSingleton() {
152153
@SuppressWarnings("unchecked")
153154
protected abstract <K extends Comparable<? super K>, V> Map<K, V> mapOfTuples(Tuple2<? extends K, ? extends V>... entries);
154155

156+
protected abstract <K extends Comparable<? super K>, V> Map<K, V> mapOfTuples(Iterable<? extends Tuple2<? extends K, ? extends V>> entries);
157+
155158
@SuppressWarnings("unchecked")
156159
protected abstract <K extends Comparable<? super K>, V> Map<K, V> mapOfEntries(java.util.Map.Entry<? extends K, ? extends V>... entries);
157160

@@ -308,13 +311,32 @@ public void shouldConstructFromJavaStream() {
308311
assertThat(map).isEqualTo(this.<String, Integer> emptyMap().put("1", 1).put("2", 2).put("3", 3));
309312
}
310313

314+
@Test
315+
public void shouldConstructFromJavaStreamWithDuplicatedKeys() {
316+
assertThat(mapOf(Stream.range(0, 4).toJavaStream()
317+
, i -> Math.max(1, Math.min(i, 2))
318+
, i -> String.valueOf(i + 1)
319+
))
320+
.hasSize(2)
321+
.isEqualTo(mapOf(1, "2", 2, "4"));
322+
}
323+
311324
@Test
312325
public void shouldConstructFromJavaStreamEntries() {
313326
final java.util.stream.Stream<Integer> javaStream = java.util.stream.Stream.of(1, 2, 3);
314327
final Map<String, Integer> map = mapOf(javaStream, i -> Tuple.of(String.valueOf(i), i));
315328
assertThat(map).isEqualTo(this.<String, Integer> emptyMap().put("1", 1).put("2", 2).put("3", 3));
316329
}
317330

331+
@Test
332+
public void shouldConstructFromJavaStreamEntriesWithDuplicatedKeys() {
333+
assertThat(mapOf(Stream.range(0, 4).toJavaStream(), i ->
334+
Map.entry(Math.max(1, Math.min(i, 2)), String.valueOf(i + 1))
335+
))
336+
.hasSize(2)
337+
.isEqualTo(mapOf(1, "2", 2, "4"));
338+
}
339+
318340
@Test
319341
@SuppressWarnings("unchecked")
320342
public void shouldConstructFromUtilEntries() {
@@ -325,19 +347,99 @@ public void shouldConstructFromUtilEntries() {
325347

326348
@Test
327349
@SuppressWarnings("unchecked")
328-
public void shouldConstructFromEntries() {
350+
public void shouldConstructFromUtilEntriesWithDuplicatedKeys() {
351+
assertThat(mapOfEntries(
352+
asJavaEntry(1, "1"), asJavaEntry(1, "2"),
353+
asJavaEntry(2, "3"), asJavaEntry(2, "4")
354+
))
355+
.hasSize(2)
356+
.isEqualTo(mapOf(1, "2", 2, "4"));
357+
}
358+
359+
@Test
360+
@SuppressWarnings("unchecked")
361+
public void shouldConstructFromEntriesVararg() {
329362
final Map<String, Integer> actual = mapOfTuples(Map.entry("1", 1), Map.entry("2", 2), Map.entry("3", 3));
330363
final Map<String, Integer> expected = this.<String, Integer>emptyMap().put("1", 1).put("2", 2).put("3", 3);
331364
assertThat(actual).isEqualTo(expected);
332365
}
333366

367+
@Test
368+
@SuppressWarnings("unchecked")
369+
public void shouldConstructFromEntriesVarargWithDuplicatedKeys() {
370+
assertThat(mapOfTuples(
371+
Map.entry(1, "1"), Map.entry(1, "2"),
372+
Map.entry(2, "3"), Map.entry(2, "4")
373+
))
374+
.hasSize(2)
375+
.isEqualTo(mapOf(1, "2", 2, "4"));
376+
}
377+
378+
@Test
379+
public void shouldConstructFromEntriesIterable() {
380+
final Map<String, Integer> actual = mapOfTuples(asList(Map.entry("1", 1), Map.entry("2", 2), Map.entry("3", 3)));
381+
final Map<String, Integer> expected = this.<String, Integer>emptyMap().put("1", 1).put("2", 2).put("3", 3);
382+
assertThat(actual).isEqualTo(expected);
383+
}
384+
385+
@Test
386+
public void shouldConstructFromEntriesIterableWithDuplicatedKeys() {
387+
assertThat(mapOfTuples(asList(
388+
Map.entry(1, "1"), Map.entry(1, "2"),
389+
Map.entry(2, "3"), Map.entry(2, "4")
390+
)))
391+
.hasSize(2)
392+
.isEqualTo(mapOf(1, "2", 2, "4"));
393+
}
394+
334395
@Test
335396
public void shouldConstructFromPairs() {
336397
final Map<String, Integer> actual = mapOf("1", 1, "2", 2, "3", 3);
337398
final Map<String, Integer> expected = this.<String, Integer>emptyMap().put("1", 1).put("2", 2).put("3", 3);
338399
assertThat(actual).isEqualTo(expected);
339400
}
340401

402+
@Test
403+
public void shouldConstructFromPairsWithDuplicatedKeys() {
404+
final Map<Integer, String> actual = mapOf(1, "1", 1, "2", 2, "3");
405+
final Map<Integer, String> expected = this.<Integer, String>emptyMap().put(1, "2").put(2, "3");
406+
assertThat(actual).isEqualTo(expected);
407+
}
408+
409+
@Test
410+
public void shouldConstructWithTabulate() {
411+
final Map<String, Integer> actual = mapTabulate(4, i -> Tuple.of(i.toString(), i));
412+
final Map<String, Integer> expected = this.<String, Integer>emptyMap().put("0", 0).put("1", 1).put("2", 2).put("3", 3);
413+
assertThat(actual).isEqualTo(expected);
414+
}
415+
416+
@Test
417+
public void shouldConstructWithTabulateWithDuplicatedKeys() {
418+
assertThat(mapTabulate(4, i ->
419+
Tuple.of(Math.max(1, Math.min(i, 2)), String.valueOf(i + 1))
420+
))
421+
.hasSize(2)
422+
.isEqualTo(mapOf(1, "2", 2, "4"));
423+
}
424+
425+
@Test
426+
public void shouldConstructWithFill() {
427+
AtomicInteger i = new AtomicInteger();
428+
final Map<String, Integer> actual = mapFill(4, () -> Tuple.of(String.valueOf(i.get()), i.getAndIncrement()));
429+
final Map<String, Integer> expected = this.<String, Integer>emptyMap().put("0", 0).put("1", 1).put("2", 2).put("3", 3);
430+
assertThat(actual).isEqualTo(expected);
431+
}
432+
433+
@Test
434+
public void shouldConstructWithFillWithDuplicatedKeys() {
435+
AtomicInteger i = new AtomicInteger();
436+
assertThat(mapFill(4, () ->
437+
Tuple.of(Math.max(1, Math.min(i.get(), 2)), String.valueOf(i.getAndIncrement() + 1))
438+
))
439+
.hasSize(2)
440+
.isEqualTo(mapOf(1, "2", 2, "4"));
441+
}
442+
341443
// -- equality
342444

343445
@Test

vavr/src/test/java/io/vavr/collection/HashMapTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ protected final <K extends Comparable<? super K>, V> HashMap<K, V> mapOfTuples(T
6161
return HashMap.ofEntries(entries);
6262
}
6363

64+
@Override
65+
protected <K extends Comparable<? super K>, V> Map<K, V> mapOfTuples(Iterable<? extends Tuple2<? extends K, ? extends V>> entries) {
66+
return HashMap.ofEntries(entries);
67+
}
68+
6469
@SuppressWarnings("varargs")
6570
@SafeVarargs
6671
@Override

vavr/src/test/java/io/vavr/collection/LinkedHashMapTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ protected final <K extends Comparable<? super K>, V> LinkedHashMap<K, V> mapOfTu
4242
return LinkedHashMap.ofEntries(entries);
4343
}
4444

45+
@Override
46+
protected <K extends Comparable<? super K>, V> Map<K, V> mapOfTuples(Iterable<? extends Tuple2<? extends K, ? extends V>> entries) {
47+
return LinkedHashMap.ofEntries(entries);
48+
}
49+
4550
@SuppressWarnings("varargs")
4651
@SafeVarargs
4752
@Override

vavr/src/test/java/io/vavr/collection/TreeMapTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ protected final <K extends Comparable<? super K>, V> TreeMap<K, V> mapOfTuples(T
7878
return TreeMap.ofEntries(entries);
7979
}
8080

81+
@Override
82+
protected <K extends Comparable<? super K>, V> Map<K, V> mapOfTuples(Iterable<? extends Tuple2<? extends K, ? extends V>> entries) {
83+
return TreeMap.ofEntries(entries);
84+
}
85+
8186
@SuppressWarnings("varargs")
8287
@SafeVarargs
8388
@Override

0 commit comments

Comments
 (0)