com.landawn.abacus.util.Multimap Maven / Gradle / Ivy
Show all versions of abacus-common Show documentation
/*
* Copyright (c) 2015, Haiyang Li.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.landawn.abacus.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import com.landawn.abacus.annotation.Internal;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.If.OrElse;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.stream.EntryStream;
import com.landawn.abacus.util.stream.Stream;
/**
* Similar to {@link Map}, but in which each key may be associated with multiple values.
*
*
* - a ->1, 2
*
- b -> 3
*
*
* @author Haiyang Li
* @param the key type
* @param
* @param the value type
* @see N#newMultimap(Supplier, Supplier)
* @see N#newListMultimap()
* @see N#newListMultimap(Class, Class)
* @see N#newListMultimap(Supplier, Supplier)
* @see N#newSetMultimap()
* @see N#newSetMultimap(Class, Class)
* @see N#newSetMultimap(Supplier, Supplier)
* @since 0.8
*/
public class Multimap> {
final Supplier extends Map> mapSupplier;
final Supplier extends V> valueSupplier;
final Map valueMap;
/**
* Returns a Multimap>
.
*/
Multimap() {
this(HashMap.class, ArrayList.class);
}
/**
* Returns a Multimap>
.
*
* @param initialCapacity
*/
Multimap(int initialCapacity) {
this(N. newHashMap(initialCapacity), (Supplier) Suppliers.ofList());
}
@SuppressWarnings("rawtypes")
Multimap(final Class extends Map> mapType, final Class extends Collection> valueType) {
this(Maps.mapType2Supplier(mapType), valueType2Supplier(valueType));
}
Multimap(final Supplier extends Map> mapSupplier, final Supplier extends V> valueSupplier) {
this.mapSupplier = mapSupplier;
this.valueSupplier = valueSupplier;
this.valueMap = mapSupplier.get();
}
@Internal
Multimap(final Map valueMap, final Supplier extends V> valueSupplier) {
this.mapSupplier = Maps.mapType2Supplier(valueMap.getClass());
this.valueSupplier = valueSupplier;
this.valueMap = valueMap;
}
/**
* Value type 2 supplier.
*
* @param valueType
* @return
*/
@SuppressWarnings("rawtypes")
static Supplier valueType2Supplier(final Class extends Collection> valueType) {
return Suppliers.ofCollection(valueType);
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param map
* @param multimapSupplier
* @return
*/
public static , M extends Multimap> M from(final Map extends K, ? extends E> map,
final IntFunction extends M> multimapSupplier) {
final M multimap = multimapSupplier.apply(map == null ? 0 : map.size());
if (N.notNullOrEmpty(map)) {
multimap.putAll(map);
}
return multimap;
}
/**
*
* @param
* @param the key type
* @param the value type
* @param
* @param
* @param c
* @param keyMapper
* @param multimapSupplier
* @return
* @throws X the x
*/
public static , M extends Multimap, X extends Exception> M from(final Collection extends T> c,
final Throwables.Function super T, ? extends K, X> keyMapper, final IntFunction extends M> multimapSupplier) throws X {
final M multimap = multimapSupplier.apply(c == null ? 0 : c.size());
if (N.notNullOrEmpty(c)) {
for (T e : c) {
multimap.put(keyMapper.apply(e), e);
}
}
return multimap;
}
/**
*
* @param
* @param the key type
* @param
* @param the value type
* @param
* @param
* @param
* @param c
* @param keyMapper
* @param valueExtractor
* @param multimapSupplier
* @return
* @throws X the x
* @throws X2 the x2
*/
public static , M extends Multimap, X extends Exception, X2 extends Exception> M from(
final Collection extends T> c, final Throwables.Function super T, ? extends K, X> keyMapper,
final Throwables.Function super T, ? extends E, X2> valueExtractor, final IntFunction extends M> multimapSupplier) throws X, X2 {
final M multimap = multimapSupplier.apply(c == null ? 0 : c.size());
if (N.notNullOrEmpty(c)) {
for (T e : c) {
multimap.put(keyMapper.apply(e), valueExtractor.apply(e));
}
}
return multimap;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param map
* @param multimapSupplier
* @return
*/
public static , M extends Multimap> M fromm(final Map extends K, ? extends Collection extends E>> map,
final IntFunction extends M> multimapSupplier) {
final M multimap = multimapSupplier.apply(map == null ? 0 : map.size());
if (N.notNullOrEmpty(map)) {
for (Map.Entry extends K, ? extends Collection extends E>> entry : map.entrySet()) {
multimap.putAll(entry.getKey(), entry.getValue());
}
}
return multimap;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param map
* @param multimapSupplier
* @return
* @see ListMultimap#invertFrom(Map)
* @see SetMultimap#invertFrom(Map)
*/
public static , M extends Multimap> M invertFrom(final Map map,
final IntFunction extends M> multimapSupplier) {
final M multimap = multimapSupplier.apply(map == null ? 0 : map.size());
if (N.notNullOrEmpty(map)) {
for (Map.Entry entry : map.entrySet()) {
multimap.put(entry.getValue(), entry.getKey());
}
}
return multimap;
}
/**
* Flat invert from.
*
* @param the key type
* @param
* @param the value type
* @param
* @param map
* @param multimapSupplier
* @return
* @see ListMultimap#flatInvertFrom(Map)
* @see SetMultimap#flatInvertFrom(Map)
*/
public static , M extends Multimap> M flatInvertFrom(final Map> map,
final IntFunction extends M> multimapSupplier) {
final M multimap = multimapSupplier.apply(map == null ? 0 : map.size());
if (N.notNullOrEmpty(map)) {
for (Map.Entry> entry : map.entrySet()) {
final Collection extends E> c = entry.getValue();
if (N.notNullOrEmpty(c)) {
for (E e : c) {
multimap.put(e, entry.getKey());
}
}
}
}
return multimap;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param
* @param multimap
* @param multimapSupplier
* @return
*/
public static , VV extends Collection, M extends Multimap> M invertFrom(final Multimap multimap,
final IntFunction extends M> multimapSupplier) {
final M res = multimapSupplier.apply(multimap == null ? 0 : multimap.size());
if (N.notNullOrEmpty(multimap)) {
for (Map.Entry entry : multimap.entrySet()) {
final V c = entry.getValue();
if (N.notNullOrEmpty(c)) {
for (E e : c) {
res.put(e, entry.getKey());
}
}
}
}
return res;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param a
* @param b
* @param multimapSupplier
* @return
*/
public static , M extends Multimap> M concat(final Map extends K, ? extends E> a,
final Map extends K, ? extends E> b, final IntFunction extends M> multimapSupplier) {
final M res = multimapSupplier.apply((a == null ? 0 : a.size()) + (b == null ? 0 : b.size()));
res.putAll(a);
res.putAll(b);
return res;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param
* @param a
* @param b
* @param c
* @param multimapSupplier
* @return
*/
public static , M extends Multimap> M concat(final Map extends K, ? extends E> a,
final Map extends K, ? extends E> b, final Map extends K, ? extends E> c, final IntFunction extends M> multimapSupplier) {
final M res = multimapSupplier.apply((a == null ? 0 : a.size()) + (b == null ? 0 : b.size()) + (c == null ? 0 : c.size()));
res.putAll(a);
res.putAll(b);
res.putAll(c);
return res;
}
/**
*
* @param the key type
* @param
* @param the value type
* @param map
* @param valueSupplier
* @return
*/
public static > Multimap wrap(final Map map, final Supplier extends V> valueSupplier) {
N.checkArgNotNull(map, "map");
N.checkArgNotNull(valueSupplier, "valueSupplier");
return new Multimap<>(map, valueSupplier);
}
/**
*
* @param key
* @return
*/
public V get(final Object key) {
return valueMap.get(key);
}
/**
* Gets the or default.
*
* @param key
* @param defaultValue
* @return
*/
public V getOrDefault(final Object key, V defaultValue) {
final V value = valueMap.get(key);
if (value == null) {
return defaultValue;
}
return value;
}
/**
*
* @param key
* @param e
* @return
*/
public boolean put(final K key, final E e) {
V val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
valueMap.put(key, val);
}
return val.add(e);
}
/**
* If the specified value is not already associated with the specified key
* associates it with the given key and returns {@code true}, else returns {@code false}.
*
* @param key
* @param e
* @return
*/
public boolean putIfAbsent(final K key, final E e) {
V val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
valueMap.put(key, val);
} else if (val.contains(e)) {
return false;
}
return val.add(e);
}
/**
* If the specified key is not already associated with any value (or is mapped
* to {@code null}) associates it with the given value and returns {@code true}, else returns {@code false}.
*
* @param key
* @param e
* @return
*/
public boolean putIfKeyAbsent(final K key, final E e) {
V val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
val.add(e);
valueMap.put(key, val);
return true;
}
return false;
}
/**
*
* @param key
* @param c
* @return
*/
public boolean putAll(final K key, final Collection extends E> c) {
if (N.isNullOrEmpty(c)) {
return false;
}
V val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
valueMap.put(key, val);
}
return val.addAll(c);
}
/**
* If the specified key is not already associated with any value (or is mapped
* to {@code null}) associates it with the given values in the specified {@code collection}
* and returns {@code true}, else returns {@code false}.
*
* @param key
* @param c
* @return
*/
public boolean putAllIfKeyAbsent(final K key, final Collection extends E> c) {
if (N.isNullOrEmpty(c)) {
return false;
}
V val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
val.addAll(c);
valueMap.put(key, val);
return true;
}
return false;
}
/**
*
* @param m
* @return
*/
public boolean putAll(final Map extends K, ? extends E> m) {
if (N.isNullOrEmpty(m)) {
return false;
}
boolean result = false;
K key = null;
V val = null;
for (Map.Entry extends K, ? extends E> e : m.entrySet()) {
key = e.getKey();
val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
valueMap.put(key, val);
}
result |= val.add(e.getValue());
}
return result;
}
/**
*
* @param m
* @return
*/
public boolean putAll(final Multimap extends K, ? extends E, ? extends Collection extends E>> m) {
if (N.isNullOrEmpty(m)) {
return false;
}
boolean result = false;
K key = null;
V val = null;
for (Map.Entry extends K, ? extends Collection extends E>> e : m.entrySet()) {
if (N.isNullOrEmpty(e.getValue())) {
continue;
}
key = e.getKey();
val = valueMap.get(key);
if (val == null) {
val = valueSupplier.get();
valueMap.put(key, val);
}
result |= val.addAll(e.getValue());
}
return result;
}
/**
*
* @param key
* @param e
* @return
*/
public boolean remove(final Object key, final Object e) {
V val = valueMap.get(key);
if (val != null && val.remove(e)) {
if (val.isEmpty()) {
valueMap.remove(key);
}
return true;
}
return false;
}
/**
* Removes the all.
*
* @param key
* @return values associated with specified key.
*/
public V removeAll(final Object key) {
return valueMap.remove(key);
}
/**
* Removes the all.
*
* @param key
* @param c
* @return
*/
public boolean removeAll(final Object key, final Collection> c) {
if (N.isNullOrEmpty(c)) {
return false;
}
boolean result = false;
final V val = valueMap.get(key);
if (N.notNullOrEmpty(val)) {
result = val.removeAll(c);
if (val.isEmpty()) {
valueMap.remove(key);
}
}
return result;
}
/**
*
*
* ListMultimap listMultimap = ListMultimap.of("a", 1, "b", 2, "a", 2, "a", 2); // -> {a=[1, 2, 2], b=[2]}
* listMultimap.removeAll(N.asMap("a", N.asList(2))); // -> {a=[1], b=[2]}
*
*
*
* @param m
* @return
*/
public boolean removeAll(final Map, ? extends Collection>> m) {
if (N.isNullOrEmpty(m)) {
return false;
}
boolean result = false;
Object key = null;
V val = null;
for (Map.Entry, ? extends Collection>> e : m.entrySet()) {
key = e.getKey();
val = valueMap.get(key);
if (N.notNullOrEmpty(val)) {
if (!result) {
result = val.removeAll(e.getValue());
} else {
val.removeAll(e.getValue());
}
if (val.isEmpty()) {
valueMap.remove(key);
}
}
}
return result;
}
/**
* Removes the all.
*
* @param m
* @return
*/
public boolean removeAll(final Multimap, ?, ?> m) {
if (N.isNullOrEmpty(m)) {
return false;
}
boolean result = false;
Object key = null;
V val = null;
for (Map.Entry, ? extends Collection>> e : m.entrySet()) {
key = e.getKey();
val = valueMap.get(key);
if (N.notNullOrEmpty(val) && N.notNullOrEmpty(e.getValue())) {
if (!result) {
result = val.removeAll(e.getValue());
} else {
val.removeAll(e.getValue());
}
if (val.isEmpty()) {
valueMap.remove(key);
}
}
}
return result;
}
/**
* Remove the specified value (one occurrence) from the value set associated with keys which satisfy the specified predicate
.
*
* @param
* @param value
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeIf(E value, Throwables.Predicate super K, X> predicate) throws X {
Set removingKeys = null;
for (K key : this.valueMap.keySet()) {
if (predicate.test(key)) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(key);
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
boolean modified = false;
for (K k : removingKeys) {
if (remove(k, value)) {
modified = true;
}
}
return modified;
}
/**
* Remove the specified value (one occurrence) from the value set associated with keys which satisfy the specified predicate
.
*
* @param
* @param value
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeIf(E value, Throwables.BiPredicate super K, ? super V, X> predicate) throws X {
Set removingKeys = null;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue())) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(entry.getKey());
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
boolean modified = false;
for (K k : removingKeys) {
if (remove(k, value)) {
modified = true;
}
}
return modified;
}
/**
* Remove the specified values (all occurrences) from the value set associated with keys which satisfy the specified predicate
.
*
* @param
* @param values
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeAllIf(Collection> values, Throwables.Predicate super K, X> predicate) throws X {
Set removingKeys = null;
for (K key : this.valueMap.keySet()) {
if (predicate.test(key)) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(key);
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
boolean modified = false;
for (K k : removingKeys) {
if (removeAll(k, values)) {
modified = true;
}
}
return modified;
}
/**
* Remove the specified values (all occurrences) from the value set associated with keys which satisfy the specified predicate
.
*
* @param
* @param values
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeAllIf(Collection> values, Throwables.BiPredicate super K, ? super V, X> predicate) throws X {
Set removingKeys = null;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue())) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(entry.getKey());
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
boolean modified = false;
for (K k : removingKeys) {
if (removeAll(k, values)) {
modified = true;
}
}
return modified;
}
/**
* Remove all the values associated with keys which satisfy the specified predicate
.
*
* @param
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeAllIf(Throwables.Predicate super K, X> predicate) throws X {
Set removingKeys = null;
for (K key : this.valueMap.keySet()) {
if (predicate.test(key)) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(key);
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
for (K k : removingKeys) {
removeAll(k);
}
return true;
}
/**
* Remove all the values associated with keys which satisfy the specified predicate
.
*
* @param
* @param predicate
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean removeAllIf(Throwables.BiPredicate super K, ? super V, X> predicate) throws X {
Set removingKeys = null;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue())) {
if (removingKeys == null) {
removingKeys = N.newHashSet();
}
removingKeys.add(entry.getKey());
}
}
if (N.isNullOrEmpty(removingKeys)) {
return false;
}
for (K k : removingKeys) {
removeAll(k);
}
return true;
}
/**
* Replace the specified {@code oldValue} (one occurrence) with the specified {@code newValue}.
* False
is returned if no oldValue
is found.
*
* @param key
* @param oldValue
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
*/
public boolean replace(final K key, final E oldValue, final E newValue) {
final V val = valueMap.get(key);
if (val == null) {
return false;
}
return replaceOne(val, oldValue, newValue);
}
private boolean replaceOne(final V val, final E oldValue, final E newValue) {
if (val instanceof List) {
final List list = (List) val;
if (list instanceof ArrayList) {
if (oldValue == null) {
for (int i = 0, len = list.size(); i < len; i++) {
if (list.get(i) == null) {
list.set(i, newValue);
return true;
}
}
} else {
for (int i = 0, len = list.size(); i < len; i++) {
if (oldValue.equals(list.get(i))) {
list.set(i, newValue);
return true;
}
}
}
} else {
final ListIterator iter = list.listIterator();
if (oldValue == null) {
while (iter.hasNext()) {
if (iter.next() == null) {
iter.set(newValue);
return true;
}
}
} else {
while (iter.hasNext()) {
if (oldValue.equals(iter.next())) {
iter.set(newValue);
return true;
}
}
}
}
return false;
} else {
if (val.remove(oldValue)) {
val.add(newValue);
return true;
}
return false;
}
}
/**
* Replace the specified {@code oldValue} (all occurrences) with the specified {@code newValue}.
* False
is returned if no oldValue
is found.
*
* @param key
* @param oldValues
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
*/
public boolean replaceAll(final K key, final E oldValue, final E newValue) {
final V val = valueMap.get(key);
if (val == null) {
return false;
}
return replaceAll(val, oldValue, newValue);
}
private boolean replaceAll(final V val, final E oldValue, final E newValue) {
boolean modified = false;
if (val instanceof List) {
final List list = (List) val;
if (list instanceof ArrayList) {
if (oldValue == null) {
for (int i = 0, len = list.size(); i < len; i++) {
if (list.get(i) == null) {
list.set(i, newValue);
modified = true;
}
}
} else {
for (int i = 0, len = list.size(); i < len; i++) {
if (oldValue.equals(list.get(i))) {
list.set(i, newValue);
modified = true;
}
}
}
} else {
final ListIterator iter = list.listIterator();
if (oldValue == null) {
while (iter.hasNext()) {
if (iter.next() == null) {
iter.set(newValue);
modified = true;
}
}
} else {
while (iter.hasNext()) {
if (oldValue.equals(iter.next())) {
iter.set(newValue);
modified = true;
}
}
}
}
} else {
final Object[] tmp = val.toArray();
modified = N.replaceAll(tmp, oldValue, newValue) > 0;
val.clear();
val.addAll(Arrays.asList((E[]) tmp));
}
return modified;
}
/**
* Replace all the specified {@code oldValues} (all occurrences) with single specified {@code newValue}.
* False
is returned if no oldValue
is found.
*
* @param key
* @param oldValues
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @deprecated
*/
@Deprecated
public boolean replaceAll(final K key, final Collection extends E> oldValues, final E newValue) {
final V val = valueMap.get(key);
if (val == null) {
return false;
}
if (val.removeAll(oldValues)) {
val.add(newValue);
return true;
}
return false;
}
/**
* Replace the specified {@code oldValue} (one occurrence) from the value set associated with keys which satisfy the specified {@code predicate} with the specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValue
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean replaceIf(Throwables.Predicate super K, X> predicate, E oldValue, E newValue) throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey())) {
modified = modified | replaceOne(entry.getValue(), oldValue, newValue);
}
}
return modified;
}
/**
* Replace the specified {@code oldValue} (one occurrence) from the value set associated with keys which satisfy the specified {@code predicate} with the specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValue
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean replaceIf(Throwables.BiPredicate super K, ? super V, X> predicate, E oldValue, E newValue) throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue())) {
modified = modified | replaceOne(entry.getValue(), oldValue, newValue);
}
}
return modified;
}
/**
* Replace the specified {@code oldValue} (all occurrences) from the value set associated with keys which satisfy the specified {@code predicate} with the specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValue
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean replaceAllIf(Throwables.Predicate super K, X> predicate, E oldValue, E newValue) throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey())) {
modified = modified | replaceAll(entry.getValue(), oldValue, newValue);
}
}
return modified;
}
/**
* Replace the specified {@code oldValue} (all occurrences) from the value set associated with keys which satisfy the specified {@code predicate} with the specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValue
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
*/
public boolean replaceAllIf(Throwables.BiPredicate super K, ? super V, X> predicate, E oldValue, E newValue) throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue())) {
modified = modified | replaceAll(entry.getValue(), oldValue, newValue);
}
}
return modified;
}
/**
* Replace all the specified {@code oldValue} (all occurrences) from the value set associated with keys which satisfy the specified {@code predicate} with single specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValues
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
* @deprecated
*/
@Deprecated
public boolean replaceAllIf(Throwables.Predicate super K, X> predicate, Collection extends E> oldValues, E newValue) throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey()) && entry.getValue().removeAll(oldValues)) {
entry.getValue().add(newValue);
modified = true;
}
}
return modified;
}
/**
* Replace all the specified {@code oldValue} (all occurrences) from the value set associated with keys which satisfy the specified {@code predicate} with single specified {@code newValue}.
*
* @param
* @param predicate
* @param oldValues
* @param newValue
* @return true
if this Multimap is modified by this operation, otherwise false
.
* @throws X the x
* @deprecated
*/
@Deprecated
public boolean replaceAllIf(Throwables.BiPredicate super K, ? super V, X> predicate, Collection extends E> oldValues, E newValue)
throws X {
boolean modified = false;
for (Map.Entry entry : this.valueMap.entrySet()) {
if (predicate.test(entry.getKey(), entry.getValue()) && entry.getValue().removeAll(oldValues)) {
entry.getValue().add(newValue);
modified = true;
}
}
return modified;
}
/**
* The associated keys will be removed if null or empty values are returned by the specified function
.
*
* @param
* @param function
* @throws X the x
*/
public void replaceAll(Throwables.BiFunction super K, ? super V, ? extends V, X> function) throws X {
List keyToRemove = null;
V newVal = null;
for (Map.Entry entry : valueMap.entrySet()) {
newVal = function.apply(entry.getKey(), entry.getValue());
if (N.isNullOrEmpty(newVal)) {
if (keyToRemove == null) {
keyToRemove = new ArrayList<>();
}
keyToRemove.add(entry.getKey());
} else {
try {
entry.setValue(newVal);
} catch (IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
}
}
if (N.notNullOrEmpty(keyToRemove)) {
for (K key : keyToRemove) {
valueMap.remove(key);
}
}
}
/**
*
* @param key
* @param e
* @return
*/
public boolean contains(final Object key, final Object e) {
final V val = valueMap.get(key);
return val == null ? false : val.contains(e);
}
/**
*
* @param key
* @return
*/
public boolean containsKey(final Object key) {
return valueMap.containsKey(key);
}
/**
*
* @param e
* @return
*/
public boolean containsValue(final Object e) {
Collection values = values();
for (V val : values) {
if (val.contains(e)) {
return true;
}
}
return false;
}
/**
*
* @param key
* @param c
* @return
*/
public boolean containsAll(final Object key, final Collection> c) {
final V val = valueMap.get(key);
return val == null ? false : (N.isNullOrEmpty(c) ? true : val.containsAll(c));
}
/**
* Filter by key.
*
* @param
* @param filter
* @return
* @throws X the x
*/
public Multimap filterByKey(Throwables.Predicate super K, X> filter) throws X {
final Multimap result = new Multimap<>(mapSupplier, valueSupplier);
for (Map.Entry entry : valueMap.entrySet()) {
if (filter.test(entry.getKey())) {
result.valueMap.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
* Filter by value.
*
* @param
* @param filter
* @return
* @throws X the x
*/
public Multimap filterByValue(Throwables.Predicate super V, X> filter) throws X {
final Multimap result = new Multimap<>(mapSupplier, valueSupplier);
for (Map.Entry entry : valueMap.entrySet()) {
if (filter.test(entry.getValue())) {
result.valueMap.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
*
* @param
* @param filter
* @return
* @throws X the x
*/
public Multimap filter(Throwables.BiPredicate super K, ? super V, X> filter) throws X {
final Multimap result = new Multimap<>(mapSupplier, valueSupplier);
for (Map.Entry entry : valueMap.entrySet()) {
if (filter.test(entry.getKey(), entry.getValue())) {
result.valueMap.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
*
* @param
* @param action
* @throws X the x
*/
public void forEach(Throwables.BiConsumer super K, ? super V, X> action) throws X {
N.checkArgNotNull(action);
for (Map.Entry entry : valueMap.entrySet()) {
action.accept(entry.getKey(), entry.getValue());
}
}
/**
* Flat for each.
*
* @param
* @param action
* @throws X the x
*/
public void flatForEach(Throwables.BiConsumer super K, ? super E, X> action) throws X {
N.checkArgNotNull(action);
K key = null;
for (Map.Entry entry : valueMap.entrySet()) {
key = entry.getKey();
for (E e : entry.getValue()) {
action.accept(key, e);
}
}
}
/**
* For each key.
*
* @param
* @param action
* @throws X the x
*/
public void forEachKey(final Throwables.Consumer super K, X> action) throws X {
N.checkArgNotNull(action);
for (K k : valueMap.keySet()) {
action.accept(k);
}
}
/**
* For each value.
*
* @param
* @param action
* @throws X the x
*/
public void forEachValue(final Throwables.Consumer super V, X> action) throws X {
N.checkArgNotNull(action);
for (V v : valueMap.values()) {
action.accept(v);
}
}
/**
* Flat for each value.
*
* @param
* @param action
* @throws X the x
*/
public void flatForEachValue(Throwables.Consumer super E, X> action) throws X {
N.checkArgNotNull(action);
for (V v : valueMap.values()) {
for (E e : v) {
action.accept(e);
}
}
}
/**
* The implementation is equivalent to performing the following steps for this Multimap:
*
*
* final V oldValue = get(key);
*
* if (N.notNullOrEmpty(oldValue)) {
* return oldValue;
* }
*
* final V newValue = mappingFunction.apply(key);
*
* if (N.notNullOrEmpty(newValue)) {
* valueMap.put(key, newValue);
* }
*
* return newValue;
*
*
* @param
* @param key
* @param mappingFunction
* @return
* @throws X the x
*/
public V computeIfAbsent(K key, Throwables.Function super K, ? extends V, X> mappingFunction) throws X {
N.checkArgNotNull(mappingFunction);
final V oldValue = get(key);
if (N.notNullOrEmpty(oldValue)) {
return oldValue;
}
final V newValue = mappingFunction.apply(key);
if (N.notNullOrEmpty(newValue)) {
valueMap.put(key, newValue);
}
return newValue;
}
/**
* The implementation is equivalent to performing the following steps for this Multimap:
*
*
* final V oldValue = get(key);
*
* if (N.isNullOrEmpty(oldValue)) {
* return oldValue;
* }
*
* final V newValue = remappingFunction.apply(key, oldValue);
*
* if (N.notNullOrEmpty(newValue)) {
* valueMap.put(key, newValue);
* } else {
* valueMap.remove(key);
* }
*
* return newValue;
*
*
* @param
* @param key
* @param remappingFunction
* @return
* @throws X the x
*/
public V computeIfPresent(K key, Throwables.BiFunction super K, ? super V, ? extends V, X> remappingFunction) throws X {
N.checkArgNotNull(remappingFunction);
final V oldValue = get(key);
if (N.isNullOrEmpty(oldValue)) {
return oldValue;
}
final V newValue = remappingFunction.apply(key, oldValue);
if (N.notNullOrEmpty(newValue)) {
valueMap.put(key, newValue);
} else {
valueMap.remove(key);
}
return newValue;
}
/**
* The implementation is equivalent to performing the following steps for this Multimap:
*
*
* final V oldValue = get(key);
* final V newValue = remappingFunction.apply(key, oldValue);
*
* if (N.notNullOrEmpty(newValue)) {
* valueMap.put(key, newValue);
* } else {
* if (oldValue != null) {
* valueMap.remove(key);
* }
* }
*
* return newValue;
*
*
* @param
* @param key
* @param remappingFunction
* @return
* @throws X the x
*/
public V compute(K key, Throwables.BiFunction super K, ? super V, ? extends V, X> remappingFunction) throws X {
N.checkArgNotNull(remappingFunction);
final V oldValue = get(key);
final V newValue = remappingFunction.apply(key, oldValue);
if (N.notNullOrEmpty(newValue)) {
valueMap.put(key, newValue);
} else {
if (oldValue != null) {
valueMap.remove(key);
}
}
return newValue;
}
/**
* The implementation is equivalent to performing the following steps for this Multimap:
*
*
* final V oldValue = get(key);
* final V newValue = oldValue == null ? value : remappingFunction.apply(oldValue, value);
*
* if (N.notNullOrEmpty(newValue)) {
* valueMap.put(key, newValue);
* } else {
* if (oldValue != null) {
* valueMap.remove(key);
* }
* }
*
* return newValue;
*
*
* @param
* @param key
* @param value
* @param remappingFunction
* @return
* @throws X the x
*/
public V merge(K key, V value, Throwables.BiFunction super V, ? super V, ? extends V, X> remappingFunction) throws X {
N.checkArgNotNull(remappingFunction);
N.checkArgNotNull(value);
final V oldValue = get(key);
final V newValue = oldValue == null ? value : remappingFunction.apply(oldValue, value);
if (N.notNullOrEmpty(newValue)) {
valueMap.put(key, newValue);
} else {
if (oldValue != null) {
valueMap.remove(key);
}
}
return newValue;
}
/**
* The implementation is equivalent to performing the following steps for this Multimap:
*
*
* final V oldValue = get(key);
*
* if (N.isNullOrEmpty(oldValue)) {
* put(key, e);
* return get(key);
* }
*
* final V newValue = remappingFunction.apply(oldValue, e);
*
* if (N.notNullOrEmpty(newValue)) {
* valueMap.put(key, newValue);
* } else {
* if (oldValue != null) {
* valueMap.remove(key);
* }
* }
*
* return newValue;
*
*
* @param
* @param key
* @param e
* @param remappingFunction
* @return
* @throws X the x
*/
public V merge(K key, E e, Throwables.BiFunction super V, ? super E, ? extends V, X> remappingFunction) throws X {
N.checkArgNotNull(remappingFunction);
N.checkArgNotNull(e);
final V oldValue = get(key);
if (N.isNullOrEmpty(oldValue)) {
put(key, e);
return get(key);
}
final V newValue = remappingFunction.apply(oldValue, e);
if (N.notNullOrEmpty(newValue)) {
valueMap.put(key, newValue);
} else {
if (oldValue != null) {
valueMap.remove(key);
}
}
return newValue;
}
public Multimap copy() {
final Multimap copy = new Multimap<>(mapSupplier, valueSupplier);
copy.putAll(this);
return copy;
}
public , M extends Multimap> M inverse(final IntFunction extends M> multimapSupplier) {
return Multimap.invertFrom(this, multimapSupplier);
}
public Set keySet() {
return valueMap.keySet();
}
public Collection values() {
return valueMap.values();
}
public List flatValues() {
final List result = new ArrayList<>(totalCountOfValues());
for (V v : valueMap.values()) {
result.addAll(v);
}
return result;
}
/**
*
* @param
* @param supplier
* @return
*/
public > R flatValues(final IntFunction supplier) {
final R result = supplier.apply(totalCountOfValues());
for (V v : valueMap.values()) {
result.addAll(v);
}
return result;
}
public Set> entrySet() {
return valueMap.entrySet();
}
public Map toMap() {
final Map result = Maps.newOrderingMap(valueMap);
result.putAll(valueMap);
return result;
}
/**
*
* @param
* @param supplier
* @return
*/
public > M toMap(final IntFunction extends M> supplier) {
final M map = supplier.apply(size());
map.putAll(valueMap);
return map;
}
public Multiset toMultiset() {
final Multiset multiset = new Multiset<>(valueMap.getClass());
for (Map.Entry entry : valueMap.entrySet()) {
multiset.set(entry.getKey(), entry.getValue().size());
}
return multiset;
}
// It won't work.
// /**
// * Returns a synchronized {@code Multimap} which shares the same internal {@code Map} with this {@code Multimap}.
// * That's to say the changes in one of the returned {@code Multimap} and this {@code Multimap} will impact another one.
// *
// * @see Collections#synchronizedMap(Map)
// */
// public Multimap synchronizedd() {
// return new Multimap<>(Collections.synchronizedMap(valueMap), concreteValueType);
// }
/**
* Returns a view of this multimap as a {@code Map} from each distinct key
* to the nonempty collection of that key's associated values.
*
* Changes to the returned map or the collections that serve as its values
* will update the underlying multimap, and vice versa.
*
* @return
*/
public Map unwrap() {
return valueMap;
}
public Stream> stream() {
return Stream.of(valueMap.entrySet());
}
public EntryStream entryStream() {
return EntryStream.of(valueMap);
}
/**
* Clear.
*/
public void clear() {
valueMap.clear();
}
public int size() {
return valueMap.size();
}
/**
* Returns the total count of all the elements in all values.
*
* @return
*/
public int totalCountOfValues() {
int count = 0;
for (V v : valueMap.values()) {
count += v.size();
}
return count;
}
/**
* Checks if is empty.
*
* @return true, if is empty
*/
public boolean isEmpty() {
return valueMap.isEmpty();
}
/**
*
* @param
* @param
* @param func
* @return
* @throws X the x
*/
public R apply(Throwables.Function super Multimap, R, X> func) throws X {
return func.apply(this);
}
/**
*
* @param
* @param
* @param func
* @return
* @throws X the x
*/
public Optional applyIfNotEmpty(Throwables.Function super Multimap, R, X> func) throws X {
return isEmpty() ? Optional. empty() : Optional.ofNullable(func.apply(this));
}
/**
*
* @param
* @param action
* @throws X the x
*/
public void accept(Throwables.Consumer super Multimap, X> action) throws X {
action.accept(this);
}
/**
* Accept if not empty.
*
* @param
* @param action
* @throws X the x
*/
public OrElse acceptIfNotEmpty(Throwables.Consumer super Multimap, X> action) throws X {
return If.is(size() > 0).then(this, action);
}
@Override
public int hashCode() {
return valueMap.hashCode();
}
/**
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(final Object obj) {
return obj == this || (obj instanceof Multimap && valueMap.equals(((Multimap) obj).valueMap));
}
@Override
public String toString() {
return valueMap.toString();
}
}