You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2024/09/04 13:44:52 UTC
(causeway) branch master updated: CAUSEWAY-3809: implements other to Map conversions
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new 4b3d82d129 CAUSEWAY-3809: implements other to Map conversions
4b3d82d129 is described below
commit 4b3d82d129ad6e7a40aad9bcc01ac4f194500564
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Sep 4 15:44:46 2024 +0200
CAUSEWAY-3809: implements other to Map conversions
---
.../apache/causeway/commons/collections/Can.java | 53 ++++++++++++--
.../causeway/commons/collections/Can_Empty.java | 17 ++++-
.../causeway/commons/collections/Can_Multiple.java | 27 +++++++-
.../commons/collections/Can_Singleton.java | 27 +++++++-
.../causeway/commons/collections/CanTest.java | 81 ++++++++++++++++------
5 files changed, 175 insertions(+), 30 deletions(-)
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
index 801bdc4c59..1b9248b9aa 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can.java
@@ -785,13 +785,21 @@ extends ImmutableCollection<T>, Comparable<Can<T>>, Serializable {
/**
* Return a {@link Collector},
- * that delegates {@link Map} creation to {@link Collectors#toUnmodifiableMap(Function, Function)}.
+ * that delegates {@link Map} creation to {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)}.
* @param keyExtractor a mapping function to produce keys, must be non-null
+ * @param mergeFunction a merge function, used to resolve collisions between
+ * values associated with the same key, as supplied
+ * to {@link Map#merge(Object, Object, BiFunction)}
+ * @param mapFactory a supplier providing a new empty {@code Map}
+ * into which the results will be inserted
* @return an unmodifiable (hash) {@link Map}
*/
- public static <T, K>
- Collector<T, ?, Map<K, T>> toMapCollector(final @NonNull Function<? super T, ? extends K> keyExtractor) {
- return Collectors.toUnmodifiableMap(keyExtractor, UnaryOperator.identity());
+ public static <T, K, M extends Map<K, T>>
+ Collector<T, ?, M> toMapCollector(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final BinaryOperator<T> mergeFunction,
+ final Supplier<M> mapFactory) {
+ return Collectors.toMap(keyExtractor, UnaryOperator.identity(), mergeFunction, mapFactory);
}
// -- CONVERSIONS
@@ -844,10 +852,45 @@ extends ImmutableCollection<T>, Comparable<Can<T>>, Serializable {
*/
T[] toArray(Class<T> elementType);
+ // -- TO MAP
+
/**
- * Returns am unmodifiable (hash) {@link Map}, mapping
+ * Returns an unmodifiable (hash) {@link Map}, with values from this {@link Can},
+ * and keys as produced by given {@code keyExtractor}.
+ * <p>
+ * On duplicate keys, behavior is unspecified.
* @param keyExtractor a mapping function to produce keys, must be non-null
*/
<K> Map<K, T> toMap(@NonNull Function<? super T, ? extends K> keyExtractor);
+ /**
+ * Variant of {@link #toMap(Function)}, that protects the resulting {@link Map} from modification.
+ * @see #toMap(Function)
+ */
+ <K> Map<K, T> toUnmodifiableMap(@NonNull Function<? super T, ? extends K> keyExtractor);
+
+ /**
+ * Returns an unmodifiable (hash) {@link Map}, with values from this {@link Can},
+ * and keys as produced by given {@code keyExtractor}.
+ * @param keyExtractor a mapping function to produce keys, must be non-null
+ * @param mergeFunction a merge function, used to resolve collisions between
+ * values associated with the same key, as supplied
+ * to {@link Map#merge(Object, Object, BiFunction)}
+ * @param mapFactory a supplier providing a new empty {@code Map}
+ * into which the results will be inserted
+ */
+ <K, M extends Map<K, T>> M toMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory);
+
+ /**
+ * Variant of {@link #toMap(Function, BinaryOperator, Supplier)}, that protects the resulting {@link Map} from modification.
+ * @see #toMap(Function, BinaryOperator, Supplier)
+ */
+ <K, M extends Map<K, T>> Map<K, T> toUnmodifiableMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory);
+
}
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
index 39249ff50f..17bd3b2c10 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Empty.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -303,7 +304,21 @@ final class Can_Empty<T> implements Can<T> {
@Override
public <K> Map<K, T> toMap(final @NonNull Function<? super T, ? extends K> keyExtractor) {
- return Collections.emptyMap();
+ return new HashMap<>();
+ }
+ @Override
+ public <K> Map<K, T> toUnmodifiableMap(@NonNull final Function<? super T, ? extends K> keyExtractor) {
+ return Collections.emptyMap();
+ }
+ @Override
+ public <K, M extends Map<K, T>> M toMap(@NonNull final Function<? super T, ? extends K> keyExtractor,
+ @NonNull final BinaryOperator<T> mergeFunction, @NonNull final Supplier<M> mapFactory) {
+ return mapFactory.get();
+ }
+ @Override
+ public <K, M extends Map<K, T>> M toUnmodifiableMap(@NonNull final Function<? super T, ? extends K> keyExtractor,
+ @NonNull final BinaryOperator<T> mergeFunction, @NonNull final Supplier<M> mapFactory) {
+ return _Casts.uncheckedCast(Collections.emptyMap());
}
}
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
index 9ae7b47c3b..593486a573 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java
@@ -37,6 +37,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -468,9 +469,31 @@ final class Can_Multiple<T> implements Can<T> {
}
@Override
- public <K> Map<K, T> toMap(final @NonNull Function<? super T, ? extends K> keyExtractor) {
+ public <K> Map<K, T> toMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor) {
return stream()
- .collect(Can.toMapCollector(keyExtractor));
+ .collect(Collectors.toMap(keyExtractor, UnaryOperator.identity()));
+ }
+ @Override
+ public <K> Map<K, T> toUnmodifiableMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor) {
+ return stream()
+ .collect(Collectors.toUnmodifiableMap(keyExtractor, UnaryOperator.identity()));
+ }
+ @Override
+ public <K, M extends Map<K, T>> M toMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory) {
+ return stream()
+ .collect(Can.toMapCollector(keyExtractor, mergeFunction, mapFactory));
+ }
+ @Override
+ public <K, M extends Map<K, T>> Map<K, T> toUnmodifiableMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory) {
+ return Collections.unmodifiableMap(toMap(keyExtractor, mergeFunction, mapFactory));
}
}
diff --git a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
index f6a19cacf1..18462040ef 100644
--- a/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
+++ b/commons/src/main/java/org/apache/causeway/commons/collections/Can_Singleton.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -400,8 +401,32 @@ final class Can_Singleton<T> implements Can<T> {
}
@Override
- public <K> Map<K, T> toMap(final @NonNull Function<? super T, ? extends K> keyExtractor) {
+ public <K> Map<K, T> toMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor) {
+ var map = new HashMap<K, T>();
+ map.put(keyExtractor.apply(element), element);
+ return map;
+ }
+ @Override
+ public <K> Map<K, T> toUnmodifiableMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor) {
return Map.of(keyExtractor.apply(element), element);
}
+ @Override
+ public <K, M extends Map<K, T>> M toMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory) {
+ var map = mapFactory.get();
+ map.put(keyExtractor.apply(element), element);
+ return map;
+ }
+ @Override
+ public <K, M extends Map<K, T>> Map<K, T> toUnmodifiableMap(
+ final @NonNull Function<? super T, ? extends K> keyExtractor,
+ final @NonNull BinaryOperator<T> mergeFunction,
+ final @NonNull Supplier<M> mapFactory) {
+ return Collections.unmodifiableMap(toMap(keyExtractor, mergeFunction, mapFactory));
+ }
}
diff --git a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
index af87979d51..5b6a1d729c 100644
--- a/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/collections/CanTest.java
@@ -19,11 +19,17 @@
package org.apache.causeway.commons.collections;
import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.function.BiPredicate;
+import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -34,6 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.causeway.commons.internal.collections._Sets;
import org.apache.causeway.commons.internal.testing._SerializationTester;
+import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.val;
@@ -398,41 +405,73 @@ class CanTest {
final String name;
}
- @Test
- void toMap_emptyCan() {
+ @RequiredArgsConstructor
+ enum MapScenario {
+ HASH_MAP(customers->customers.toMap(Customer::getName)),
+ HASH_UNMOD_MAP(customers->customers.toUnmodifiableMap(Customer::getName)),
+ TREE_MAP(customers->customers.toMap(Customer::getName, (a, b)->a, TreeMap::new)),
+ TREE_UNMOD_MAP(customers->customers.toUnmodifiableMap(Customer::getName, (a, b)->a, TreeMap::new));
+ final Function<Can<Customer>, Map<String, Customer>> toMapConverterUnderTest;
+ private boolean isModifiable() {
+ return ordinal()%2 == 0;
+ }
+ void assertModifiability(final Map<String, Customer> map) {
+ if(isModifiable()) {
+ map.put("John", new Customer("John"));
+ } else {
+ assertThrows(Exception.class, ()->{
+ map.put("John", new Customer("John"));
+ });
+ }
+ }
+ void assertMapType(final Map<String, Customer> map) {
+ switch (this) {
+ case HASH_MAP:
+ assertTrue(map instanceof HashMap);
+ break;
+ case TREE_MAP:
+ assertTrue(map instanceof TreeMap);
+ break;
+ case HASH_UNMOD_MAP:
+ case TREE_UNMOD_MAP:
+ // cannot test easily
+ break;
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(MapScenario.class)
+ void toMap_emptyCan(final MapScenario scenario) {
final Can<Customer> origin = Can.empty();
- var map = origin.toMap(Customer::getName);
+ var map = scenario.toMapConverterUnderTest.apply(origin);
assertNotNull(map);
assertEquals(0, map.size());
- // verify immutability
- assertThrows(Exception.class, ()->{
- map.put("John", new Customer("John"));
- });
+ scenario.assertModifiability(map);
+ scenario.assertMapType(map);
}
- @Test
- void toMap_singleCan() {
+ @ParameterizedTest
+ @EnumSource(MapScenario.class)
+ void toMap_singleCan(final MapScenario scenario) {
final Can<Customer> origin = Can.of(new Customer("Jeff"));
- var map = origin.toMap(Customer::getName);
+ var map = scenario.toMapConverterUnderTest.apply(origin);
assertNotNull(map);
assertEquals(1, map.size());
assertEquals(new Customer("Jeff"), map.get("Jeff"));
- // verify immutability
- assertThrows(Exception.class, ()->{
- map.put("John", new Customer("John"));
- });
+ scenario.assertModifiability(map);
+ scenario.assertMapType(map);
}
- @Test
- void toMap_multiCan() {
+ @ParameterizedTest
+ @EnumSource(MapScenario.class)
+ void toMap_multiCan(final MapScenario scenario) {
final Can<Customer> origin = Can.of(new Customer("Jeff"), new Customer("Jane"));
- var map = origin.toMap(Customer::getName);
+ var map = scenario.toMapConverterUnderTest.apply(origin);
assertNotNull(map);
assertEquals(2, map.size());
assertEquals(new Customer("Jeff"), map.get("Jeff"));
assertEquals(new Customer("Jane"), map.get("Jane"));
- // verify immutability
- assertThrows(Exception.class, ()->{
- map.put("John", new Customer("John"));
- });
+ scenario.assertModifiability(map);
+ scenario.assertMapType(map);
}
// -- HEPER