com.jcwhatever.nucleus.collections.wrap.MultimapWrapper.java Source code

Java tutorial

Introduction

Here is the source code for com.jcwhatever.nucleus.collections.wrap.MultimapWrapper.java

Source

/*
 * 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();
        }
    }
}