/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cinchapi.common.util; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.concurrent.ThreadSafe; import org.cliffc.high_scale_lib.NonBlockingHashMap; import org.cliffc.high_scale_lib.NonBlockingHashSet; import com.google.common.collect.HashMultiset; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; /** * A thread-safe lock-free alternate implementation of a * {@link com.google.common.collect.HashMultimap HashMultimap}. * <p> * This structure differs from the {@link Multimap} interface in some ways. For * example, views returned from methods such as {@link #keys()}, * {@link #entries()}, etc do not "read through" to the underlying collection. * That means changes made to those views do not update the Multimap instance * and vice-versa. * </p> * * @author Jeff Nelson */ @ThreadSafe public class NonBlockingHashMultimap<K, V> implements Multimap<K, V> { /** * Return a new {@link NonBlockingHashMultimap}. * * @return the map */ public static <K, V> NonBlockingHashMultimap<K, V> create() { return new NonBlockingHashMultimap<K, V>( new NonBlockingHashMap<K, Set<V>>()); } /** * Construct a new instance. * * @param map */ private NonBlockingHashMultimap(NonBlockingHashMap<K, Set<V>> map) { this.map = map; } /** * A immutable empty set that does not throw exceptions if a caller attempts * a modification. */ private final NoOpEmptySet emptySet = new NoOpEmptySet(); /** * The backing store. */ private final NonBlockingHashMap<K, Set<V>> map; /** * An accumulation of the number of entries that are in the multimap at a * given time. */ private int totalSize; @Override public Map<K, Collection<V>> asMap() { Map<K, Collection<V>> theMap = Maps.newHashMap(); for (K key : keySet()) { theMap.put(key, get(key)); } return theMap; } @Override public void clear() { map.clear(); totalSize = 0; } @SuppressWarnings("unchecked") @Override public boolean containsEntry(Object key, Object value) { try { return get((K) key).contains(value); } catch (ClassCastException e) { return false; } } @Override public boolean containsKey(Object key) { return map.containsKey(key); } @Override public boolean containsValue(Object value) { for (Set<V> set : map.values()) { if(set.contains(value)) { return true; } } return false; } /** * {@inheritDoc} * <p> * <strong>NOTE:</strong> This method deviates from the original * {@link Multimap} interface in that changes to the returned collection * WILL NOT update the underlying multimap and vice-versa. */ @Override public Collection<Entry<K, V>> entries() { Set<Entry<K, V>> entries = new NonBlockingHashSet<Entry<K, V>>(); for (K key : keySet()) { for (V value : get(key)) { entries.add(Maps.immutableEntry(key, value)); } } return entries; } /** * {@inheritDoc} * <p> * <strong>NOTE:</strong> This method deviates from the original * {@link Multimap} interface in that changes to the returned collection * WILL NOT update the underlying multimap and vice-versa. */ @Override public Set<V> get(K key) { Set<V> values = map.get(key); return values != null ? values : emptySet; } @Override public boolean isEmpty() { return totalSize == 0; } /** * {@inheritDoc} * <p> * <strong>NOTE:</strong> This method deviates from the original * {@link Multimap} interface in that changes to the returned coll ection * WILL NOT update the underlying multimap and vice-versa. * </p> */ @Override public Multiset<K> keys() { Set<K> keys = keySet(); HashMultiset<K> multiset = HashMultiset.create(keys.size()); for (K key : keys) { multiset.add(key, get(key).size()); } return multiset; } @Override public Set<K> keySet() { return map.keySet(); } @Override public boolean put(K key, V value) { Set<V> values = map.get(key); if(values == null) { values = new NonBlockingHashSet<V>(); map.put(key, values); } if(values.add(value)) { ++totalSize; return true; } else { return false; } } @Override public boolean putAll(K key, Iterable<? extends V> values) { if(!Iterables.isEmpty(values)) { Set<V> set = get(key); if(set == emptySet) { set = new NonBlockingHashSet<V>(); map.put(key, set); } int currentSize = set.size(); Iterators.addAll(set, values.iterator()); if(set.size() - currentSize > 0) { totalSize += (set.size() - currentSize); return true; } else { return false; } } else { return false; } } @Override public boolean putAll(Multimap<? extends K, ? extends V> multimap) { boolean changed = false; for (Map.Entry<? extends K, ? extends V> entry : multimap.entries()) { changed |= put(entry.getKey(), entry.getValue()); } return changed; } @Override public boolean remove(Object key, Object value) { Set<V> values = map.get(key); if(values != null && values.remove(value)) { totalSize--; if(values.isEmpty()) { map.remove(key); } return true; } else { return false; } } @Override public Collection<V> removeAll(Object key) { Set<V> old = map.remove(key); if(old != null) { totalSize -= old.size(); return old; } else { return emptySet; } } @Override public Collection<V> replaceValues(K key, Iterable<? extends V> values) { Collection<V> result = removeAll(key); putAll(key, values); return result; } @Override public int size() { return totalSize; } /** * {@inheritDoc} * <p> * <strong>NOTE:</strong> This method deviates from the original * {@link Multimap} interface in that changes to the returned collection * WILL NOT update the underlying multimap and vice-versa. */ @Override public Collection<V> values() { List<V> values = Lists.newArrayList(); for (Entry<K, V> entry : entries()) { values.add(entry.getValue()); } return values; } @Override public int hashCode() { return map.hashCode(); } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if(obj instanceof NonBlockingHashMultimap) { return map.equals(((NonBlockingHashMultimap<K, V>) obj).map); } else { return false; } } @Override public String toString() { return map.toString(); } /** * An empty set that is non-operable. Attempts to modify this set won't * throw any exceptions and will just silently be ignored. */ private final class NoOpEmptySet implements Set<V> { @Override public boolean add(V e) { return false; } @Override public boolean addAll(Collection<? extends V> c) { return false; } @Override public void clear() {} @Override public boolean contains(Object o) { return false; } @Override public boolean containsAll(Collection<?> c) { return false; } @Override public boolean isEmpty() { return true; } @Override public Iterator<V> iterator() { return Collections.emptyIterator(); } @Override public boolean remove(Object o) { return false; } @Override public boolean removeAll(Collection<?> c) { return false; } @Override public boolean retainAll(Collection<?> c) { return false; } @Override public int size() { return 0; } @Override public Object[] toArray() { return new Object[] {}; } @Override public <T> T[] toArray(T[] a) { return a; } } }