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