This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git


The following commit(s) were added to refs/heads/master by this push:
     new d7a8201d5 provide an inverse method for multimaps
d7a8201d5 is described below

commit d7a8201d50f1a5c291d64015200a9648c6eb6ac2
Author: Paul King <[email protected]>
AuthorDate: Fri Dec 12 21:15:50 2025 +1000

    provide an inverse method for multimaps
---
 .../apache/commons/collections4/MultiMapUtils.java | 22 ++++++++++++++++++
 .../commons/collections4/MultiValuedMap.java       | 11 +++++++++
 .../multimap/ArrayListValuedHashMap.java           |  6 +++++
 .../multimap/ArrayListValuedLinkedHashMap.java     |  6 +++++
 .../multimap/HashSetValuedHashMap.java             |  6 +++++
 .../multimap/LinkedHashSetValuedLinkedHashMap.java |  6 +++++
 .../commons/collections4/MultiMapUtilsTest.java    | 26 ++++++++++++++++++++++
 .../multimap/ArrayListValuedHashMapTest.java       | 13 +++++++++++
 .../multimap/ArrayListValuedLinkedHashMapTest.java | 13 +++++++++++
 .../multimap/HashSetValuedHashMapTest.java         | 13 +++++++++++
 .../LinkedHashSetValuedLinkedHashMapTest.java      | 13 +++++++++++
 .../multimap/TransformedMultiValuedMapTest.java    |  6 +++++
 12 files changed, 141 insertions(+)

diff --git a/src/main/java/org/apache/commons/collections4/MultiMapUtils.java 
b/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
index ef7fc8c96..c698a38e5 100644
--- a/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
+++ b/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.collections4.bag.HashBag;
@@ -170,6 +171,27 @@ public class MultiMapUtils {
         return map == null || map.isEmpty();
     }
 
+    /**
+     * A utility method to invert the mappings from an input MultiValuedMap
+     * and add them to an output MultiValuedMap. Use this method to have 
complete
+     * control of the output MultiValuedMap or when merging several inverse 
mappings.
+     * In simple cases, consider just using the {@link 
MultiValuedMap#inverted()} method.
+     *
+     * @param input take key-to-value mappings from here
+     * @param output add value-to-key mappings here
+     * @param <K> the output MultiValuedMap key type
+     * @param <V> the output MultiValuedMap value type
+     * @param <M> the output MultiValuedMap with key and value types reversed 
compared with input
+     * @return the updated output MultiValuedMap
+     */
+    public static <K, V, M extends MultiValuedMap<K, V>>
+    M invert(MultiValuedMap<? extends V, ? extends K> input, M output) {
+        for (Map.Entry<? extends V, ? extends K> e : input.entries()) {
+            output.put(e.getValue(), e.getKey());
+        }
+        return output;
+    }
+
     /**
      * Creates a {@link ListValuedMap} with an {@link java.util.ArrayList 
ArrayList} as
      * collection class to store the values mapped to a key.
diff --git a/src/main/java/org/apache/commons/collections4/MultiValuedMap.java 
b/src/main/java/org/apache/commons/collections4/MultiValuedMap.java
index 96d2f6b08..350e74e7e 100644
--- a/src/main/java/org/apache/commons/collections4/MultiValuedMap.java
+++ b/src/main/java/org/apache/commons/collections4/MultiValuedMap.java
@@ -140,6 +140,17 @@ public interface MultiValuedMap<K, V> {
      */
     Collection<V> get(K key);
 
+    /**
+     * Returns a new MultiValuedMap with inverted mappings.
+     * The new multimap will have a value-to-key mapping
+     * for each key-to-value mapping in the original.
+     *
+     * @return a new MultiValuedMap with inverted mappings
+     */
+    default MultiValuedMap<V, K> inverted() {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Returns {@code true} if this map contains no key-value mappings.
      *
diff --git 
a/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java
 
b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java
index a9d2a1153..47586dc63 100644
--- 
a/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java
+++ 
b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMap.java
@@ -25,6 +25,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.commons.collections4.MultiMapUtils;
 import org.apache.commons.collections4.MultiValuedMap;
 
 /**
@@ -118,6 +119,11 @@ public class ArrayListValuedHashMap<K, V> extends 
AbstractListValuedMap<K, V>
         return new ArrayList<>(initialListCapacity);
     }
 
+    @Override
+    public ArrayListValuedHashMap<V, K> inverted() {
+        return MultiMapUtils.invert(this, new ArrayListValuedHashMap<V, K>());
+    }
+
     /**
      * Deserializes an instance from an ObjectInputStream.
      *
diff --git 
a/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java
 
b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java
index f1b797b53..2556ab3f7 100644
--- 
a/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java
+++ 
b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java
@@ -25,6 +25,7 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.apache.commons.collections4.MultiMapUtils;
 import org.apache.commons.collections4.MultiValuedMap;
 
 /**
@@ -118,6 +119,11 @@ public class ArrayListValuedLinkedHashMap<K, V> extends 
AbstractListValuedMap<K,
         return new ArrayList<>(initialListCapacity);
     }
 
+    @Override
+    public ArrayListValuedLinkedHashMap<V, K> inverted() {
+        return MultiMapUtils.invert(this, new 
ArrayListValuedLinkedHashMap<>());
+    }
+
     /**
      * Deserializes an instance from an ObjectInputStream.
      *
diff --git 
a/src/main/java/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java
 
b/src/main/java/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java
index b122e2c41..184730cbf 100644
--- 
a/src/main/java/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java
+++ 
b/src/main/java/org/apache/commons/collections4/multimap/HashSetValuedHashMap.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
+import org.apache.commons.collections4.MultiMapUtils;
 import org.apache.commons.collections4.MultiValuedMap;
 
 /**
@@ -117,6 +118,11 @@ public class HashSetValuedHashMap<K, V> extends 
AbstractSetValuedMap<K, V>
         return new HashSet<>(initialSetCapacity);
     }
 
+    @Override
+    public HashSetValuedHashMap<V, K> inverted() {
+        return MultiMapUtils.invert(this, new HashSetValuedHashMap<V, K>());
+    }
+
     /**
      * Deserializes an instance from an ObjectInputStream.
      *
diff --git 
a/src/main/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMap.java
 
b/src/main/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMap.java
index c8be8751d..c381893b6 100644
--- 
a/src/main/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMap.java
+++ 
b/src/main/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMap.java
@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 
+import org.apache.commons.collections4.MultiMapUtils;
 import org.apache.commons.collections4.MultiValuedMap;
 
 /**
@@ -117,6 +118,11 @@ public class LinkedHashSetValuedLinkedHashMap<K, V> 
extends AbstractSetValuedMap
         return new LinkedHashSet<>(initialSetCapacity);
     }
 
+    @Override
+    public LinkedHashSetValuedLinkedHashMap<V, K> inverted() {
+        return MultiMapUtils.invert(this, new 
LinkedHashSetValuedLinkedHashMap<V, K>());
+    }
+
     /**
      * Deserializes an instance from an ObjectInputStream.
      *
diff --git 
a/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java 
b/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
index 14f177f2b..7ff98ba07 100644
--- a/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
@@ -29,6 +29,8 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
+import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
+import 
org.apache.commons.collections4.multimap.LinkedHashSetValuedLinkedHashMap;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -116,6 +118,30 @@ class MultiMapUtilsTest {
         assertEquals(new HashSet<>(Arrays.asList(values)), set);
     }
 
+    @Test
+    void testInvert() {
+        final HashSetValuedHashMap<String, String> usages = new 
HashSetValuedHashMap<>();
+
+        final LinkedHashSetValuedLinkedHashMap<String, String> deps = new 
LinkedHashSetValuedLinkedHashMap<>();
+        deps.put("commons-configuration2", "commons-logging");
+        deps.put("commons-configuration2", "commons-lang3");
+        deps.put("commons-configuration2", "commons-text");
+        deps.put("commons-beanutils", "commons-collections");
+        deps.put("commons-beanutils", "commons-logging");
+        MultiMapUtils.invert(deps, usages);
+        final Set<String> loggingUsagesCompile = usages.get("commons-logging");
+        assertEquals("[commons-configuration2, commons-beanutils]", 
loggingUsagesCompile.toString());
+        final Set<String> codecUsagesCompile = usages.get("commons-codec");
+        assertEquals("[]", codecUsagesCompile.toString());
+
+        final LinkedHashSetValuedLinkedHashMap<String, String> optionalDeps = 
new LinkedHashSetValuedLinkedHashMap<>();
+        optionalDeps.put("commons-configuration2", "commons-codec");
+        optionalDeps.put("commons-collections", "commons-codec");
+        MultiMapUtils.invert(optionalDeps, usages);
+        final Set<String> codecUsagesAll = usages.get("commons-codec");
+        assertEquals("[commons-collections, commons-configuration2]", 
codecUsagesAll.toString());
+    }
+
     @Test
     void testIsEmptyWithEmptyMap() {
         assertTrue(MultiMapUtils.isEmpty(new ArrayListValuedHashMap<>()));
diff --git 
a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java
 
b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java
index 47e9a63e6..f65ee297c 100644
--- 
a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java
@@ -93,6 +93,19 @@ public class ArrayListValuedHashMapTest<K, V> extends 
AbstractMultiValuedMapTest
         assertNotSame(map1.hashCode(), map2.hashCode());
     }
 
+    @Test
+    void testInverted() {
+        final ArrayListValuedHashMap<String, String> shopping = new 
ArrayListValuedHashMap<>(4);
+        shopping.put("Alice", "Bread");
+        shopping.put("Alice", "Milk");
+        shopping.put("Alice", "Milk");
+        shopping.put("Bob", "Pizza");
+        shopping.put("Bob", "Bread");
+        shopping.put("Bob", "Bread");
+        assertEquals("{Pizza=[Bob], Bread=[Bob, Bob, Alice], Milk=[Alice, 
Alice]}",
+                shopping.inverted().toString());
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testListValuedMapAdd() {
diff --git 
a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java
 
b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java
index 74353b50a..550a19240 100644
--- 
a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java
@@ -99,6 +99,19 @@ public class ArrayListValuedLinkedHashMapTest<K, V> extends 
AbstractMultiValuedM
         assertNotSame(map1.hashCode(), map2.hashCode());
     }
 
+    @Test
+    void testInverted() {
+        final ArrayListValuedLinkedHashMap<String, String> shopping = new 
ArrayListValuedLinkedHashMap<>(4);
+        shopping.put("Alice", "Bread");
+        shopping.put("Alice", "Milk");
+        shopping.put("Alice", "Milk");
+        shopping.put("Bob", "Pizza");
+        shopping.put("Bob", "Bread");
+        shopping.put("Bob", "Bread");
+        assertEquals("{Bread=[Alice, Bob, Bob], Milk=[Alice, Alice], 
Pizza=[Bob]}",
+                shopping.inverted().toString());
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testListValuedMapAdd() {
diff --git 
a/src/test/java/org/apache/commons/collections4/multimap/HashSetValuedHashMapTest.java
 
b/src/test/java/org/apache/commons/collections4/multimap/HashSetValuedHashMapTest.java
index 7b50d6cd1..419ae3814 100644
--- 
a/src/test/java/org/apache/commons/collections4/multimap/HashSetValuedHashMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/multimap/HashSetValuedHashMapTest.java
@@ -110,6 +110,19 @@ public class HashSetValuedHashMapTest<K, V> extends 
AbstractMultiValuedMapTest<K
         assertEquals("{}", map3.toString());
     }
 
+    @Test
+    void testInverted() {
+        final HashSetValuedHashMap<String, String> dependencies = new 
HashSetValuedHashMap<>();
+        dependencies.put("commons-configuration2", "commons-logging");
+        dependencies.put("commons-configuration2", "commons-lang3");
+        dependencies.put("commons-configuration2", "commons-text");
+        dependencies.put("commons-beanutils", "commons-collections");
+        dependencies.put("commons-beanutils", "commons-logging");
+        final Set<String> loggingUsages = 
dependencies.inverted().get("commons-logging");
+        assertEquals("[commons-beanutils, commons-configuration2]",
+                loggingUsages.toString());
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testSetValuedMapAdd() {
diff --git 
a/src/test/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMapTest.java
 
b/src/test/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMapTest.java
index 2b37cb76d..561432363 100644
--- 
a/src/test/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/multimap/LinkedHashSetValuedLinkedHashMapTest.java
@@ -96,6 +96,19 @@ public class LinkedHashSetValuedLinkedHashMapTest<K, V> 
extends AbstractMultiVal
         assertEquals("{}", map3.toString());
     }
 
+    @Test
+    void testInverted() {
+        final LinkedHashSetValuedLinkedHashMap<String, String> citiesLived = 
new LinkedHashSetValuedLinkedHashMap<>(4);
+        citiesLived.put("Alice", "N.Y.");
+        citiesLived.put("Alice", "L.A.");
+        citiesLived.put("Alice", "Chicago");
+        citiesLived.put("Bob", "N.Y.");
+        citiesLived.put("Cara", "L.A.");
+        citiesLived.put("Cara", "Chicago");
+        assertEquals("{N.Y.=[Alice, Bob], L.A.=[Alice, Cara], Chicago=[Alice, 
Cara]}",
+                citiesLived.inverted().toString());
+    }
+
     @Test
     void testLinkedHashSetValuedLinkedHashMap_2() {
         final Map<K, V> map = new HashMap<>();
diff --git 
a/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
 
b/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
index 37ef87d8f..d83b47108 100644
--- 
a/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/multimap/TransformedMultiValuedMapTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.collections4.multimap;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Collection;
@@ -99,6 +100,11 @@ public class TransformedMultiValuedMapTest<K, V> extends 
AbstractMultiValuedMapT
         assertTrue(transMap.get((K) "D").contains(Integer.valueOf(4)));
     }
 
+    @Test
+    void testInvertedIsUnsupportedByDefault() {
+        assertThrows(UnsupportedOperationException.class, () -> 
makeObject().inverted());
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testKeyTransformedMap() {

Reply via email to