/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.marketdata;
import java.util.Collections;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.opengamma.util.ArgumentChecker;
/**
* A cut down multimap that also provides an inverted version of itself. If
* there are many to many mappings from types K -> V and V -> K, this provides
* methods to lookup all Vs associated with a K. By using the {@link #inverse()}
* method, it is then possible to lookup all Ks associated with a V.
* <p>
* Modifications made to this instance will be automatically reflected in its
* inverse. Similarly, modifications made to the inverse will be reflected in
* this instance.
* <p>
* This code is not thread-safe, so any synchronization required must be done
* externally.
*
* @param <K> the type of the keys for the map
* @param <V> the type of the values for the map, recommended to be
* a different type from K to avoid confusion when calling methods
*/
public class BidirectionalMultiMap<K, V> {
/**
* The multimap holding mappings from K -> V.
*/
private final SetMultimap<K, V> _primaryMap;
/**
* The multimap holding mappings from V -> K.
*/
private final SetMultimap<V, K> _secondaryMap;
/**
* The inverted BidirectionalMultiMap which is a view over the same
* underlying data.
*/
private final BidirectionalMultiMap<V, K> _inverse;
/**
* Create the multimap.
*/
public BidirectionalMultiMap() {
this(HashMultimap.<K, V>create(), HashMultimap.<V, K>create());
}
/**
* Private constructor taking the underlying multimaps. No arg checking as
* only called from this class. The inverse will be generated from the
* passed multimaps.
*
* @param primaryMap the multimap holding mappings from K -> V
* @param secondaryMap the multimap holding mappings from V -> K
*/
private BidirectionalMultiMap(SetMultimap<K, V> primaryMap, SetMultimap<V, K> secondaryMap) {
_primaryMap = primaryMap;
_secondaryMap = secondaryMap;
// Ensure that this instance is used as the inverse of the inverse
_inverse = new BidirectionalMultiMap<>(_secondaryMap, _primaryMap, this);
}
/**
* Private constructor taking the underlying multimaps and the inverse.
* No arg checking as only called from this class.
*
* @param primaryMap the multimap holding mappings from K -> V
* @param secondaryMap the multimap holding mappings from V -> K
* @param inverse the inverse of this BidirectionalMultiMap
*/
private BidirectionalMultiMap(SetMultimap<K, V> primaryMap, SetMultimap<V, K> secondaryMap,
BidirectionalMultiMap<V, K> inverse) {
_primaryMap = primaryMap;
_secondaryMap = secondaryMap;
_inverse = inverse;
}
/**
* Return the inverse of this BidirectionalMultiMap.
*
* @return the inverse, not null
*/
public BidirectionalMultiMap<V, K> inverse() {
return _inverse;
}
/**
* Add a mapping from key to value into this multimap. If the mapping already
* exists then this method will have no effect.
*
* @param key they key the value is added against, not null
* @param value the value to add for the key, not null
*/
public void put(K key, V value) {
_primaryMap.put(ArgumentChecker.notNull(key, "key"), ArgumentChecker.notNull(value, "value"));
_secondaryMap.put(value, key);
}
/**
* Returns the collection of values associated with the specified
* key in this multimap, if any. An empty collection will be
* returned if there are no mappings. The returned collection does
* not permit modifications.
*
* @param key the key find mappings for
* @return the collection of values associated with the key, not null
*/
public Set<V> get(K key) {
return Collections.unmodifiableSet(_primaryMap.get(key));
}
/**
* Returns true if this multimap contains at least one key-value
* pair with the specified key.
*
* @param key the key to check for
* @return true if this multimap contains at least one key-value
* pair with the specified key
*/
public boolean containsKey(K key) {
return _primaryMap.containsKey(key);
}
/**
* Returns all distinct keys contained in this multimap. The
* returned set does not permit modifications.
*
* @return the set of distinct keys, not null
*/
public Set<K> keySet() {
return Collections.unmodifiableSet(_primaryMap.keySet());
}
/**
* Remove an entry from this multimap.
*
* @param key they key the value is removed from, not null
* @param value the value to remove for the key, not null
*/
public void remove(K key, V value) {
_primaryMap.remove(ArgumentChecker.notNull(key, "key"), ArgumentChecker.notNull(value, "value"));
_secondaryMap.remove(value, key);
}
/**
* Return the number of mappings in this multimap.
*
* @return the number of mappings
*/
public int size() {
return _primaryMap.size();
}
/**
* Remove all the mappings for a key, returning the set
* of values removed. At the same time, all values
* referencing the key will have the key removed. The
* returned set does not permit modifications.
*
* @param key the key to remove
* @return the set of values removed
*/
public Set<V> removeAll(K key) {
Set<V> removed = _primaryMap.removeAll(key);
for (V value : removed) {
_inverse.remove(value, key);
}
return Collections.unmodifiableSet(removed);
}
}