/**
* Copyright 2013 the original author or authors.
*
* 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 io.neba.core.util;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
* A thread-safe storage for key -> value associations. Unlike
* {@link ConcurrentHashMap}, this map synchronizes writes since the multi-value
* associations require two independent read/writes to the map state; one to
* retrieve an existing collection, a second one to add another value to it. <br />
* Note that it is <em>not</em> save to modify the contents of the collection
* returned by this map since it is still based on the contents of this map and
* not a shallow copy for performance considerations.
* <br />
* In addition, the values stored in this map are distinct, i.e.
* if a value already exists in the values collection it is {@link Collection#remove(Object) removed}
* prior to the insertion of the new element.
*
* @param <K> The key's type.
* @param <V> The value's type.
* @author Olaf Otto
*/
public class ConcurrentDistinctMultiValueMap<K, V> {
private final Map<K, Collection<V>> store = new ConcurrentHashMap<>(128);
public Collection<V> get(K key) {
return this.store.get(key);
}
public synchronized void put(K key, V value) {
Collection<V> vs = getOrCreate(key);
vs.add(value);
}
public void clear() {
this.store.clear();
}
public Collection<V> remove(K key) {
return this.store.remove(key);
}
public Set<Entry<K, Collection<V>>> entrySet() {
return this.store.entrySet();
}
public Collection<Collection<V>> values() {
return this.store.values();
}
public synchronized void put(K key, Collection<V> values) {
Collection<V> vs = getOrCreate(key);
vs.addAll(values);
}
private Collection<V> getOrCreate(K key) {
Collection<V> vs = this.store.get(key);
if (vs == null) {
vs = new ConcurrentLinkedDistinctQueue<>();
this.store.put(key, vs);
}
return vs;
}
/**
* @return a shallow copy of the current state of this map. Note that the
* map values (the collection) are also copied; it is thus save to
* modify the state of the returned map and the state of the
* collections returned as the map values. Never returns null.
*/
public Map<K, Collection<V>> getContents() {
HashMap<K, Collection<V>> contents = new HashMap<>(this.store.size());
Set<Entry<K, Collection<V>>> entries = this.store.entrySet();
for (Entry<K, Collection<V>> entry : entries) {
contents.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return contents;
}
public int size() {
return this.store.size();
}
public boolean isEmpty() {
return this.store.isEmpty();
}
/**
* Removes the given value from all collections stored under any key.
*
* @param value must not be <code>null</code>.
*/
public void removeValue(V value) {
if (value == null) {
throw new IllegalArgumentException("Method argument value must not be null.");
}
for (Collection<V> values : values()) {
values.remove(value);
}
}
}