//@formatter:off
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 jane.test.map;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import jane.core.map.LongMap;
/**
* LongHashMap is an implementation of LongMap without concurrency locking.
* This code is adoption of 'HashMap' from Apache Harmony refactored to support primitive long keys.
*/
public final class LongHashMap<V> extends LongMap<V>
{
/**
* default size that an HashMap created using the default constructor would have.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for this table, used when not otherwise specified in a constructor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* Salt added to keys before hashing, so it is harder to trigger hash collision attack.
*/
// private static final long hashSalt = new Random().nextLong();
/**
* The internal data structure to hold Entries
*/
private Entry<V>[] elementData;
/**
* Actual count of entries
*/
private int elementCount;
/**
* modification count, to keep track of structural modifications between the HashMap and the iterator
*/
private int modCount;
/**
* maximum number of elements that can be put in this map before having to rehash
*/
private int threshold;
/**
* maximum ratio of (stored elements)/(storage size) which does not lead to rehash
*/
private final float loadFactor;
private static final class Entry<V>
{
private final long key;
private Entry<V> next;
private V value;
private Entry(long k)
{
key = k;
}
}
/**
* Calculates the capacity of storage required for storing given number of elements
* @param x number of elements
* @return storage size
*/
private static int calculateCapacity(int x)
{
if(x >= MAXIMUM_CAPACITY)
return MAXIMUM_CAPACITY;
if(x <= 0)
return 16;
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
private static int longHash(long key)
{
// key ^= hashSalt;
int h = (int)(key ^ (key >>> 32));
// h ^= (h >>> 20) ^ (h >>> 12);
// return h ^ (h >>> 7) ^ (h >>> 4);
return h;
}
/**
* Create a new element array
*
* @return Reference to the element array
*/
@SuppressWarnings("unchecked")
private Entry<V>[] newElementArray(int s)
{
return new Entry[s];
}
/**
* Computes the threshold for rehashing
*/
private void computeThreshold()
{
threshold = (int)(elementData.length * loadFactor);
}
/**
* Constructs a new empty {@code HashMap} instance.
*/
public LongHashMap()
{
this(DEFAULT_INITIAL_CAPACITY);
}
/**
* Constructs a new {@code HashMap} instance with the specified capacity.
* @param capacity the initial capacity of this hash map.
* @throws IllegalArgumentException when the capacity is less than zero.
*/
public LongHashMap(int capacity)
{
this(capacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new {@code HashMap} instance with the specified capacity and load factor.
* @param capacity the initial capacity of this hash map.
* @param loadFactor the initial load factor.
* @throws IllegalArgumentException when the capacity is less than zero or the load factor is less or equal to zero.
*/
public LongHashMap(int capacity, float loadFactor)
{
if(capacity < 0 || loadFactor <= 0)
throw new IllegalArgumentException();
capacity = calculateCapacity(capacity);
elementCount = 0;
elementData = newElementArray(capacity);
this.loadFactor = loadFactor;
computeThreshold();
}
/**
* Returns the number of elements in this map.
*/
@Override
public int size()
{
return elementCount;
}
/**
* Returns whether this map is empty.
* @return {@code true} if this map has no elements, {@code false} otherwise.
* @see #size()
*/
@Override
public boolean isEmpty()
{
return elementCount == 0;
}
/**
* Returns the value of the mapping with the specified key.
* @param key the key.
* @return the value of the mapping with the specified key, or {@code null}
* if no mapping for the specified key is found.
*/
@Override
public V get(long key)
{
Entry<V> m = findNonNullKeyEntry(key, longHash(key) & (elementData.length - 1));
return m != null ? m.value : null;
}
private Entry<V> findNonNullKeyEntry(long key, int index)
{
Entry<V> m = elementData[index];
while(m != null && m.key != key)
m = m.next;
return m;
}
/**
* Maps the specified key to the specified value.
* @param key the key.
* @param value the value.
* @return the value of any previous mapping with the specified key or
* {@code null} if there was no such mapping.
*/
@Override
public V put(long key, V value)
{
int index = longHash(key) & (elementData.length - 1);
Entry<V> entry = findNonNullKeyEntry(key, index);
if(entry == null)
{
modCount++;
entry = new Entry<>(key);
entry.next = elementData[index];
elementData[index] = entry;
if(++elementCount > threshold)
rehash();
}
V result = entry.value;
entry.value = value;
return result;
}
private void rehash()
{
int capacity = elementData.length;
if(capacity >= MAXIMUM_CAPACITY)
return;
int length = calculateCapacity((capacity == 0 ? 1 : capacity << 1));
Entry<V>[] newData = newElementArray(length);
for(int i = 0; i < elementData.length; i++)
{
Entry<V> entry = elementData[i];
elementData[i] = null;
while(entry != null)
{
int index = longHash(entry.key) & (length - 1);
Entry<V> next = entry.next;
entry.next = newData[index];
newData[index] = entry;
entry = next;
}
}
elementData = newData;
computeThreshold();
}
/**
* Removes the mapping with the specified key from this map.
* @param key the key of the mapping to remove.
* @return the value of the removed mapping or {@code null} if no mapping
* for the specified key was found.
*/
@Override
public V remove(long key)
{
int index = longHash(key) & (elementData.length - 1);
Entry<V> entry = elementData[index], last = null;
while(entry != null && entry.key != key)
{
last = entry;
entry = entry.next;
}
if(entry == null)
return null;
if(last == null)
elementData[index] = entry.next;
else
last.next = entry.next;
modCount++;
elementCount--;
return entry.value;
}
@Override
public boolean remove(long key, V value)
{
int index = longHash(key) & (elementData.length - 1);
Entry<V> entry = elementData[index], last = null;
while(entry != null && entry.key != key)
{
last = entry;
entry = entry.next;
}
if(entry == null)
return false;
if(entry.value == null)
{
if(value != null)
return false;
}
else if(!entry.value.equals(value))
return false;
if(last == null)
elementData[index] = entry.next;
else
last.next = entry.next;
modCount++;
elementCount--;
return true;
}
/**
* Removes all mappings from this hash map, leaving it empty.
* @see #isEmpty
* @see #size
*/
@Override
public void clear()
{
if(elementCount > 0)
{
elementCount = 0;
Arrays.fill(elementData, null);
modCount++;
}
}
@Override
public LongIterator keyIterator()
{
return new KeyIterator<>(this);
}
@Override
public Iterator<V> valueIterator()
{
return new ValueIterator<>(this);
}
@Override
public MapIterator<V> entryIterator()
{
return new EntryIterator<>(this);
}
private static abstract class AbstractMapIterator<V>
{
private final LongHashMap<V> associatedMap;
private int position;
private int expectedModCount;
private Entry<V> futureEntry;
protected Entry<V> currentEntry;
private Entry<V> prevEntry;
private AbstractMapIterator(LongHashMap<V> hm)
{
associatedMap = hm;
expectedModCount = hm.modCount;
futureEntry = null;
}
public boolean hasNext()
{
if(futureEntry != null)
return true;
for(; position < associatedMap.elementData.length; ++position)
if(associatedMap.elementData[position] != null)
return true;
return false;
}
private void checkConcurrentMod()
{
if(expectedModCount != associatedMap.modCount)
throw new ConcurrentModificationException();
}
protected final void moveNext()
{
checkConcurrentMod();
if(!hasNext())
throw new NoSuchElementException();
if(futureEntry == null)
{
currentEntry = associatedMap.elementData[position++];
futureEntry = currentEntry.next;
prevEntry = null;
}
else
{
if(currentEntry != null)
prevEntry = currentEntry;
currentEntry = futureEntry;
futureEntry = futureEntry.next;
}
}
public final void remove()
{
checkConcurrentMod();
if(currentEntry == null)
throw new IllegalStateException();
if(prevEntry == null)
{
int index = longHash(currentEntry.key) & (associatedMap.elementData.length - 1);
associatedMap.elementData[index] = associatedMap.elementData[index].next;
}
else
prevEntry.next = currentEntry.next;
currentEntry = null;
expectedModCount++;
associatedMap.modCount++;
associatedMap.elementCount--;
}
}
private static final class KeyIterator<V> extends AbstractMapIterator<V> implements LongIterator
{
private KeyIterator(LongHashMap<V> map)
{
super(map);
}
@Override
public long next()
{
moveNext();
return currentEntry.key;
}
}
private static final class ValueIterator<V> extends AbstractMapIterator<V> implements Iterator<V>
{
private ValueIterator(LongHashMap<V> map)
{
super(map);
}
@Override
public V next()
{
moveNext();
return currentEntry.value;
}
}
private static final class EntryIterator<V> extends AbstractMapIterator<V> implements MapIterator<V>
{
private EntryIterator(LongHashMap<V> map)
{
super(map);
}
@Override
public boolean moveToNext()
{
if(!hasNext())
return false;
moveNext();
return true;
}
@Override
public long key()
{
return currentEntry.key;
}
@Override
public V value()
{
return currentEntry.value;
}
}
}