|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package org.apache.commons.collections4.multimap; |
|
|
|
import java.io.IOException; |
|
import java.io.ObjectInputStream; |
|
import java.io.ObjectOutputStream; |
|
import java.util.AbstractCollection; |
|
import java.util.AbstractMap; |
|
import java.util.AbstractSet; |
|
import java.util.ArrayList; |
|
import java.util.Collection; |
|
import java.util.Iterator; |
|
import java.util.Map; |
|
import java.util.Map.Entry; |
|
import java.util.Set; |
|
|
|
import org.apache.commons.collections4.CollectionUtils; |
|
import org.apache.commons.collections4.IteratorUtils; |
|
import org.apache.commons.collections4.MapIterator; |
|
import org.apache.commons.collections4.MultiSet; |
|
import org.apache.commons.collections4.MultiValuedMap; |
|
import org.apache.commons.collections4.Transformer; |
|
import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; |
|
import org.apache.commons.collections4.iterators.EmptyMapIterator; |
|
import org.apache.commons.collections4.iterators.IteratorChain; |
|
import org.apache.commons.collections4.iterators.LazyIteratorChain; |
|
import org.apache.commons.collections4.iterators.TransformIterator; |
|
import org.apache.commons.collections4.keyvalue.AbstractMapEntry; |
|
import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; |
|
import org.apache.commons.collections4.multiset.AbstractMultiSet; |
|
import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public abstract class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> { |
|
|
|
|
|
private transient Collection<V> valuesView; |
|
|
|
|
|
private transient EntryValues entryValuesView; |
|
|
|
|
|
private transient MultiSet<K> keysMultiSetView; |
|
|
|
|
|
private transient AsMap asMapView; |
|
|
|
|
|
private transient Map<K, Collection<V>> map; |
|
|
|
|
|
|
|
*/ |
|
protected AbstractMultiValuedMap() { |
|
super(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected AbstractMultiValuedMap(final Map<K, ? extends Collection<V>> map) { |
|
if (map == null) { |
|
throw new NullPointerException("Map must not be null."); |
|
} |
|
this.map = (Map<K, Collection<V>>) map; |
|
} |
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
*/ |
|
protected Map<K, ? extends Collection<V>> getMap() { |
|
return map; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected void setMap(final Map<K, ? extends Collection<V>> map) { |
|
this.map = (Map<K, Collection<V>>) map; |
|
} |
|
|
|
protected abstract Collection<V> createCollection(); |
|
|
|
|
|
@Override |
|
public boolean containsKey(final Object key) { |
|
return getMap().containsKey(key); |
|
} |
|
|
|
@Override |
|
public boolean containsValue(final Object value) { |
|
return values().contains(value); |
|
} |
|
|
|
@Override |
|
public boolean containsMapping(final Object key, final Object value) { |
|
final Collection<V> coll = getMap().get(key); |
|
return coll != null && coll.contains(value); |
|
} |
|
|
|
@Override |
|
public Collection<Entry<K, V>> entries() { |
|
return entryValuesView != null ? entryValuesView : (entryValuesView = new EntryValues()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Collection<V> get(final K key) { |
|
return wrappedCollection(key); |
|
} |
|
|
|
Collection<V> wrappedCollection(final K key) { |
|
return new WrappedCollection(key); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Collection<V> remove(final Object key) { |
|
return CollectionUtils.emptyIfNull(getMap().remove(key)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean removeMapping(final Object key, final Object value) { |
|
final Collection<V> coll = getMap().get(key); |
|
if (coll == null) { |
|
return false; |
|
} |
|
final boolean changed = coll.remove(value); |
|
if (coll.isEmpty()) { |
|
getMap().remove(key); |
|
} |
|
return changed; |
|
} |
|
|
|
@Override |
|
public boolean isEmpty() { |
|
return getMap().isEmpty(); |
|
} |
|
|
|
@Override |
|
public Set<K> keySet() { |
|
return getMap().keySet(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int size() { |
|
// the total size should be cached to improve performance |
|
// but this requires that all modifications of the multimap |
|
// (including the wrapped collections and entry/value |
|
|
|
int size = 0; |
|
for (final Collection<V> col : getMap().values()) { |
|
size += col.size(); |
|
} |
|
return size; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Collection<V> values() { |
|
final Collection<V> vs = valuesView; |
|
return vs != null ? vs : (valuesView = new Values()); |
|
} |
|
|
|
@Override |
|
public void clear() { |
|
getMap().clear(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean put(final K key, final V value) { |
|
Collection<V> coll = getMap().get(key); |
|
if (coll == null) { |
|
coll = createCollection(); |
|
if (coll.add(value)) { |
|
map.put(key, coll); |
|
return true; |
|
} |
|
return false; |
|
} |
|
return coll.add(value); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean putAll(final Map<? extends K, ? extends V> map) { |
|
if (map == null) { |
|
throw new NullPointerException("Map must not be null."); |
|
} |
|
boolean changed = false; |
|
for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { |
|
changed |= put(entry.getKey(), entry.getValue()); |
|
} |
|
return changed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean putAll(final MultiValuedMap<? extends K, ? extends V> map) { |
|
if (map == null) { |
|
throw new NullPointerException("Map must not be null."); |
|
} |
|
boolean changed = false; |
|
for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) { |
|
changed |= put(entry.getKey(), entry.getValue()); |
|
} |
|
return changed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public MultiSet<K> keys() { |
|
if (keysMultiSetView == null) { |
|
keysMultiSetView = UnmodifiableMultiSet.unmodifiableMultiSet(new KeysMultiSet()); |
|
} |
|
return keysMultiSetView; |
|
} |
|
|
|
@Override |
|
public Map<K, Collection<V>> asMap() { |
|
return asMapView != null ? asMapView : (asMapView = new AsMap(map)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean putAll(final K key, final Iterable<? extends V> values) { |
|
if (values == null) { |
|
throw new NullPointerException("Values must not be null."); |
|
} |
|
|
|
if (values instanceof Collection<?>) { |
|
final Collection<? extends V> valueCollection = (Collection<? extends V>) values; |
|
return !valueCollection.isEmpty() && get(key).addAll(valueCollection); |
|
} |
|
final Iterator<? extends V> it = values.iterator(); |
|
return it.hasNext() && CollectionUtils.addAll(get(key), it); |
|
} |
|
|
|
@Override |
|
public MapIterator<K, V> mapIterator() { |
|
if (size() == 0) { |
|
return EmptyMapIterator.emptyMapIterator(); |
|
} |
|
return new MultiValuedMapIterator(); |
|
} |
|
|
|
@Override |
|
public boolean equals(final Object obj) { |
|
if (this == obj) { |
|
return true; |
|
} |
|
if (obj instanceof MultiValuedMap) { |
|
return asMap().equals(((MultiValuedMap<?, ?>) obj).asMap()); |
|
} |
|
return false; |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return getMap().hashCode(); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return getMap().toString(); |
|
} |
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
class WrappedCollection implements Collection<V> { |
|
|
|
protected final K key; |
|
|
|
public WrappedCollection(final K key) { |
|
this.key = key; |
|
} |
|
|
|
protected Collection<V> getMapping() { |
|
return getMap().get(key); |
|
} |
|
|
|
@Override |
|
public boolean add(final V value) { |
|
Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
coll = createCollection(); |
|
AbstractMultiValuedMap.this.map.put(key, coll); |
|
} |
|
return coll.add(value); |
|
} |
|
|
|
@Override |
|
public boolean addAll(final Collection<? extends V> other) { |
|
Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
coll = createCollection(); |
|
AbstractMultiValuedMap.this.map.put(key, coll); |
|
} |
|
return coll.addAll(other); |
|
} |
|
|
|
@Override |
|
public void clear() { |
|
final Collection<V> coll = getMapping(); |
|
if (coll != null) { |
|
coll.clear(); |
|
AbstractMultiValuedMap.this.remove(key); |
|
} |
|
} |
|
|
|
@Override |
|
public Iterator<V> iterator() { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return IteratorUtils.EMPTY_ITERATOR; |
|
} |
|
return new ValuesIterator(key); |
|
} |
|
|
|
@Override |
|
public int size() { |
|
final Collection<V> coll = getMapping(); |
|
return coll == null ? 0 : coll.size(); |
|
} |
|
|
|
@Override |
|
public boolean contains(final Object obj) { |
|
final Collection<V> coll = getMapping(); |
|
return coll != null && coll.contains(obj); |
|
} |
|
|
|
@Override |
|
public boolean containsAll(final Collection<?> other) { |
|
final Collection<V> coll = getMapping(); |
|
return coll != null && coll.containsAll(other); |
|
} |
|
|
|
@Override |
|
public boolean isEmpty() { |
|
final Collection<V> coll = getMapping(); |
|
return coll == null || coll.isEmpty(); |
|
} |
|
|
|
@Override |
|
public boolean remove(final Object item) { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return false; |
|
} |
|
|
|
final boolean result = coll.remove(item); |
|
if (coll.isEmpty()) { |
|
AbstractMultiValuedMap.this.remove(key); |
|
} |
|
return result; |
|
} |
|
|
|
@Override |
|
public boolean removeAll(final Collection<?> c) { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return false; |
|
} |
|
|
|
final boolean result = coll.removeAll(c); |
|
if (coll.isEmpty()) { |
|
AbstractMultiValuedMap.this.remove(key); |
|
} |
|
return result; |
|
} |
|
|
|
@Override |
|
public boolean retainAll(final Collection<?> c) { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return false; |
|
} |
|
|
|
final boolean result = coll.retainAll(c); |
|
if (coll.isEmpty()) { |
|
AbstractMultiValuedMap.this.remove(key); |
|
} |
|
return result; |
|
} |
|
|
|
@Override |
|
public Object[] toArray() { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return CollectionUtils.EMPTY_COLLECTION.toArray(); |
|
} |
|
return coll.toArray(); |
|
} |
|
|
|
@Override |
|
@SuppressWarnings("unchecked") |
|
public <T> T[] toArray(final T[] a) { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return (T[]) CollectionUtils.EMPTY_COLLECTION.toArray(a); |
|
} |
|
return coll.toArray(a); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
final Collection<V> coll = getMapping(); |
|
if (coll == null) { |
|
return CollectionUtils.EMPTY_COLLECTION.toString(); |
|
} |
|
return coll.toString(); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
private class KeysMultiSet extends AbstractMultiSet<K> { |
|
|
|
@Override |
|
public boolean contains(final Object o) { |
|
return getMap().containsKey(o); |
|
} |
|
|
|
@Override |
|
public boolean isEmpty() { |
|
return getMap().isEmpty(); |
|
} |
|
|
|
@Override |
|
public int size() { |
|
return AbstractMultiValuedMap.this.size(); |
|
} |
|
|
|
@Override |
|
protected int uniqueElements() { |
|
return getMap().size(); |
|
} |
|
|
|
@Override |
|
public int getCount(final Object object) { |
|
int count = 0; |
|
final Collection<V> col = AbstractMultiValuedMap.this.getMap().get(object); |
|
if (col != null) { |
|
count = col.size(); |
|
} |
|
return count; |
|
} |
|
|
|
@Override |
|
protected Iterator<MultiSet.Entry<K>> createEntrySetIterator() { |
|
final MapEntryTransformer transformer = new MapEntryTransformer(); |
|
return IteratorUtils.transformedIterator(map.entrySet().iterator(), transformer); |
|
} |
|
|
|
private final class MapEntryTransformer |
|
implements Transformer<Map.Entry<K, Collection<V>>, MultiSet.Entry<K>> { |
|
@Override |
|
public MultiSet.Entry<K> transform(final Map.Entry<K, Collection<V>> mapEntry) { |
|
return new AbstractMultiSet.AbstractEntry<K>() { |
|
@Override |
|
public K getElement() { |
|
return mapEntry.getKey(); |
|
} |
|
|
|
@Override |
|
public int getCount() { |
|
return mapEntry.getValue().size(); |
|
} |
|
}; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private class EntryValues extends AbstractCollection<Entry<K, V>> { |
|
|
|
@Override |
|
public Iterator<Entry<K, V>> iterator() { |
|
return new LazyIteratorChain<Entry<K, V>>() { |
|
|
|
final Collection<K> keysCol = new ArrayList<>(getMap().keySet()); |
|
final Iterator<K> keyIterator = keysCol.iterator(); |
|
|
|
@Override |
|
protected Iterator<? extends Entry<K, V>> nextIterator(final int count) { |
|
if (!keyIterator.hasNext()) { |
|
return null; |
|
} |
|
final K key = keyIterator.next(); |
|
final Transformer<V, Entry<K, V>> entryTransformer = new Transformer<V, Entry<K, V>>() { |
|
|
|
@Override |
|
public Entry<K, V> transform(final V input) { |
|
return new MultiValuedMapEntry(key, input); |
|
} |
|
|
|
}; |
|
return new TransformIterator<>(new ValuesIterator(key), entryTransformer); |
|
} |
|
}; |
|
} |
|
|
|
@Override |
|
public int size() { |
|
return AbstractMultiValuedMap.this.size(); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
private class MultiValuedMapEntry extends AbstractMapEntry<K, V> { |
|
|
|
public MultiValuedMapEntry(final K key, final V value) { |
|
super(key, value); |
|
} |
|
|
|
@Override |
|
public V setValue(final V value) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
private class MultiValuedMapIterator implements MapIterator<K, V> { |
|
|
|
private final Iterator<Entry<K, V>> it; |
|
|
|
private Entry<K, V> current = null; |
|
|
|
public MultiValuedMapIterator() { |
|
this.it = AbstractMultiValuedMap.this.entries().iterator(); |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return it.hasNext(); |
|
} |
|
|
|
@Override |
|
public K next() { |
|
current = it.next(); |
|
return current.getKey(); |
|
} |
|
|
|
@Override |
|
public K getKey() { |
|
if (current == null) { |
|
throw new IllegalStateException(); |
|
} |
|
return current.getKey(); |
|
} |
|
|
|
@Override |
|
public V getValue() { |
|
if (current == null) { |
|
throw new IllegalStateException(); |
|
} |
|
return current.getValue(); |
|
} |
|
|
|
@Override |
|
public void remove() { |
|
it.remove(); |
|
} |
|
|
|
@Override |
|
public V setValue(final V value) { |
|
if (current == null) { |
|
throw new IllegalStateException(); |
|
} |
|
return current.setValue(value); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
private class Values extends AbstractCollection<V> { |
|
@Override |
|
public Iterator<V> iterator() { |
|
final IteratorChain<V> chain = new IteratorChain<>(); |
|
for (final K k : keySet()) { |
|
chain.addIterator(new ValuesIterator(k)); |
|
} |
|
return chain; |
|
} |
|
|
|
@Override |
|
public int size() { |
|
return AbstractMultiValuedMap.this.size(); |
|
} |
|
|
|
@Override |
|
public void clear() { |
|
AbstractMultiValuedMap.this.clear(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private class ValuesIterator implements Iterator<V> { |
|
private final Object key; |
|
private final Collection<V> values; |
|
private final Iterator<V> iterator; |
|
|
|
public ValuesIterator(final Object key) { |
|
this.key = key; |
|
this.values = getMap().get(key); |
|
this.iterator = values.iterator(); |
|
} |
|
|
|
@Override |
|
public void remove() { |
|
iterator.remove(); |
|
if (values.isEmpty()) { |
|
AbstractMultiValuedMap.this.remove(key); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return iterator.hasNext(); |
|
} |
|
|
|
@Override |
|
public V next() { |
|
return iterator.next(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private class AsMap extends AbstractMap<K, Collection<V>> { |
|
final transient Map<K, Collection<V>> decoratedMap; |
|
|
|
AsMap(final Map<K, Collection<V>> map) { |
|
this.decoratedMap = map; |
|
} |
|
|
|
@Override |
|
public Set<Map.Entry<K, Collection<V>>> entrySet() { |
|
return new AsMapEntrySet(); |
|
} |
|
|
|
@Override |
|
public boolean containsKey(final Object key) { |
|
return decoratedMap.containsKey(key); |
|
} |
|
|
|
@Override |
|
public Collection<V> get(final Object key) { |
|
final Collection<V> collection = decoratedMap.get(key); |
|
if (collection == null) { |
|
return null; |
|
} |
|
@SuppressWarnings("unchecked") |
|
final |
|
K k = (K) key; |
|
return wrappedCollection(k); |
|
} |
|
|
|
@Override |
|
public Set<K> keySet() { |
|
return AbstractMultiValuedMap.this.keySet(); |
|
} |
|
|
|
@Override |
|
public int size() { |
|
return decoratedMap.size(); |
|
} |
|
|
|
@Override |
|
public Collection<V> remove(final Object key) { |
|
final Collection<V> collection = decoratedMap.remove(key); |
|
if (collection == null) { |
|
return null; |
|
} |
|
|
|
final Collection<V> output = createCollection(); |
|
output.addAll(collection); |
|
collection.clear(); |
|
return output; |
|
} |
|
|
|
@Override |
|
public boolean equals(final Object object) { |
|
return this == object || decoratedMap.equals(object); |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return decoratedMap.hashCode(); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return decoratedMap.toString(); |
|
} |
|
|
|
@Override |
|
public void clear() { |
|
AbstractMultiValuedMap.this.clear(); |
|
} |
|
|
|
class AsMapEntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> { |
|
|
|
@Override |
|
public Iterator<Map.Entry<K, Collection<V>>> iterator() { |
|
return new AsMapEntrySetIterator(decoratedMap.entrySet().iterator()); |
|
} |
|
|
|
@Override |
|
public int size() { |
|
return AsMap.this.size(); |
|
} |
|
|
|
@Override |
|
public void clear() { |
|
AsMap.this.clear(); |
|
} |
|
|
|
@Override |
|
public boolean contains(final Object o) { |
|
return decoratedMap.entrySet().contains(o); |
|
} |
|
|
|
@Override |
|
public boolean remove(final Object o) { |
|
if (!contains(o)) { |
|
return false; |
|
} |
|
final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; |
|
AbstractMultiValuedMap.this.remove(entry.getKey()); |
|
return true; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
class AsMapEntrySetIterator extends AbstractIteratorDecorator<Map.Entry<K, Collection<V>>> { |
|
|
|
AsMapEntrySetIterator(final Iterator<Map.Entry<K, Collection<V>>> iterator) { |
|
super(iterator); |
|
} |
|
|
|
@Override |
|
public Map.Entry<K, Collection<V>> next() { |
|
final Map.Entry<K, Collection<V>> entry = super.next(); |
|
final K key = entry.getKey(); |
|
return new UnmodifiableMapEntry<>(key, wrappedCollection(key)); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
*/ |
|
protected void doWriteObject(final ObjectOutputStream out) throws IOException { |
|
out.writeInt(map.size()); |
|
for (final Map.Entry<K, Collection<V>> entry : map.entrySet()) { |
|
out.writeObject(entry.getKey()); |
|
out.writeInt(entry.getValue().size()); |
|
for (final V value : entry.getValue()) { |
|
out.writeObject(value); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected void doReadObject(final ObjectInputStream in) |
|
throws IOException, ClassNotFoundException { |
|
final int entrySize = in.readInt(); |
|
for (int i = 0; i < entrySize; i++) { |
|
@SuppressWarnings("unchecked") |
|
final K key = (K) in.readObject(); |
|
final Collection<V> values = get(key); |
|
final int valueSize = in.readInt(); |
|
for (int j = 0; j < valueSize; j++) { |
|
@SuppressWarnings("unchecked") |
|
final |
|
V value = (V) in.readObject(); |
|
values.add(value); |
|
} |
|
} |
|
} |
|
|
|
} |