package org.corfudb.runtime.collections;
import com.google.common.reflect.TypeToken;
import lombok.Getter;
import org.corfudb.annotations.*;
import org.corfudb.runtime.object.AbstractCorfuWrapper;
import sun.misc.CRC16;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.zip.CRC32;
/**
* Created by mwei on 3/29/16.
*/
@CorfuObject(constructorType = ConstructorType.PERSISTED,
objectType = ObjectType.STATELESS)
public class FGMap<K, V> extends AbstractCorfuWrapper<FGMap<K,V>>
implements Map<K, V>
{
@Getter
public final int numBuckets;
public FGMap(int numBuckets) {
this.numBuckets = numBuckets;
}
public FGMap() {
this.numBuckets = 10;
}
@PassThrough
UUID getStreamID(int partition) {
return new UUID(getStreamID().getMostSignificantBits(),
getStreamID().getLeastSignificantBits() + (partition + 1));
}
@PassThrough
Map<K, V> getPartitionMap(int partition) {
return getBuilder()
.setTypeToken(new TypeToken<SMRMap<K,V>>() {})
.setStreamID(getStreamID(partition))
.open();
}
/**
* Get a new partition.
* <p>
* In order to avoid collisions due to imperfect hashCode() distribution,
* we apply the Luby-Rackoff transform to randomize the distribution with
* CRC32 and CRC16.
*
* @param key
* @return
*/
@PassThrough
int getPartitionNumber(Object key) {
int baseMSB = key.hashCode() >> 16;
int baseLSB = key.hashCode() & 0xFFFF;
CRC16 cMSB = new CRC16();
cMSB.update((byte) (baseMSB & 0xFF));
cMSB.update((byte) (baseMSB >> 8));
int hashCode1 = cMSB.value & 0xFFFF;
CRC32 cDbl = new CRC32();
cDbl.update(hashCode1 ^ baseLSB);
int hashCode2 = (int) cDbl.getValue();
int hashCode = ((hashCode2 ^ baseMSB) << 16) | (hashCode1 ^ baseLSB);
return Math.abs(hashCode % numBuckets);
}
@PassThrough
Map<K, V> getPartition(Object key) {
return getPartitionMap(getPartitionNumber(key));
}
@PassThrough
List<Map<K, V>> getAllPartitionMaps() {
return IntStream.range(0, numBuckets)
.mapToObj(this::getPartitionMap)
.collect(Collectors.toList());
}
@PassThrough
Set<UUID> getAllStreamIDs() {
return IntStream.range(0, numBuckets)
.mapToObj(this::getStreamID)
.collect(Collectors.toSet());
}
/**
* Returns the number of key-value mappings in this map. If the
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
@Override
@TransactionalMethod(readOnly = true)
public int size() {
return getAllPartitionMaps().stream()
.mapToInt(Map::size)
.sum();
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
@Override
@TransactionalMethod(readOnly = true)
public boolean isEmpty() {
return getAllPartitionMaps().stream()
.allMatch(Map::isEmpty);
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key. More formally, returns <tt>true</tt> if and only if
* this map contains a mapping for a key <tt>k</tt> such that
* <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
* at most one such mapping.)
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key
* @throws ClassCastException if the key is of an inappropriate type for
* this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
*/
@Override
@PassThrough
public boolean containsKey(Object key) {
return getPartition(key)
.containsKey(key);
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value. More formally, returns <tt>true</tt> if and only if
* this map contains at least one mapping to a value <tt>v</tt> such that
* <tt>(value==null ? v==null : value.equals(v))</tt>. This operation
* will probably require time linear in the map size for most
* implementations of the <tt>Map</tt> interface.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
* @throws ClassCastException if the value is of an inappropriate type for
* this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified value is null and this
* map does not permit null values
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
*/
@Override
@TransactionalMethod(readOnly = true)
public boolean containsValue(Object value) {
return getAllPartitionMaps().stream()
.anyMatch(x -> x.containsValue(value));
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
* <p>
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
* <p>
* <p>If this map permits null values, then a return value of
* {@code null} does not <i>necessarily</i> indicate that the map
* contains no mapping for the key; it's also possible that the map
* explicitly maps the key to {@code null}. The {@link #containsKey
* containsKey} operation may be used to distinguish these two cases.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws ClassCastException if the key is of an inappropriate type for
* this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
*/
@Override
@PassThrough
public V get(Object key) {
return getPartition(key).get(key);
}
/**
* Associates the specified value with the specified key in this map
* (optional operation). If the map previously contained a mapping for
* the key, the old value is replaced by the specified value. (A map
* <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
* if {@link #containsKey(Object) m.containsKey(k)} would return
* <tt>true</tt>.)
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>,
* if the implementation supports <tt>null</tt> values.)
* @throws UnsupportedOperationException if the <tt>put</tt> operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
* @throws NullPointerException if the specified key or value is null
* and this map does not permit null keys or values
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
*/
@Override
@PassThrough
public V put(K key, V value) {
return getPartition(key).put(key, value);
}
/**
* Removes the mapping for a key from this map if it is present
* (optional operation). More formally, if this map contains a mapping
* from key <tt>k</tt> to value <tt>v</tt> such that
* <code>(key==null ? k==null : key.equals(k))</code>, that mapping
* is removed. (The map can contain at most one such mapping.)
* <p>
* <p>Returns the value to which this map previously associated the key,
* or <tt>null</tt> if the map contained no mapping for the key.
* <p>
* <p>If this map permits null values, then a return value of
* <tt>null</tt> does not <i>necessarily</i> indicate that the map
* contained no mapping for the key; it's also possible that the map
* explicitly mapped the key to <tt>null</tt>.
* <p>
* <p>The map will not contain a mapping for the specified key once the
* call returns.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* @throws UnsupportedOperationException if the <tt>remove</tt> operation
* is not supported by this map
* @throws ClassCastException if the key is of an inappropriate type for
* this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key is null and this
* map does not permit null keys
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
*/
@Override
@PassThrough
public V remove(Object key) {
return getPartition(key).remove(key);
}
/**
* Copies all of the mappings from the specified map to this map
* (optional operation). The effect of this call is equivalent to that
* of calling {@link #put(Object, Object) put(k, v)} on this map once
* for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
* specified map. The behavior of this operation is undefined if the
* specified map is modified while the operation is in progress.
*
* @param m mappings to be stored in this map
* @throws UnsupportedOperationException if the <tt>putAll</tt> operation
* is not supported by this map
* @throws ClassCastException if the class of a key or value in the
* specified map prevents it from being stored in this map
* @throws NullPointerException if the specified map is null, or if
* this map does not permit null keys or values, and the
* specified map contains null keys or values
* @throws IllegalArgumentException if some property of a key or value in
* the specified map prevents it from being stored in this map
*/
@Override
@TransactionalMethod(modifiedStreamsFunction = "putAllGetStreams")
public void putAll(Map<? extends K, ? extends V> m) {
m.entrySet().stream()
.forEach(e -> getPartition(e.getKey()).put(e.getKey(), e.getValue()));
}
/**
* Get the set of streams which will be touched by this
* put all operation
*
* @param m The map used for the putAll operation
* @return A set of stream IDs
*/
Set<UUID> putAllGetStreams(Map<? extends K, ? extends V> m) {
return m.keySet().stream()
.map(this::getPartitionNumber)
.distinct()
.map(this::getStreamID)
.collect(Collectors.toSet());
}
/**
* Removes all of the mappings from this map (optional operation).
* The map will be empty after this call returns.
*
* @throws UnsupportedOperationException if the <tt>clear</tt> operation
* is not supported by this map
*/
@Override
@TransactionalMethod(modifiedStreamsFunction = "getAllStreamIDs")
public void clear() {
getAllPartitionMaps()
.forEach(Map::clear);
}
/**
* Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a set view of the keys contained in this map
*/
@Override
@TransactionalMethod(readOnly = true)
public Set<K> keySet() {
return getAllPartitionMaps().stream()
.map(Map::keySet)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
/**
* Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress
* (except through the iterator's own <tt>remove</tt> operation),
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
* support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a collection view of the values contained in this map
*/
@Override
@TransactionalMethod(readOnly = true)
public Collection<V> values() {
return getAllPartitionMaps().stream()
.map(Map::values)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
/**
* Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation, or through the
* <tt>setValue</tt> operation on a map entry returned by the
* iterator) the results of the iteration are undefined. The set
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and
* <tt>clear</tt> operations. It does not support the
* <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a set view of the mappings contained in this map
*/
@Override
@TransactionalMethod(readOnly = true)
public Set<Entry<K, V>> entrySet() {
return getAllPartitionMaps().stream()
.map(Map::entrySet)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
}