/*
* The MIT License
*
* Copyright 2015 Ahseya.
*
* 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.github.horrorho.liquiddonkey.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.jcip.annotations.ThreadSafe;
/**
* BiMapSet.
* <p>
* Lightweight bi-map of sets with common references. Remove only. No put methods. Null values not permitted. Thread
* safe.
*
* @author Ahseya
* @param <K> key type
* @param <V> value type
*/
@ThreadSafe
public class BiMapSet<K, V> {
public static <K, V> BiMapSet<K, V> from(Map<K, ? extends Collection<V>> map) {
Objects.requireNonNull(map, "Map cannot be null");
ConcurrentMap<K, Set<V>> kToVSet = new ConcurrentHashMap<>();
ConcurrentMap<V, Set<K>> vToKSet = new ConcurrentHashMap<>();
map.entrySet().stream()
.peek(entry -> {
if (entry == null || entry.getKey() == null || entry.getValue() == null || entry.getValue().contains(null)) {
throw new NullPointerException("Null values not permitted");
}
})
.filter(entry -> !entry.getValue().isEmpty())
.forEach(entry -> {
K key = entry.getKey();
Set<V> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
entry.getValue().stream().forEach(value -> {
set.add(value);
vToKSet.computeIfAbsent(value, k
-> Collections.newSetFromMap(new ConcurrentHashMap<>())).add(key);
});
kToVSet.put(key, set);
});
return new BiMapSet(kToVSet, vToKSet);
}
final ConcurrentMap<K, Set<V>> kToVSet; // Requires concurrent Set
final ConcurrentMap<V, Set<K>> vToKSet; // Requires concurrent Set
BiMapSet(ConcurrentMap kToVSet, ConcurrentMap vToKSet) {
this.kToVSet = kToVSet;
this.vToKSet = vToKSet;
}
public Set<K> keySet() {
return kToVSet.keySet();
}
public Set<V> valueSet() {
return vToKSet.keySet();
}
public Set<K> keys(V value) {
return set(value, vToKSet);
}
public Set<V> values(K key) {
return set(key, kToVSet);
}
public Set<V> removeKey(K key) {
return remove(key, vToKSet, kToVSet);
}
public Set<K> removeValue(V value) {
return remove(value, kToVSet, vToKSet);
}
public boolean isEmpty() {
return vToKSet.isEmpty() && kToVSet.isEmpty();
}
<T, U> Set<U> set(T t, Map<T, Set<U>> tToUSet) {
Set<U> set = t == null
? null
: tToUSet.get(t);
return set == null
? new HashSet<>()
: new HashSet<>(set);
}
<T, U> Set<U> remove(T t, Map<U, Set<T>> uToTSet, Map<T, Set<U>> tToUSet) {
Set<U> removed = new HashSet<>();
Set<U> uSet = t == null
? null
: tToUSet.get(t);
if (uSet == null) {
return removed;
}
uSet.forEach(u -> {
Set<T> set = uToTSet.get(u);
if (set != null) {
set.remove(t);
if (set.isEmpty()) {
removed.add(u);
uToTSet.remove(u);
}
}
});
tToUSet.remove(t);
return removed;
}
}