org.codelibs.elasticsearch.common.collect.CopyOnWriteHashMap Maven / Gradle / Ivy
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.codelibs.elasticsearch.common.collect;
import org.apache.lucene.util.mutable.MutableValueInt;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Stream;
/**
* An immutable map whose writes result in a new copy of the map to be created.
*
* This is essentially a hash array mapped trie: inner nodes use a bitmap in
* order to map hashes to slots by counting ones. In case of a collision (two
* values having the same 32-bits hash), a leaf node is created which stores
* and searches for values sequentially.
*
* Reads and writes both perform in logarithmic time. Null keys and values are
* not supported.
*
* This structure might need to perform several object creations per write so
* it is better suited for work-loads that are not too write-intensive.
*
* @see the wikipedia page
*/
public final class CopyOnWriteHashMap extends AbstractMap {
private static final int TOTAL_HASH_BITS = 32;
private static final Object[] EMPTY_ARRAY = new Object[0];
private static final int HASH_BITS = 6;
private static final int HASH_MASK = 0x3F;
/**
* Return a copy of the provided map.
*/
public static CopyOnWriteHashMap copyOf(Map extends K, ? extends V> map) {
if (map instanceof CopyOnWriteHashMap) {
// no need to copy in that case
@SuppressWarnings("unchecked")
final CopyOnWriteHashMap cowMap = (CopyOnWriteHashMap) map;
return cowMap;
} else {
return new CopyOnWriteHashMap().copyAndPutAll(map);
}
}
/**
* Abstraction of a node, implemented by both inner and leaf nodes.
*/
private abstract static class Node {
/**
* Recursively get the key with the given hash.
*/
abstract V get(Object key, int hash);
/**
* Recursively add a new entry to this node. hashBits
is
* the number of bits that are still set in the hash. When this value
* reaches a number that is less than or equal to 0, a leaf
* node needs to be created since it means that a collision occurred
* on the 32 bits of the hash.
*/
abstract Node put(K key, int hash, int hashBits, V value, MutableValueInt newValue);
/**
* Recursively remove an entry from this node.
*/
abstract Node remove(Object key, int hash);
/**
* For the current node only, append entries that are stored on this
* node to entries
and sub nodes to nodes
.
*/
abstract void visit(Deque> entries, Deque> nodes);
/**
* Whether this node stores nothing under it.
*/
abstract boolean isEmpty();
}
/**
* A leaf of the tree where all hashes are equal. Values are added and retrieved in linear time.
*/
private static class Leaf extends Node {
private final K[] keys;
private final V[] values;
Leaf(K[] keys, V[] values) {
this.keys = keys;
this.values = values;
}
@SuppressWarnings("unchecked")
Leaf() {
this((K[]) EMPTY_ARRAY, (V[]) EMPTY_ARRAY);
}
@Override
boolean isEmpty() {
return keys.length == 0;
}
@Override
void visit(Deque> entries, Deque> nodes) {
for (int i = 0; i < keys.length; ++i) {
entries.add(new AbstractMap.SimpleImmutableEntry<>(keys[i], values[i]));
}
}
@Override
V get(Object key, int hash) {
for (int i = 0; i < keys.length; i++) {
if (key.equals(keys[i])) {
return values[i];
}
}
return null;
}
private static T[] replace(T[] array, int index, T value) {
final T[] copy = Arrays.copyOf(array, array.length);
copy[index] = value;
return copy;
}
@Override
Leaf put(K key, int hash, int hashBits, V value, MutableValueInt newValue) {
assert hashBits <= 0 : hashBits;
int slot = -1;
for (int i = 0; i < keys.length; i++) {
if (key.equals(keys[i])) {
slot = i;
break;
}
}
final K[] keys2;
final V[] values2;
if (slot < 0) {
keys2 = appendElement(keys, key);
values2 = appendElement(values, value);
newValue.value = 1;
} else {
keys2 = replace(keys, slot, key);
values2 = replace(values, slot, value);
}
return new Leaf<>(keys2, values2);
}
@Override
Leaf remove(Object key, int hash) {
int slot = -1;
for (int i = 0; i < keys.length; i++) {
if (key.equals(keys[i])) {
slot = i;
break;
}
}
if (slot < 0) {
return this;
}
final K[] keys2 = removeArrayElement(keys, slot);
final V[] values2 = removeArrayElement(values, slot);
return new Leaf<>(keys2, values2);
}
}
private static T[] removeArrayElement(T[] array, int index) {
final Object result = Array.newInstance(array.getClass().getComponentType(), array.length - 1);
System.arraycopy(array, 0, result, 0, index);
if (index < array.length - 1) {
System.arraycopy(array, index + 1, result, index, array.length - index - 1);
}
return (T[]) result;
}
public static T[] appendElement(final T[] array, final T element) {
final T[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[newArray.length - 1] = element;
return newArray;
}
public static T[] insertElement(final T[] array, final T element, final int index) {
final T[] result = Arrays.copyOf(array, array.length + 1);
System.arraycopy(array, 0, result, 0, index);
result[index] = element;
if (index < array.length) {
System.arraycopy(array, index, result, index + 1, array.length - index);
}
return result;
}
/**
* An inner node in this trie. Inner nodes store up to 64 key-value pairs
* and use a bitmap in order to associate hashes to them. For example, if
* an inner node contains 5 values, then 5 bits will be set in the bitmap
* and the ordinal of the bit set in this bit map will be the slot number.
*
* As a consequence, the number of slots in an inner node is equal to the
* number of one bits in the bitmap.
*/
private static class InnerNode extends Node {
private final long mask; // the bitmap
private final K[] keys;
final Object[] subNodes; // subNodes[slot] is either a value or a sub node in case of a hash collision
InnerNode(long mask, K[] keys, Object[] subNodes) {
this.mask = mask;
this.keys = keys;
this.subNodes = subNodes;
assert consistent();
}
// only used in assert
private boolean consistent() {
assert Long.bitCount(mask) == keys.length;
assert Long.bitCount(mask) == subNodes.length;
for (int i = 0; i < keys.length; ++i) {
if (subNodes[i] instanceof Node) {
assert keys[i] == null;
} else {
assert keys[i] != null;
}
}
return true;
}
@Override
boolean isEmpty() {
return mask == 0;
}
@SuppressWarnings("unchecked")
InnerNode() {
this(0, (K[]) EMPTY_ARRAY, EMPTY_ARRAY);
}
@Override
void visit(Deque> entries, Deque> nodes) {
for (int i = 0; i < keys.length; ++i) {
final Object sub = subNodes[i];
if (sub instanceof Node) {
@SuppressWarnings("unchecked")
final Node subNode = (Node) sub;
assert keys[i] == null;
nodes.add(subNode);
} else {
@SuppressWarnings("unchecked")
final V value = (V) sub;
entries.add(new AbstractMap.SimpleImmutableEntry<>(keys[i], value));
}
}
}
/**
* For a given hash on 6 bits, its value is set if the bitmap has a one
* at the corresponding index.
*/
private boolean exists(int hash6) {
return (mask & (1L << hash6)) != 0;
}
/**
* For a given hash on 6 bits, the slot number is the number of one
* bits on the right of the hash6
-th bit.
*/
private int slot(int hash6) {
return Long.bitCount(mask & ((1L << hash6) - 1));
}
@Override
V get(Object key, int hash) {
final int hash6 = hash & HASH_MASK;
if (!exists(hash6)) {
return null;
}
final int slot = slot(hash6);
final Object sub = subNodes[slot];
assert sub != null;
if (sub instanceof Node) {
assert keys[slot] == null; // keys don't make sense on inner nodes
@SuppressWarnings("unchecked")
final Node subNode = (Node) sub;
return subNode.get(key, hash >>> HASH_BITS);
} else {
if (keys[slot].equals(key)) {
@SuppressWarnings("unchecked")
final V v = (V) sub;
return v;
} else {
// we have an entry for this hash, but the value is different
return null;
}
}
}
private Node newSubNode(int hashBits) {
if (hashBits <= 0) {
return new Leaf<>();
} else {
return new InnerNode<>();
}
}
private InnerNode putExisting(K key, int hash, int hashBits, int slot, V value, MutableValueInt newValue) {
final K[] keys2 = Arrays.copyOf(keys, keys.length);
final Object[] subNodes2 = Arrays.copyOf(subNodes, subNodes.length);
final Object previousValue = subNodes2[slot];
if (previousValue instanceof Node) {
// insert recursively
assert keys[slot] == null;
subNodes2[slot] = ((Node) previousValue).put(key, hash, hashBits, value, newValue);
} else if (keys[slot].equals(key)) {
// replace the existing entry
subNodes2[slot] = value;
} else {
// hash collision
final K previousKey = keys[slot];
final int previousHash = previousKey.hashCode() >>> (TOTAL_HASH_BITS - hashBits);
Node subNode = newSubNode(hashBits);
subNode = subNode.put(previousKey, previousHash, hashBits, (V) previousValue, newValue);
subNode = subNode.put(key, hash, hashBits, value, newValue);
keys2[slot] = null;
subNodes2[slot] = subNode;
}
return new InnerNode<>(mask, keys2, subNodes2);
}
private InnerNode putNew(K key, int hash6, int slot, V value) {
final long mask2 = mask | (1L << hash6);
final K[] keys2 = insertElement(keys, key, slot);
final Object[] subNodes2 = insertElement(subNodes, value, slot);
return new InnerNode<>(mask2, keys2, subNodes2);
}
@Override
InnerNode put(K key, int hash, int hashBits, V value, MutableValueInt newValue) {
final int hash6 = hash & HASH_MASK;
final int slot = slot(hash6);
if (exists(hash6)) {
hash >>>= HASH_BITS;
hashBits -= HASH_BITS;
return putExisting(key, hash, hashBits, slot, value, newValue);
} else {
newValue.value = 1;
return putNew(key, hash6, slot, value);
}
}
private InnerNode removeSlot(int hash6, int slot) {
final long mask2 = mask & ~(1L << hash6);
final K[] keys2 = removeArrayElement(keys, slot);
final Object[] subNodes2 = removeArrayElement(subNodes, slot);
return new InnerNode<>(mask2, keys2, subNodes2);
}
@Override
InnerNode remove(Object key, int hash) {
final int hash6 = hash & HASH_MASK;
if (!exists(hash6)) {
return this;
}
final int slot = slot(hash6);
final Object previousValue = subNodes[slot];
if (previousValue instanceof Node) {
@SuppressWarnings("unchecked")
final Node subNode = (Node) previousValue;
final Node removed = subNode.remove(key, hash >>> HASH_BITS);
if (removed == subNode) {
// not in sub-nodes
return this;
}
if (removed.isEmpty()) {
return removeSlot(hash6, slot);
}
final K[] keys2 = Arrays.copyOf(keys, keys.length);
final Object[] subNodes2 = Arrays.copyOf(subNodes, subNodes.length);
subNodes2[slot] = removed;
return new InnerNode<>(mask, keys2, subNodes2);
} else if (keys[slot].equals(key)) {
// remove entry
return removeSlot(hash6, slot);
} else {
// hash collision, nothing to remove
return this;
}
}
}
private static class EntryIterator implements Iterator> {
private final Deque> entries;
private final Deque> nodes;
public EntryIterator(Node node) {
entries = new ArrayDeque<>();
nodes = new ArrayDeque<>();
node.visit(entries, nodes);
}
@Override
public boolean hasNext() {
return !entries.isEmpty() || !nodes.isEmpty();
}
@Override
public Map.Entry next() {
while (entries.isEmpty()) {
if (nodes.isEmpty()) {
throw new NoSuchElementException();
}
final Node nextNode = nodes.pop();
nextNode.visit(entries, nodes);
}
return entries.pop();
}
@Override
public final void remove() {
throw new UnsupportedOperationException("querybuilders does not support this operation.");
}
}
private final InnerNode root;
private final int size;
/**
* Create a new empty map.
*/
public CopyOnWriteHashMap() {
this(new InnerNode(), 0);
}
private CopyOnWriteHashMap(InnerNode root, int size) {
this.root = root;
this.size = size;
}
@Override
public boolean containsKey(Object key) {
// works fine since null values are not supported
return get(key) != null;
}
@Override
public V get(Object key) {
if (key == null) {
throw new IllegalArgumentException("null keys are not supported");
}
final int hash = key.hashCode();
return root.get(key, hash);
}
@Override
public int size() {
assert size != 0 || root.isEmpty();
return size;
}
/**
* Associate key
with value
and return a new copy
* of the hash table. The current hash table is not modified.
*/
public CopyOnWriteHashMap copyAndPut(K key, V value) {
if (key == null) {
throw new IllegalArgumentException("null keys are not supported");
}
if (value == null) {
throw new IllegalArgumentException("null values are not supported");
}
final int hash = key.hashCode();
final MutableValueInt newValue = new MutableValueInt();
final InnerNode newRoot = root.put(key, hash, TOTAL_HASH_BITS, value, newValue);
final int newSize = size + newValue.value;
return new CopyOnWriteHashMap<>(newRoot, newSize);
}
/**
* Same as {#copyAndPut(Object, Object)} but for an arbitrary number of entries.
*/
public CopyOnWriteHashMap copyAndPutAll(Map extends K, ? extends V> other) {
return copyAndPutAll(other.entrySet());
}
public CopyOnWriteHashMap copyAndPutAll(Iterable> entries) {
CopyOnWriteHashMap result = this;
for (Entry entry : entries) {
result = result.copyAndPut(entry.getKey(), entry.getValue());
}
return result;
}
public CopyOnWriteHashMap copyAndPutAll(Stream> entries) {
return copyAndPutAll(entries::iterator);
}
/**
* Remove the given key from this map. The current hash table is not modified.
*/
public CopyOnWriteHashMap copyAndRemove(Object key) {
if (key == null) {
throw new IllegalArgumentException("null keys are not supported");
}
final int hash = key.hashCode();
final InnerNode newRoot = root.remove(key, hash);
if (root == newRoot) {
return this;
} else {
return new CopyOnWriteHashMap<>(newRoot, size - 1);
}
}
/**
* Same as {#copyAndRemove(Object)} but for an arbitrary number of entries.
*/
public CopyOnWriteHashMap copyAndRemoveAll(Collection> keys) {
CopyOnWriteHashMap result = this;
for (Object key : keys) {
result = result.copyAndRemove(key);
}
return result;
}
@Override
public Set> entrySet() {
return new AbstractSet>() {
@Override
public Iterator> iterator() {
return new EntryIterator<>(root);
}
@Override
public boolean contains(Object o) {
if (o == null || !(o instanceof Map.Entry)) {
return false;
}
Map.Entry, ?> entry = (java.util.Map.Entry, ?>) o;
return entry.getValue().equals(CopyOnWriteHashMap.this.get(entry.getKey()));
}
@Override
public int size() {
return CopyOnWriteHashMap.this.size();
}
};
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy