/* * 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.wrap; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.jcwhatever.nucleus.collections.ElementCounter; import com.jcwhatever.nucleus.collections.ElementCounter.ElementCount; import com.jcwhatever.nucleus.collections.ElementCounter.RemovalPolicy; import com.jcwhatever.nucleus.utils.PreCon; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import javax.annotation.Nonnull; /** * An abstract implementation of a synchronized {@link Multimap} wrapper. The wrapper is * optionally synchronized via a sync object or {@link ReadWriteLock} passed into the * constructor using a {@link SyncStrategy}. * * <p>If the map is synchronized, the sync object must be externally locked while * any associated iterator is in use. Otherwise, a {@link java.lang.IllegalStateException} will * be thrown.</p> * * <p>The actual map is provided to the abstract implementation by * overriding and returning it from the {@link #map} method.</p> * * <p>In order to make using the wrapper as an extension of a map easier, * several protected methods are provided for optional override. See {@link #onPut}, * {@link onPutAll}, {@link #onRemove}, {@link @onRemoveAll}, {@link #onClear}</p> */ public abstract class MultimapWrapper<K, V> implements Multimap<K, V> { private final KeySetWrapper _keySet; private final EntrySetWrapper _entrySet; private final ValuesWrapper _values; private final AsMapWrapper _asMap; protected final Object _sync; protected final ReadWriteLock _lock; protected final SyncStrategy _strategy; /** * Constructor. * * <p>No synchronization.</p> */ public MultimapWrapper() { this(SyncStrategy.NONE); } /** * Constructor. * * @param strategy The synchronization strategy to use. */ public MultimapWrapper(SyncStrategy strategy) { PreCon.notNull(strategy); _sync = strategy.getSync(this); _strategy = new SyncStrategy(_sync); _lock = _sync instanceof ReadWriteLock ? (ReadWriteLock)_sync : null; _keySet = new KeySetWrapper(); _entrySet = new EntrySetWrapper(); _values = new ValuesWrapper(); _asMap = new AsMapWrapper(); } /** * Invoked after put an entry into the map. * * <p>Not guaranteed to be called from a synchronized block.</p> * * <p>Intended to be optionally overridden by implementation.</p> * * @param key The entry key. * @param value The entry value. */ protected abstract void onPut(K key, V value); /** * Invoked after putting multiple values into the map. It is not * guaranteed that this is invoked for all batch "put" operations. In some * cases, {@link #onPut} is invoked for each entry added instead. * * <p>Not guaranteed to be called from a synchronized block.</p> * * <p>Intended to be optionally overridden by implementation.</p> * * @param key The key. * @param values The values added. */ protected abstract void onPutAll(K key, Iterable<? extends V> values); /** * Invoked after removing a value from an entry except when * the map is cleared. * * <p>Not guaranteed to be called from a synchronized block.</p> * * <p>Intended to be optionally overridden by implementation.</p> * * @param key The key. * @param value The value removed from the entry. */ protected abstract void onRemove(Object key, Object value); /** * Invoked after removing an entry from the map except when the * map is cleared. It is not guaranteed that this will be called * for all batch remove operations. In some cases, the {@link #onRemove} * method may be invoked for each value removed. * * <p>Not guaranteed to be called from a synchronized block.</p> * * <p>Intended to be optionally overridden by implementation.</p> * * @param key The key. * @param values The values associated with the removed entry. */ protected abstract void onRemoveAll(Object key, Collection<V> values); /** * Invoked after the map is cleared. * * <p>Not guaranteed to be called from a synchronized block.</p> * * <p>Intended to be optionally overridden by implementation.</p> * * @param entries The entries that were cleared from the map. */ protected abstract void onClear(Collection<Entry<K, V>> entries); /** * Invoked from a synchronized block to get * the encapsulated {@link Multimap}. */ protected abstract Multimap<K, V> map(); @Override public int size() { if (_lock != null) { _lock.readLock().lock(); try { return map().size(); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return map().size(); } } else { return map().size(); } } @Override public boolean isEmpty() { if (_lock != null) { _lock.readLock().lock(); try { return map().isEmpty(); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return map().isEmpty(); } } else { return map().isEmpty(); } } @Override public boolean containsKey(Object o) { if (_lock != null) { _lock.readLock().lock(); try { return map().containsKey(o); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return map().containsKey(o); } } else { return map().containsKey(o); } } @Override public boolean containsValue(Object o) { if (_lock != null) { _lock.readLock().lock(); try { return map().containsValue(o); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return map().containsValue(o); } } else { return map().containsValue(o); } } @Override public boolean containsEntry(Object o, Object o1) { if (_lock != null) { _lock.readLock().lock(); try { return map().containsEntry(o, o1); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return map().containsEntry(o, o1); } } else { return map().containsEntry(o, o1); } } @Override public boolean put(@Nonnull K k, V v) { PreCon.notNull(k); if (_lock != null) { _lock.writeLock().lock(); try { return putSource(k, v); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return putSource(k, v); } } else { return putSource(k, v); } } private boolean putSource(K k, V v) { if (map().put(k, v)) { onPut(k, v); return true; } return false; } @Override public boolean remove(@Nonnull Object key, @Nonnull Object value) { PreCon.notNull(key); PreCon.notNull(value); if (_lock != null) { _lock.writeLock().lock(); try { return removeSource(key, value); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return removeSource(key, value); } } else { return removeSource(key, value); } } private boolean removeSource(Object o, Object o1) { if (map().remove(o, o1)) { onRemove(o, o1); return true; } return false; } @Override public boolean putAll(@Nonnull K k, @Nonnull Iterable<? extends V> iterable) { PreCon.notNull(k); PreCon.notNull(iterable); if (_lock != null) { _lock.writeLock().lock(); try { return putAllSource(k, iterable); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return putAllSource(k, iterable); } } else { return putAllSource(k, iterable); } } private boolean putAllSource(K k, Iterable<? extends V> iterable) { if (map().putAll(k, iterable)) { onPutAll(k, iterable); return true; } return false; } @Override public boolean putAll(@Nonnull 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> replaceValues(@Nonnull K k, @Nonnull Iterable<? extends V> iterable) { PreCon.notNull(k); PreCon.notNull(iterable); if (_lock != null) { _lock.writeLock().lock(); try { return replaceValuesSource(k, iterable); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return replaceValuesSource(k, iterable); } } else { return replaceValuesSource(k, iterable); } } private Collection<V> replaceValuesSource(K k, Iterable<? extends V> iterable) { Collection<V> removed = map().removeAll(k); ElementCounter<V> counter; counter = new ElementCounter<>(RemovalPolicy.KEEP_COUNTING, removed); map().putAll(k, iterable); counter.subtractAll(map().get(k)); for (ElementCount<V> elmCount : counter) { V agent = elmCount.getElement(); for (int i=0; i < Math.abs(elmCount.getCount()); i++) { if (elmCount.getCount() > 0) { onRemove(k, agent); } else { onPut(k, agent); } } } return removed; } @Override public Collection<V> removeAll(Object o) { Collection<V> removed; if (_lock != null) { _lock.writeLock().lock(); try { removed = map().removeAll(o); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { removed = map().removeAll(o); } } else { removed = map().removeAll(o); } onRemoveAll(o, removed); return removed; } @Override public void clear() { Collection<Entry<K, V>> entries; if (_lock != null) { _lock.writeLock().lock(); try { entries = new HashSet<>(map().entries()); map().clear(); } finally { _lock.writeLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { entries = new HashSet<>(map().entries()); map().clear(); } } else { entries = new HashSet<>(map().entries()); map().clear(); } onClear(entries); } @Override public Collection<V> get(K k) { if (_lock != null) { _lock.readLock().lock(); try { return new GetWrapper(k, map().get(k)); } finally { _lock.readLock().unlock(); } } else if (_sync != null) { synchronized (_sync) { return new GetWrapper(k, map().get(k)); } } else { return new GetWrapper(k, map().get(k)); } } @Override public Set<K> keySet() { return _keySet; } @Override public Multiset<K> keys() { return map().keys(); // TODO: Wrap } @Override public Collection<V> values() { return _values; } @Override public Collection<Entry<K, V>> entries() { return _entrySet; } @Override public Map<K, Collection<V>> asMap() { return _asMap; } private class GetWrapper extends CollectionWrapper<V> { final K key; final Collection<V> collection; GetWrapper(K key, Collection<V> collection) { super(MultimapWrapper.this._strategy); this.key = key; this.collection = collection; } @Override protected void onAdded(V v) { onPut(key, v); } @Override protected void onRemoved(Object o) { onRemove(key, o); } @Override protected Collection<V> collection() { return collection; } } private class ValuesWrapper extends CollectionWrapper<V> { ValuesWrapper() { super(MultimapWrapper.this._strategy); } @Override protected void onAdded(V v) { throw new UnsupportedOperationException(); } @Override protected void onRemoved(Object o) { throw new UnsupportedOperationException(); } @Override protected Collection<V> collection() { return map().values(); } } private class KeySetWrapper extends SetWrapper<K> { KeySetWrapper() { super(MultimapWrapper.this._strategy); } Collection<V> removed; @Override protected Set<K> set() { return map().keySet(); } @Override protected void onAdded(K k) { throw new UnsupportedOperationException(); } @Override protected boolean onPreRemove(Object o) { @SuppressWarnings("unchecked") K k = (K)o; removed = map().get(k); return true; } @Override protected void onRemoved(Object o) { onRemoveAll(o, removed); removed = null; } } private class AsMapWrapper extends MapWrapper<K, Collection<V>> { AsMapWrapper() { super(MultimapWrapper.this._strategy); } @Override protected void onPut(K key, Collection<V> value) { MultimapWrapper.this.onPutAll(key, value); } @Override protected void onRemove(Object key, Collection<V> removed) { MultimapWrapper.this.onRemoveAll(key, removed); } @Override protected void onClear(Collection<Entry<K, Collection<V>>> entries) { for (Entry<K, Collection<V>> entry : entries) { MultimapWrapper.this.onRemoveAll(entry.getKey(), entry.getValue()); } } @Override protected Map<K, Collection<V>> map() { return MultimapWrapper.this.map().asMap(); } } private class EntrySetWrapper extends CollectionWrapper<Entry<K, V>> { EntrySetWrapper() { super(MultimapWrapper.this._strategy); } @Override protected boolean onPreAdd(Entry<K, V> kvEntry) { return true; } @Override protected void onAdded(Entry<K, V> kvEntry) { MultimapWrapper.this.onPut(kvEntry.getKey(), kvEntry.getValue()); } @Override protected boolean onPreRemove(Object o) { return o instanceof Entry; } @Override protected void onRemoved(Object o) { if (o instanceof Entry) { @SuppressWarnings("unchecked") Entry<K, V> entry = (Entry<K, V>)o; MultimapWrapper.this.onRemove(entry.getKey(), entry.getValue()); } } @Override protected void onClear(Collection<Entry<K, V>> values) { MultimapWrapper.this.onClear(values); } @Override protected Collection<Entry<K, V>> collection() { return map().entries(); } } }