/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.collections; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Multiset; import com.google.common.collect.SetMultimap; import com.jcwhatever.nucleus.collections.wrap.CollectionWrapper; import com.jcwhatever.nucleus.collections.wrap.IteratorWrapper; import com.jcwhatever.nucleus.collections.wrap.SetWrapper; import com.jcwhatever.nucleus.utils.PreCon; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Allows adding multiple values per key. * * <p>Also allows getting keys based on value.</p> * * @param <K> Key type * @param <V> Value type */ public class MultiBiMap<K, V> implements SetMultimap<K, V> { // keyed to key type private SetMultimap<K, V> _keyToValue; // keyed to value type private SetMultimap<V, K> _valueToKey; private final MapWrapper _mapWrapper = new MapWrapper(); private final EntrySetWrapper _entrySet = new EntrySetWrapper(); private final KeySetWrapper _keySet = new KeySetWrapper(); /** * Constructor. */ public MultiBiMap() { this(10, 10); } /** * Constructor. * * @param size The initial capacity of the map. */ public MultiBiMap(int size) { this(size, 10); } /** * Constructor. * * @param size The initial capacity of the map. * @param entrySize The initial capacity of the internal collections for each key. */ public MultiBiMap(int size, int entrySize) { PreCon.positiveNumber(size); PreCon.positiveNumber(entrySize); _keyToValue = MultimapBuilder.hashKeys(size).linkedHashSetValues(entrySize).build(); _valueToKey = HashMultimap.create(size, entrySize); } /** * Get the number of keys in the map. */ public int keySize() { return _keyToValue.size(); } /** * Get the number of unique values in the map. */ public int valueSize() { return _valueToKey.size(); } /** * Put a value into the map. * * @param key The key associated with the value. * @param value The value * * @return Self for chaining */ public MultiBiMap<K, V> add(K key, V value) { PreCon.notNull(key); PreCon.notNull(value); put(key, value); return this; } /** * Gets a key associated with the value. * * <p>Order of keys is not guaranteed.</p> * * @param value The value to check. */ @Nullable public K getKey(V value) { PreCon.notNull(value); Set<K> set = _valueToKey.get(value); if (set == null) return null; if (set.isEmpty()) return null; return new ArrayList<K>(set).get(0); } /** * Get a list of keys associated with the specified value. * * @param value The value to check. */ @Nullable public Set<K> getKeys(V value) { PreCon.notNull(value); return _valueToKey.get(value); } /** * Get a list of keys associated with the specified value. * * @param value The value to check. * @param output The output collection to add results to. * * @return The output collection. */ @Nullable public <T extends Collection<K>> T getKeys(V value, T output) { PreCon.notNull(value); PreCon.notNull(output); output.addAll(_valueToKey.get(value)); return output; } /** * Return the first value associated with a key in the map. * * @param key The key to check. */ @Nullable public V getValue(K key) { PreCon.notNull(key); Set<V> values = _keyToValue.get(key); if (values.isEmpty()) return null; return new ArrayList<V>(values).get(0); } /** * Remove a value from a key value collection. * * @param key The key to check. * @param value The value to remove. * * @return True if value was found and removed. */ public boolean removeValue(K key, V value) { PreCon.notNull(key); PreCon.notNull(value); Set<V> values = _keyToValue.get(key); return values != null && values.remove(value); } /** * Remove a value from all keys in the map. * * @param value The value to remove. * * @return Number of values removed. */ public int removeValues(V value) { PreCon.notNull(value); Set<K> keys = _valueToKey.get(value); if (keys == null) return 0; int removeCount = 0; for (K key : keys) { Set<V> values = _keyToValue.get(key); if (values == null) continue; values.remove(value); removeCount++; if (values.size() == 0) removeAll(key); } return removeCount; } @Override public void clear() { _keyToValue.clear(); _valueToKey.clear(); } @Override public int size() { return _keyToValue.size(); } @Override public boolean isEmpty() { return _keyToValue.isEmpty(); } @Override public boolean containsKey(@Nonnull Object key) { PreCon.notNull(key); return _keyToValue.containsKey(key); } @Override public boolean containsValue(@Nonnull Object value) { PreCon.notNull(value); return _valueToKey.containsKey(value); } @Override public boolean containsEntry(@Nonnull Object key, @Nonnull Object value) { PreCon.notNull(key); PreCon.notNull(value); return _keyToValue.containsEntry(key, value); } @Override public Set<K> keySet() { return _keySet; } @Override public Multiset<K> keys() { return _keyToValue.keys(); // TODO: Wrap } @Override public boolean put(@Nonnull K key, @Nonnull V value) { PreCon.notNull(key); PreCon.notNull(value); if (_keyToValue.put(key, value)) { _valueToKey.put(value, key); return true; } return false; } @Override public boolean remove(@Nonnull Object key, @Nonnull Object value) { PreCon.notNull(key); PreCon.notNull(value); if (_keyToValue.remove(key, value)) { _valueToKey.remove(value, key); return true; } return false; } @Override public boolean putAll(@Nonnull K k, Iterable<? extends V> iterable) { PreCon.notNull(k); PreCon.notNull(iterable); boolean isChanged = false; for (V value : iterable) { isChanged = put(k, value) || isChanged; } return isChanged; } @Override public boolean putAll(Multimap<? extends K, ? extends V> multimap) { PreCon.notNull(multimap); boolean isChanged = false; for (Entry<? extends K, ? extends V> entry : multimap.entries()) { isChanged = put(entry.getKey(), entry.getValue()) || isChanged; } return isChanged; } @Override public Collection<V> values() { return _valueToKey.keySet(); } @Override public Set<V> get(@Nonnull K key) { PreCon.notNull(key); return _keyToValue.get(key); } @Override public Set<V> removeAll(@Nonnull Object key) { PreCon.notNull(key); Set<V> removed = _keyToValue.removeAll(key); for (V value : removed) { Set<K> keys = _valueToKey.get(value); //noinspection SuspiciousMethodCalls keys.remove(key); } _valueToKey.removeAll(removed); return removed; } @Override public Set<V> replaceValues(K k, Iterable<? extends V> iterable) { throw new UnsupportedOperationException(); // TODO } @Override public Set<Entry<K, V>> entries() { return _entrySet; } @Override public Map<K, Collection<V>> asMap() { return _mapWrapper; } private class KeySetWrapper extends SetWrapper<K> { @Override public Iterator<K> iterator() { return new KeySetIteratorWrapper(); } @Override public boolean add(K key) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object key) { return !MultiBiMap.this.removeAll(key).isEmpty(); } @Override public boolean addAll(Collection<? extends K> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { boolean isChanged = false; for (Object obj : c) { isChanged = remove(obj) || isChanged; } return isChanged; } @Override public boolean retainAll(Collection<?> c) { Set<K> removed = new HashSet<>(_keyToValue.keySet()); for (Object obj : c) { //noinspection SuspiciousMethodCalls removed.remove(obj); } for (K key : removed) { remove(key); } return removed.size() != _keyToValue.keySet().size(); } @Override protected Set<K> set() { return _keyToValue.keySet(); } } private class KeySetIteratorWrapper extends IteratorWrapper<K> { Iterator<K> iterator = _keyToValue.keySet().iterator(); @Override protected Iterator<K> iterator() { return iterator; } @Override public void remove() { MultiBiMap.this.removeAll(getCurrent()); } } private class EntrySetWrapper extends SetWrapper<Entry<K, V>> { @Override public Iterator<Entry<K, V>> iterator() { return new EntrySetIteratorWrapper(); } @Override public boolean add(Entry<K, V> e) { return MultiBiMap.this.put(e.getKey(), e.getValue()); } @Override public boolean remove(Object o) { if (o instanceof Entry) { Entry<?, ?> entry = (Entry<?, ?>)o; return MultiBiMap.this.remove(entry.getKey(), entry.getValue()); } return false; } @Override public boolean addAll(Collection<? extends Entry<K, V>> c) { boolean isChanged = false; for (Entry<K, V> entry : c) { isChanged = MultiBiMap.this.put(entry.getKey(), entry.getValue()) || isChanged; } return isChanged; } @Override public boolean removeAll(Collection<?> c) { boolean isChanged = false; for (Object obj : c) { isChanged = remove(obj) || isChanged; } return isChanged; } @Override public boolean retainAll(Collection<?> c) { Set<Entry<K, V>> entries = new HashSet<>(_keyToValue.entries()); for (Object obj : c) { //noinspection SuspiciousMethodCalls entries.remove(obj); } for (Entry<K, V> entry : entries) { MultiBiMap.this.remove(entry.getKey(), entry.getValue()); } return entries.size() != _keyToValue.entries().size(); } @Override public void clear() { MultiBiMap.this.clear(); } @Override protected Set<Entry<K, V>> set() { return _keyToValue.entries(); } } private class EntrySetIteratorWrapper extends IteratorWrapper<Entry<K, V>> { Iterator<Entry<K, V>> iterator = _keyToValue.entries().iterator(); @Override protected Iterator<Entry<K, V>> iterator() { return iterator; } @Override public void remove() { MultiBiMap.this.remove(getCurrent().getKey(), getCurrent().getValue()); } } private class MapWrapper extends com.jcwhatever.nucleus.collections.wrap.MapWrapper<K, Collection<V>> { final MapKeySetWrapper keySet = new MapKeySetWrapper(); final MapValuesWrapper values = new MapValuesWrapper(); final MapEntrySetWrapper entrySet = new MapEntrySetWrapper(); @Override protected Map<K, Collection<V>> map() { return _keyToValue.asMap(); } @Override public Collection<V> put(K key, Collection<V> values) { List<V> result = new ArrayList<V>(values.size()); for (V value : values) { Set<K> keys = MultiBiMap.this._valueToKey.get(value); if (keys.contains(key)) result.add(value); MultiBiMap.this.put(key, value); } return result; } @Override public Collection<V> remove(Object key) { return MultiBiMap.this.removeAll(key); } @Override public void putAll(Map<? extends K, ? extends Collection<V>> m) { for (Entry<? extends K, ? extends Collection<V>> entry : m.entrySet()) { MultiBiMap.this.putAll(entry.getKey(), entry.getValue()); } } @Override public void clear() { MultiBiMap.this.clear(); } @Override public Set<K> keySet() { return keySet; } @Override public Collection<Collection<V>> values() { return values; } @Override public Set<Entry<K, Collection<V>>> entrySet() { return entrySet; } } private class MapKeySetWrapper extends SetWrapper<K> { @Override public Iterator<K> iterator() { return new MapKeySetIteratorWrapper(); } @Override public boolean add(K key) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object key) { return !MultiBiMap.this.removeAll(key).isEmpty(); } @Override public boolean addAll(Collection<? extends K> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { boolean isChanged = false; for (Object key : c) { isChanged = !MultiBiMap.this.removeAll(key).isEmpty() || isChanged; } return isChanged; } @Override public boolean retainAll(Collection<?> c) { Set<K> removed = new HashSet<>(_keyToValue.keySet()); for (Object key : c) { //noinspection SuspiciousMethodCalls removed.remove(key); } for (K key : removed) { MultiBiMap.this.removeAll(key); } return removed.size() != _keyToValue.keySet().size(); } @Override public void clear() { MultiBiMap.this.clear(); } @Override protected Set<K> set() { return _keyToValue.asMap().keySet(); } } private class MapKeySetIteratorWrapper extends IteratorWrapper<K> { Iterator<K> iterator = _keyToValue.keySet().iterator(); @Override protected Iterator<K> iterator() { return iterator; } @Override public void remove() { MultiBiMap.this.removeAll(getCurrent()); } } private class MapValuesWrapper extends CollectionWrapper<Collection<V>> { @Override protected Collection<Collection<V>> collection() { return _keyToValue.asMap().values(); } @Override public Iterator<Collection<V>> iterator() { return new MapValuesIteratorWrapper(); } @Override public boolean add(Collection<V> e) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Collection<V>> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { MultiBiMap.this.clear(); } } private class MapValuesIteratorWrapper extends IteratorWrapper<Collection<V>> { Iterator<Collection<V>> iterator = _keyToValue.asMap().values().iterator(); @Override protected Iterator<Collection<V>> iterator() { return iterator; } @Override public void remove() { throw new UnsupportedOperationException(); } } private class MapEntrySetWrapper extends SetWrapper<Entry<K, Collection<V>>> { @Override public Iterator<Entry<K, Collection<V>>> iterator() { return collection().iterator(); } @Override public boolean add(Entry<K, Collection<V>> e) { return MultiBiMap.this.putAll(e.getKey(), e.getValue()); } @Override public boolean remove(Object o) { Set<Entry<K, Collection<V>>> entrySet = _keyToValue.asMap().entrySet(); //noinspection SuspiciousMethodCalls if (!entrySet.contains(o)) return false; for (Entry<K, Collection<V>> entry : entrySet) { if (entry.equals(o)) { return !MultiBiMap.this.removeAll(entry.getKey()).isEmpty(); } } return false; } @Override public boolean addAll(Collection<? extends Entry<K, Collection<V>>> c) { boolean isChanged = false; for (Entry<K, Collection<V>> entry : c) { isChanged = MultiBiMap.this.putAll(entry.getKey(), entry.getValue()) || isChanged; } return isChanged; } @Override public boolean removeAll(Collection<?> c) { Set<Entry<K, Collection<V>>> entrySet = _keyToValue.asMap().entrySet(); //noinspection SuspiciousMethodCalls boolean isChanged = false; for (Object obj : c) { //noinspection SuspiciousMethodCalls if (!entrySet.contains(obj)) continue; Iterator<Entry<K, Collection<V>>> iterator = _keyToValue.asMap().entrySet().iterator(); while (iterator.hasNext()) { Entry<K, Collection<V>> entry = iterator.next(); if (entry.equals(obj)) { isChanged = !MultiBiMap.this.removeAll(entry.getKey()).isEmpty() || isChanged; break; } } } return false; } @Override public boolean retainAll(Collection<?> c) { Set<Entry<K, Collection<V>>> removed = new HashSet<>(_keyToValue.asMap().entrySet()); for (Object obj : c) { //noinspection SuspiciousMethodCalls removed.remove(obj); } removeAll(removed); return removed.size() != _keyToValue.asMap().entrySet().size(); } @Override public void clear() { MultiBiMap.this.clear(); } @Override protected Set<Entry<K, Collection<V>>> set() { return _keyToValue.asMap().entrySet(); } } }