package edu.stanford.nlp.util; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.function.Predicate; /** * Implementation of CollectionValuedMap that appears to store an "original" * map and changes to that map. No one currently uses it. See {@link DeltaMap}. * * @author Teg Grenager (grenager@cs.stanford.edu) * @version Jan 14, 2004 */ public class DeltaCollectionValuedMap<K, V> extends CollectionValuedMap<K, V> { private static final long serialVersionUID = 1L; private final CollectionValuedMap<K, V> originalMap; @SuppressWarnings("serial") private final Map<K, Collection<V>> deltaMap; private static final Object removedValue = new Object(); static class SimpleEntry<K, V> implements Map.Entry<K, V> { K key; V value; public SimpleEntry(K key, V value) { this.key = key; this.value = value; } public SimpleEntry(Map.Entry<K, V> e) { this.key = e.getKey(); this.value = e.getValue(); } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry e = ErasureUtils.uncheckedCast(o); return eq(key, e.getKey()) && eq(value, e.getValue()); } @Override public int hashCode() { return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); } @Override public String toString() { return key + "=" + value; } private static boolean eq(Object o1, Object o2) { return (o1 == null ? o2 == null : o1.equals(o2)); } } @Override public Collection<V> get(Object key) { // key could be not in original or in deltaMap // key could be not in original but in deltaMap // key could be in original but removed from deltaMap // key could be in original but mapped to something else in deltaMap Collection<V> deltaResult = deltaMap.get(key); if (deltaResult == null) { return originalMap.get(key); } if (deltaResult == removedValue) { return cf.newEmptyCollection(); } return deltaResult; } // Modification Operations @Override public Collection<V> put(K key, Collection<V> value) { throw new UnsupportedOperationException(); } @Override public void putAll(Map<? extends K, ? extends Collection<V>> m) { throw new UnsupportedOperationException(); } @Override public void add(K key, V value) { Collection<V> deltaC = deltaMap.get(key); if (deltaC == null) { deltaC = cf.newCollection(); Collection<V> originalC = originalMap.get(key); if (originalC != null) { deltaC.addAll(originalC); } deltaMap.put(key, deltaC); } deltaC.add(value); } /** * Adds all of the mappings in m to this CollectionValuedMap. * If m is a CollectionValuedMap, it will behave strangely. Use the constructor instead. * */ @Override public void addAll(Map<K, V> m) { for (Map.Entry<K, V> e : m.entrySet()) { add(e.getKey(), e.getValue()); } } @Override public Collection<V> remove(Object key) { Collection<V> result = get(key); deltaMap.put(ErasureUtils.uncheckedCast(key), ErasureUtils.uncheckedCast(removedValue)); return result; } @Override public void removeMapping(K key, V value) { Collection<V> deltaC = deltaMap.get(key); if (deltaC == null) { Collection<V> originalC = originalMap.get(key); if (originalC != null && originalC.contains(value)) { deltaC = cf.newCollection(); deltaC.addAll(originalC); deltaMap.put(key, deltaC); } } if (deltaC != null) { deltaC.remove(value); } } @SuppressWarnings("SuspiciousMethodCalls") @Override public boolean containsKey(Object key) { // key could be not in original or in deltaMap // key could be not in original but in deltaMap // key could be in original but removed from deltaMap // key could be in original but mapped to something else in deltaMap Object value = deltaMap.get(key); if (value == null) { return originalMap.containsKey(key); } return value != removedValue; } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } // Bulk Operations /** * This is more expensive than normal. */ @Override public void clear() { // iterate over all keys in originalMap and set them to null in deltaMap for (K key : originalMap.keySet()) { deltaMap.put(key, ErasureUtils.uncheckedCast(removedValue)); } } @Override public boolean isEmpty() { return size() == 0; } @Override public int size() { return entrySet().size(); } @Override public Collection<Collection<V>> values() { throw new UnsupportedOperationException(); } // Views /** * This is cheap. * * @return A set view of the mappings contained in this map. */ @Override public Set<Entry<K, Collection<V>>> entrySet() { return new AbstractSet<Entry<K, Collection<V>>>() { @Override public Iterator<Map.Entry<K, Collection<V>>> iterator() { Predicate<Entry<K, Collection<V>>> filter1 = e -> ! deltaMap.containsKey(e.getKey()); Iterator<Map.Entry<K, Collection<V>>> iter1 = new FilteredIterator<>(originalMap.entrySet().iterator(), filter1); Predicate<Entry<K, Collection<V>>> filter2 = e -> e.getValue() != removedValue; Iterator<Map.Entry<K, Collection<V>>> iter2 = new FilteredIterator<>(deltaMap.entrySet().iterator(), filter2); return new ConcatenationIterator<>(iter1, iter2); } @Override public int size() { int size = 0; for (@SuppressWarnings("unused") Entry<K, Collection<V>> ignored : this) { size++; } return size; } }; } public DeltaCollectionValuedMap(CollectionValuedMap<K, V> originalMap) { super(originalMap.mf, originalMap.cf, originalMap.treatCollectionsAsImmutable); this.originalMap = originalMap; this.deltaMap = mf.newMap(); } }