Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/org/apache/commons/collections4/MultiMapUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -170,6 +171,27 @@ public static boolean isEmpty(final MultiValuedMap<?, ?> map) {
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.
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/apache/commons/collections4/MultiValuedMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections4.MultiMapUtils;
import org.apache.commons.collections4.MultiValuedMap;

/**
Expand Down Expand Up @@ -118,6 +119,11 @@ protected ArrayList<V> createCollection() {
return new ArrayList<>(initialListCapacity);
}

@Override
public ArrayListValuedHashMap<V, K> inverted() {
return MultiMapUtils.invert(this, new ArrayListValuedHashMap<V, K>());
}

/**
* Deserializes an instance from an ObjectInputStream.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.collections4.MultiMapUtils;
import org.apache.commons.collections4.MultiValuedMap;

/**
Expand Down Expand Up @@ -118,6 +119,11 @@ protected ArrayList<V> createCollection() {
return new ArrayList<>(initialListCapacity);
}

@Override
public ArrayListValuedLinkedHashMap<V, K> inverted() {
return MultiMapUtils.invert(this, new ArrayListValuedLinkedHashMap<>());
}

/**
* Deserializes an instance from an ObjectInputStream.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.HashSet;
import java.util.Map;

import org.apache.commons.collections4.MultiMapUtils;
import org.apache.commons.collections4.MultiValuedMap;

/**
Expand Down Expand Up @@ -117,6 +118,11 @@ protected HashSet<V> createCollection() {
return new HashSet<>(initialSetCapacity);
}

@Override
public HashSetValuedHashMap<V, K> inverted() {
return MultiMapUtils.invert(this, new HashSetValuedHashMap<V, K>());
}

/**
* Deserializes an instance from an ObjectInputStream.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.LinkedHashSet;
import java.util.Map;

import org.apache.commons.collections4.MultiMapUtils;
import org.apache.commons.collections4.MultiValuedMap;

/**
Expand Down Expand Up @@ -117,6 +118,11 @@ protected LinkedHashSet<V> createCollection() {
return new LinkedHashSet<>(initialSetCapacity);
}

@Override
public LinkedHashSetValuedLinkedHashMap<V, K> inverted() {
return MultiMapUtils.invert(this, new LinkedHashSetValuedLinkedHashMap<V, K>());
}

/**
* Deserializes an instance from an ObjectInputStream.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
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;

/**
Expand Down Expand Up @@ -116,6 +118,30 @@ void testGetValuesAsSet() {
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<>()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ void testEqualsHashCodeContract() {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ void testEqualsHashCodeContract() {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ void testHashSetValueHashMap_1() {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ void testHashSetValueHashMap_1() {
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<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -99,6 +100,11 @@ void testFactory_decorateTransform() {
assertTrue(transMap.get((K) "D").contains(Integer.valueOf(4)));
}

@Test
void testInvertedIsUnsupportedByDefault() {
assertThrows(UnsupportedOperationException.class, () -> makeObject().inverted());
}

@Test
@SuppressWarnings("unchecked")
void testKeyTransformedMap() {
Expand Down