com.gs.collections.impl.map.strategy.mutable.UnifiedMapWithHashingStrategy Maven / Gradle / Ivy
Show all versions of gs-collections Show documentation
/*
* Copyright 2015 Goldman Sachs.
*
* Licensed 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 com.gs.collections.impl.map.strategy.mutable;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import com.gs.collections.api.block.HashingStrategy;
import com.gs.collections.api.block.function.Function;
import com.gs.collections.api.block.function.Function0;
import com.gs.collections.api.block.function.Function2;
import com.gs.collections.api.block.procedure.Procedure;
import com.gs.collections.api.block.procedure.Procedure2;
import com.gs.collections.api.block.procedure.primitive.ObjectIntProcedure;
import com.gs.collections.api.map.ImmutableMap;
import com.gs.collections.api.map.MapIterable;
import com.gs.collections.api.map.MutableMap;
import com.gs.collections.api.map.UnsortedMapIterable;
import com.gs.collections.api.tuple.Pair;
import com.gs.collections.impl.block.factory.Functions;
import com.gs.collections.impl.block.factory.Predicates;
import com.gs.collections.impl.block.procedure.MapCollectProcedure;
import com.gs.collections.impl.factory.HashingStrategyMaps;
import com.gs.collections.impl.factory.Sets;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.map.mutable.AbstractMutableMap;
import com.gs.collections.impl.parallel.BatchIterable;
import com.gs.collections.impl.set.strategy.mutable.UnifiedSetWithHashingStrategy;
import com.gs.collections.impl.tuple.ImmutableEntry;
import com.gs.collections.impl.utility.ArrayIterate;
import com.gs.collections.impl.utility.Iterate;
import net.jcip.annotations.NotThreadSafe;
/**
* UnifiedMapWithHashingStrategy stores key/value pairs in a single array, where alternate slots are keys and values.
* This is nicer to CPU caches as consecutive memory addresses are very cheap to access. Entry objects are not stored in the
* table like in java.util.HashMap. Instead of trying to deal with collisions in the main array using Entry objects,
* we put a special object in the key slot and put a regular Object[] in the value slot. The array contains the key value
* pairs in consecutive slots, just like the main array, but it's a linear list with no hashing.
*
* The difference between UnifiedMap and UnifiedMapWithHashingStrategy is that a HashingStrategy based UnifiedMap
* does not rely on the hashCode or equality of the object at the key, but instead relies on a HashingStrategy
* implementation provided by a developer to compute the hashCode and equals for the objects stored in the map.
*/
@NotThreadSafe
@SuppressWarnings("ObjectEquality")
public class UnifiedMapWithHashingStrategy extends AbstractMutableMap
implements Externalizable, BatchIterable
{
protected static final Object NULL_KEY = new Object()
{
@Override
public boolean equals(Object obj)
{
throw new RuntimeException("Possible corruption through unsynchronized concurrent modification.");
}
@Override
public int hashCode()
{
throw new RuntimeException("Possible corruption through unsynchronized concurrent modification.");
}
@Override
public String toString()
{
return "UnifiedMapWithHashingStrategy.NULL_KEY";
}
};
protected static final Object CHAINED_KEY = new Object()
{
@Override
public boolean equals(Object obj)
{
throw new RuntimeException("Possible corruption through unsynchronized concurrent modification.");
}
@Override
public int hashCode()
{
throw new RuntimeException("Possible corruption through unsynchronized concurrent modification.");
}
@Override
public String toString()
{
return "UnifiedMapWithHashingStrategy.CHAINED_KEY";
}
};
protected static final float DEFAULT_LOAD_FACTOR = 0.75f;
protected static final int DEFAULT_INITIAL_CAPACITY = 8;
private static final long serialVersionUID = 1L;
protected transient Object[] table;
protected transient int occupied;
protected float loadFactor = DEFAULT_LOAD_FACTOR;
protected int maxSize;
private HashingStrategy super K> hashingStrategy;
/**
* @deprecated No argument default constructor used for serialization. Instantiating an UnifiedMapWithHashingStrategyMultimap with
* this constructor will have a null hashingStrategy and throw NullPointerException when used.
*/
@Deprecated
public UnifiedMapWithHashingStrategy()
{
}
public UnifiedMapWithHashingStrategy(HashingStrategy super K> hashingStrategy)
{
this.hashingStrategy = hashingStrategy;
this.allocate(DEFAULT_INITIAL_CAPACITY << 1);
}
public UnifiedMapWithHashingStrategy(HashingStrategy super K> hashingStrategy, int initialCapacity)
{
this(hashingStrategy, initialCapacity, DEFAULT_LOAD_FACTOR);
}
public UnifiedMapWithHashingStrategy(HashingStrategy super K> hashingStrategy, int initialCapacity, float loadFactor)
{
if (initialCapacity < 0)
{
throw new IllegalArgumentException("initial capacity cannot be less than 0");
}
if (loadFactor <= 0.0)
{
throw new IllegalArgumentException("load factor cannot be less than or equal to 0");
}
if (loadFactor > 1.0)
{
throw new IllegalArgumentException("load factor cannot be greater than 1");
}
this.hashingStrategy = hashingStrategy;
this.loadFactor = loadFactor;
this.init(this.fastCeil(initialCapacity / loadFactor));
}
public UnifiedMapWithHashingStrategy(HashingStrategy super K> hashingStrategy, Map extends K, ? extends V> map)
{
this(hashingStrategy, Math.max(map.size(), DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
this.putAll(map);
}
public UnifiedMapWithHashingStrategy(HashingStrategy super K> hashingStrategy, Pair... pairs)
{
this(hashingStrategy, Math.max(pairs.length, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
ArrayIterate.forEach(pairs, new MapCollectProcedure, K, V>(
this,
Functions.firstOfPair(),
Functions.secondOfPair()));
}
public static UnifiedMapWithHashingStrategy newMap(HashingStrategy super K> hashingStrategy)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy);
}
public static UnifiedMapWithHashingStrategy newMap(
HashingStrategy super K> hashingStrategy,
int size)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, size);
}
public static UnifiedMapWithHashingStrategy newMap(
HashingStrategy super K> hashingStrategy,
int size,
float loadFactor)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, size, loadFactor);
}
public static UnifiedMapWithHashingStrategy newMap(
HashingStrategy super K> hashingStrategy,
Map extends K, ? extends V> map)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, map);
}
public static UnifiedMapWithHashingStrategy newMapWith(HashingStrategy super K> hashingStrategy, Iterable> inputIterable)
{
UnifiedMapWithHashingStrategy outputMap = UnifiedMapWithHashingStrategy.newMap(hashingStrategy);
for (Pair single : inputIterable)
{
outputMap.add(single);
}
return outputMap;
}
public static UnifiedMapWithHashingStrategy newMap(UnifiedMapWithHashingStrategy map)
{
return new UnifiedMapWithHashingStrategy(map.hashingStrategy, map);
}
public static UnifiedMapWithHashingStrategy newMapWith(
HashingStrategy super K> hashingStrategy,
Pair... pairs)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, pairs);
}
public static UnifiedMapWithHashingStrategy newWithKeysValues(
HashingStrategy super K> hashingStrategy,
K key, V value)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, 1).withKeysValues(key, value);
}
public static UnifiedMapWithHashingStrategy newWithKeysValues(
HashingStrategy super K> hashingStrategy,
K key1, V value1,
K key2, V value2)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, 2).withKeysValues(key1, value1, key2, value2);
}
public static UnifiedMapWithHashingStrategy newWithKeysValues(
HashingStrategy super K> hashingStrategy,
K key1, V value1,
K key2, V value2,
K key3, V value3)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, 3).withKeysValues(key1, value1, key2, value2, key3, value3);
}
public static UnifiedMapWithHashingStrategy newWithKeysValues(
HashingStrategy super K> hashingStrategy,
K key1, V value1,
K key2, V value2,
K key3, V value3,
K key4, V value4)
{
return new UnifiedMapWithHashingStrategy(hashingStrategy, 4).withKeysValues(key1, value1, key2, value2, key3, value3, key4, value4);
}
public UnifiedMapWithHashingStrategy withKeysValues(K key, V value)
{
this.put(key, value);
return this;
}
public UnifiedMapWithHashingStrategy withKeysValues(K key1, V value1, K key2, V value2)
{
this.put(key1, value1);
this.put(key2, value2);
return this;
}
public UnifiedMapWithHashingStrategy withKeysValues(K key1, V value1, K key2, V value2, K key3, V value3)
{
this.put(key1, value1);
this.put(key2, value2);
this.put(key3, value3);
return this;
}
public UnifiedMapWithHashingStrategy withKeysValues(K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4)
{
this.put(key1, value1);
this.put(key2, value2);
this.put(key3, value3);
this.put(key4, value4);
return this;
}
public HashingStrategy super K> hashingStrategy()
{
return this.hashingStrategy;
}
@Override
public UnifiedMapWithHashingStrategy clone()
{
return new UnifiedMapWithHashingStrategy(this.hashingStrategy, this);
}
public MutableMap newEmpty()
{
return new UnifiedMapWithHashingStrategy(this.hashingStrategy);
}
@Override
public MutableMap newEmpty(int capacity)
{
return UnifiedMapWithHashingStrategy.newMap(this.hashingStrategy, capacity);
}
private int fastCeil(float v)
{
int possibleResult = (int) v;
if (v - possibleResult > 0.0F)
{
possibleResult++;
}
return possibleResult;
}
protected int init(int initialCapacity)
{
int capacity = 1;
while (capacity < initialCapacity)
{
capacity <<= 1;
}
return this.allocate(capacity);
}
protected int allocate(int capacity)
{
this.allocateTable(capacity << 1); // the table size is twice the capacity to handle both keys and values
this.computeMaxSize(capacity);
return capacity;
}
protected void allocateTable(int sizeToAllocate)
{
this.table = new Object[sizeToAllocate];
}
protected void computeMaxSize(int capacity)
{
// need at least one free slot for open addressing
this.maxSize = Math.min(capacity - 1, (int) (capacity * this.loadFactor));
}
protected final int index(K key)
{
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
int h = this.hashingStrategy.computeHashCode(key);
h ^= h >>> 20 ^ h >>> 12;
h ^= h >>> 7 ^ h >>> 4;
return (h & (this.table.length >> 1) - 1) << 1;
}
public void clear()
{
if (this.occupied == 0)
{
return;
}
this.occupied = 0;
Object[] set = this.table;
for (int i = set.length; i-- > 0; )
{
set[i] = null;
}
}
public V put(K key, V value)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
this.table[index + 1] = value;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return null;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
V result = (V) this.table[index + 1];
this.table[index + 1] = value;
return result;
}
return this.chainedPut(key, index, value);
}
private V chainedPut(K key, int index, V value)
{
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
for (int i = 0; i < chain.length; i += 2)
{
if (chain[i] == null)
{
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
chain[i + 1] = value;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return null;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
V result = (V) chain[i + 1];
chain[i + 1] = value;
return result;
}
}
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
this.table[index + 1] = newChain;
newChain[chain.length] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[chain.length + 1] = value;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return null;
}
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[3] = value;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return null;
}
@Override
public V updateValue(K key, Function0 extends V> factory, Function super V, ? extends V> function)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.valueOf(factory.value());
this.table[index + 1] = result;
++this.occupied;
return result;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
V oldValue = (V) this.table[index + 1];
V newValue = function.valueOf(oldValue);
this.table[index + 1] = newValue;
return newValue;
}
return this.chainedUpdateValue(key, index, factory, function);
}
private V chainedUpdateValue(K key, int index, Function0 extends V> factory, Function super V, ? extends V> function)
{
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
for (int i = 0; i < chain.length; i += 2)
{
if (chain[i] == null)
{
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.valueOf(factory.value());
chain[i + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
V oldValue = (V) chain[i + 1];
V result = function.valueOf(oldValue);
chain[i + 1] = result;
return result;
}
}
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
this.table[index + 1] = newChain;
newChain[chain.length] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.valueOf(factory.value());
newChain[chain.length + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.valueOf(factory.value());
newChain[3] = result;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
@Override
public V updateValueWith(K key, Function0 extends V> factory, Function2 super V, ? super P, ? extends V> function, P parameter)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.value(factory.value(), parameter);
this.table[index + 1] = result;
++this.occupied;
return result;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
V oldValue = (V) this.table[index + 1];
V newValue = function.value(oldValue, parameter);
this.table[index + 1] = newValue;
return newValue;
}
return this.chainedUpdateValueWith(key, index, factory, function, parameter);
}
private
V chainedUpdateValueWith(
K key,
int index,
Function0 extends V> factory,
Function2 super V, ? super P, ? extends V> function,
P parameter)
{
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
for (int i = 0; i < chain.length; i += 2)
{
if (chain[i] == null)
{
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.value(factory.value(), parameter);
chain[i + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
V oldValue = (V) chain[i + 1];
V result = function.value(oldValue, parameter);
chain[i + 1] = result;
return result;
}
}
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
this.table[index + 1] = newChain;
newChain[chain.length] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.value(factory.value(), parameter);
newChain[chain.length + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
V result = function.value(factory.value(), parameter);
newChain[3] = result;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
@Override
public V getIfAbsentPut(K key, Function0 extends V> function)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
V result = function.value();
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
this.table[index + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
return (V) this.table[index + 1];
}
return this.chainedGetIfAbsentPut(key, index, function);
}
private V chainedGetIfAbsentPut(K key, int index, Function0 extends V> function)
{
V result = null;
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
int i = 0;
for (; i < chain.length; i += 2)
{
if (chain[i] == null)
{
result = function.value();
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
chain[i + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
break;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
result = (V) chain[i + 1];
break;
}
}
if (i == chain.length)
{
result = function.value();
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
newChain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[i + 1] = result;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
}
else
{
result = function.value();
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[3] = result;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
return result;
}
@Override
public V getIfAbsentPut(K key, V value)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
this.table[index + 1] = value;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return value;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
return (V) this.table[index + 1];
}
return this.chainedGetIfAbsentPut(key, index, value);
}
private V chainedGetIfAbsentPut(K key, int index, V value)
{
V result = value;
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
int i = 0;
for (; i < chain.length; i += 2)
{
if (chain[i] == null)
{
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
chain[i + 1] = value;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
break;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
result = (V) chain[i + 1];
break;
}
}
if (i == chain.length)
{
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
newChain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[i + 1] = value;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
}
else
{
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[3] = value;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
return result;
}
@Override
public
V getIfAbsentPutWith(K key, Function super P, ? extends V> function, P parameter)
{
int index = this.index(key);
Object cur = this.table[index];
if (cur == null)
{
V result = function.valueOf(parameter);
this.table[index] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
this.table[index + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
return result;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, key))
{
return (V) this.table[index + 1];
}
return this.chainedGetIfAbsentPutWith(key, index, function, parameter);
}
private
V chainedGetIfAbsentPutWith(K key, int index, Function super P, ? extends V> function, P parameter)
{
V result = null;
if (this.table[index] == CHAINED_KEY)
{
Object[] chain = (Object[]) this.table[index + 1];
int i = 0;
for (; i < chain.length; i += 2)
{
if (chain[i] == null)
{
result = function.valueOf(parameter);
chain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
chain[i + 1] = result;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
break;
}
if (this.nonNullTableObjectEquals(chain[i], key))
{
result = (V) chain[i + 1];
break;
}
}
if (i == chain.length)
{
result = function.valueOf(parameter);
Object[] newChain = new Object[chain.length + 4];
System.arraycopy(chain, 0, newChain, 0, chain.length);
newChain[i] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[i + 1] = result;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
}
else
{
result = function.valueOf(parameter);
Object[] newChain = new Object[4];
newChain[0] = this.table[index];
newChain[1] = this.table[index + 1];
newChain[2] = UnifiedMapWithHashingStrategy.toSentinelIfNull(key);
newChain[3] = result;
this.table[index] = CHAINED_KEY;
this.table[index + 1] = newChain;
if (++this.occupied > this.maxSize)
{
this.rehash(this.table.length);
}
}
return result;
}
public int getCollidingBuckets()
{
int count = 0;
for (int i = 0; i < this.table.length; i += 2)
{
if (this.table[i] == CHAINED_KEY)
{
count++;
}
}
return count;
}
/**
* Returns the number of JVM words that is used by this map. A word is 4 bytes in a 32bit VM and 8 bytes in a 64bit
* VM. Each array has a 2 word header, thus the formula is:
* words = (internal table length + 2) + sum (for all chains (chain length + 2))
*
* @return the number of JVM words that is used by this map.
*/
public int getMapMemoryUsedInWords()
{
int headerSize = 2;
int sizeInWords = this.table.length + headerSize;
for (int i = 0; i < this.table.length; i += 2)
{
if (this.table[i] == CHAINED_KEY)
{
sizeInWords += headerSize + ((Object[]) this.table[i + 1]).length;
}
}
return sizeInWords;
}
protected void rehash(int newCapacity)
{
int oldLength = this.table.length;
Object[] old = this.table;
this.allocate(newCapacity);
this.occupied = 0;
for (int i = 0; i < oldLength; i += 2)
{
Object cur = old[i];
if (cur == CHAINED_KEY)
{
Object[] chain = (Object[]) old[i + 1];
for (int j = 0; j < chain.length; j += 2)
{
if (chain[j] != null)
{
this.put(this.nonSentinel(chain[j]), (V) chain[j + 1]);
}
}
}
else if (cur != null)
{
this.put(this.nonSentinel(cur), (V) old[i + 1]);
}
}
}
public V get(Object key)
{
int index = this.index((K) key);
Object cur = this.table[index];
if (cur != null)
{
Object val = this.table[index + 1];
if (cur == CHAINED_KEY)
{
return this.getFromChain((Object[]) val, (K) key);
}
if (this.nonNullTableObjectEquals(cur, (K) key))
{
return (V) val;
}
}
return null;
}
private V getFromChain(Object[] chain, K key)
{
for (int i = 0; i < chain.length; i += 2)
{
Object k = chain[i];
if (k == null)
{
return null;
}
if (this.nonNullTableObjectEquals(k, key))
{
return (V) chain[i + 1];
}
}
return null;
}
public boolean containsKey(Object key)
{
int index = this.index((K) key);
Object cur = this.table[index];
if (cur == null)
{
return false;
}
if (cur != CHAINED_KEY && this.nonNullTableObjectEquals(cur, (K) key))
{
return true;
}
return cur == CHAINED_KEY && this.chainContainsKey((Object[]) this.table[index + 1], (K) key);
}
private boolean chainContainsKey(Object[] chain, K key)
{
for (int i = 0; i < chain.length; i += 2)
{
Object k = chain[i];
if (k == null)
{
return false;
}
if (this.nonNullTableObjectEquals(k, key))
{
return true;
}
}
return false;
}
public boolean containsValue(Object value)
{
for (int i = 0; i < this.table.length; i += 2)
{
if (this.table[i] == CHAINED_KEY)
{
if (this.chainedContainsValue((Object[]) this.table[i + 1], (V) value))
{
return true;
}
}
else if (this.table[i] != null)
{
if (UnifiedMapWithHashingStrategy.nullSafeEquals(value, this.table[i + 1]))
{
return true;
}
}
}
return false;
}
private boolean chainedContainsValue(Object[] chain, V value)
{
for (int i = 0; i < chain.length; i += 2)
{
if (chain[i] == null)
{
return false;
}
if (UnifiedMapWithHashingStrategy.nullSafeEquals(value, chain[i + 1]))
{
return true;
}
}
return false;
}
public void forEachKeyValue(Procedure2 super K, ? super V> procedure)
{
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachEntry((Object[]) this.table[i + 1], procedure);
}
else if (cur != null)
{
procedure.value(this.nonSentinel(cur), (V) this.table[i + 1]);
}
}
}
public MutableMap collectKeysAndValues(
Iterable iterable,
Function super E, ? extends K> keyFunction,
Function super E, ? extends V> valueFunction)
{
Iterate.forEach(iterable, new MapCollectProcedure(this, keyFunction, valueFunction));
return this;
}
public V removeKey(K key)
{
return this.remove(key);
}
private void chainedForEachEntry(Object[] chain, Procedure2 super K, ? super V> procedure)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
procedure.value(this.nonSentinel(cur), (V) chain[i + 1]);
}
}
public int getBatchCount(int batchSize)
{
return Math.max(1, this.table.length / 2 / batchSize);
}
public void batchForEach(Procedure super V> procedure, int sectionIndex, int sectionCount)
{
int sectionSize = this.table.length / sectionCount;
int start = sectionIndex * sectionSize;
int end = sectionIndex == sectionCount - 1 ? this.table.length : start + sectionSize;
if (start % 2 == 0)
{
start++;
}
for (int i = start; i < end; i += 2)
{
Object value = this.table[i];
if (value instanceof Object[])
{
this.chainedForEachValue((Object[]) value, procedure);
}
else if (value == null && this.table[i - 1] != null || value != null)
{
procedure.value((V) value);
}
}
}
@Override
public void forEachKey(Procedure super K> procedure)
{
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachKey((Object[]) this.table[i + 1], procedure);
}
else if (cur != null)
{
procedure.value(this.nonSentinel(cur));
}
}
}
private void chainedForEachKey(Object[] chain, Procedure super K> procedure)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
procedure.value(this.nonSentinel(cur));
}
}
@Override
public void forEachValue(Procedure super V> procedure)
{
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachValue((Object[]) this.table[i + 1], procedure);
}
else if (cur != null)
{
procedure.value((V) this.table[i + 1]);
}
}
}
private void chainedForEachValue(Object[] chain, Procedure super V> procedure)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
procedure.value((V) chain[i + 1]);
}
}
@Override
public boolean isEmpty()
{
return this.occupied == 0;
}
public void putAll(Map extends K, ? extends V> map)
{
if (map instanceof UnifiedMapWithHashingStrategy, ?>)
{
this.copyMap((UnifiedMapWithHashingStrategy) map);
}
else if (map instanceof UnsortedMapIterable)
{
MapIterable mapIterable = (MapIterable) map;
mapIterable.forEachKeyValue(new Procedure2()
{
public void value(K key, V value)
{
UnifiedMapWithHashingStrategy.this.put(key, value);
}
});
}
else
{
Iterator extends Entry extends K, ? extends V>> iterator = this.getEntrySetFrom(map).iterator();
while (iterator.hasNext())
{
Entry extends K, ? extends V> entry = iterator.next();
this.put(entry.getKey(), entry.getValue());
}
}
}
private Set extends Entry extends K, ? extends V>> getEntrySetFrom(Map extends K, ? extends V> map)
{
Set extends Entry extends K, ? extends V>> entries = map.entrySet();
if (entries != null)
{
return entries;
}
if (map.isEmpty())
{
return Sets.immutable.>of().castToSet();
}
throw new IllegalStateException("Entry set was null and size was non-zero");
}
protected void copyMap(UnifiedMapWithHashingStrategy unifiedMap)
{
for (int i = 0; i < unifiedMap.table.length; i += 2)
{
Object cur = unifiedMap.table[i];
if (cur == CHAINED_KEY)
{
this.copyChain((Object[]) unifiedMap.table[i + 1]);
}
else if (cur != null)
{
this.put(this.nonSentinel(cur), (V) unifiedMap.table[i + 1]);
}
}
}
private void copyChain(Object[] chain)
{
for (int j = 0; j < chain.length; j += 2)
{
Object cur = chain[j];
if (cur == null)
{
break;
}
this.put(this.nonSentinel(cur), (V) chain[j + 1]);
}
}
public V remove(Object key)
{
int index = this.index((K) key);
Object cur = this.table[index];
if (cur != null)
{
Object val = this.table[index + 1];
if (cur == CHAINED_KEY)
{
return this.removeFromChain((Object[]) val, (K) key, index);
}
if (this.nonNullTableObjectEquals(cur, (K) key))
{
this.table[index] = null;
this.table[index + 1] = null;
this.occupied--;
return (V) val;
}
}
return null;
}
private V removeFromChain(Object[] chain, K key, int index)
{
for (int i = 0; i < chain.length; i += 2)
{
Object k = chain[i];
if (k == null)
{
return null;
}
if (this.nonNullTableObjectEquals(k, key))
{
V val = (V) chain[i + 1];
this.overwriteWithLastElementFromChain(chain, index, i);
return val;
}
}
return null;
}
private void overwriteWithLastElementFromChain(Object[] chain, int index, int i)
{
int j = chain.length - 2;
for (; j > i; j -= 2)
{
if (chain[j] != null)
{
chain[i] = chain[j];
chain[i + 1] = chain[j + 1];
break;
}
}
chain[j] = null;
chain[j + 1] = null;
if (j == 0)
{
this.table[index] = null;
this.table[index + 1] = null;
}
this.occupied--;
}
public int size()
{
return this.occupied;
}
public Set> entrySet()
{
return new EntrySet();
}
public Set keySet()
{
return new KeySet();
}
public Collection values()
{
return new ValuesCollection();
}
@Override
public boolean equals(Object object)
{
if (this == object)
{
return true;
}
if (!(object instanceof Map))
{
return false;
}
Map, ?> other = (Map, ?>) object;
if (this.size() != other.size())
{
return false;
}
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
if (!this.chainedEquals((Object[]) this.table[i + 1], other))
{
return false;
}
}
else if (cur != null)
{
K key = this.nonSentinel(cur);
V value = (V) this.table[i + 1];
Object otherValue = other.get(key);
if (!UnifiedMapWithHashingStrategy.nullSafeEquals(otherValue, value) || (value == null && otherValue == null && !other.containsKey(key)))
{
return false;
}
}
}
return true;
}
private boolean chainedEquals(Object[] chain, Map, ?> other)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return true;
}
K key = this.nonSentinel(cur);
V value = (V) chain[i + 1];
Object otherValue = other.get(key);
if (!UnifiedMapWithHashingStrategy.nullSafeEquals(otherValue, value) || (value == null && otherValue == null && !other.containsKey(key)))
{
return false;
}
}
return true;
}
@Override
public int hashCode()
{
int hashCode = 0;
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
hashCode += this.chainedHashCode((Object[]) this.table[i + 1]);
}
else if (cur != null)
{
Object value = this.table[i + 1];
hashCode += this.hashingStrategy.computeHashCode(this.nonSentinel(cur)) ^ (value == null ? 0 : value.hashCode());
}
}
return hashCode;
}
private int chainedHashCode(Object[] chain)
{
int hashCode = 0;
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return hashCode;
}
Object value = chain[i + 1];
hashCode += this.hashingStrategy.computeHashCode(this.nonSentinel(cur)) ^ (value == null ? 0 : value.hashCode());
}
return hashCode;
}
@Override
public String toString()
{
final StringBuilder builder = new StringBuilder();
builder.append('{');
this.forEachKeyValue(new Procedure2()
{
private boolean first = true;
public void value(K key, V value)
{
if (this.first)
{
this.first = false;
}
else
{
builder.append(", ");
}
builder.append(key == UnifiedMapWithHashingStrategy.this ? "(this Map)" : key);
builder.append('=');
builder.append(value == UnifiedMapWithHashingStrategy.this ? "(this Map)" : value);
}
});
builder.append('}');
return builder.toString();
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
this.hashingStrategy = (HashingStrategy super K>) in.readObject();
int size = in.readInt();
this.loadFactor = in.readFloat();
this.init(Math.max((int) (size / this.loadFactor) + 1,
DEFAULT_INITIAL_CAPACITY));
for (int i = 0; i < size; i++)
{
this.put((K) in.readObject(), (V) in.readObject());
}
}
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeObject(this.hashingStrategy);
out.writeInt(this.size());
out.writeFloat(this.loadFactor);
for (int i = 0; i < this.table.length; i += 2)
{
Object o = this.table[i];
if (o != null)
{
if (o == CHAINED_KEY)
{
this.writeExternalChain(out, (Object[]) this.table[i + 1]);
}
else
{
out.writeObject(this.nonSentinel(o));
out.writeObject(this.table[i + 1]);
}
}
}
}
private void writeExternalChain(ObjectOutput out, Object[] chain) throws IOException
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
out.writeObject(this.nonSentinel(cur));
out.writeObject(chain[i + 1]);
}
}
@Override
public void forEachWithIndex(ObjectIntProcedure super V> objectIntProcedure)
{
int index = 0;
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
index = this.chainedForEachValueWithIndex((Object[]) this.table[i + 1], objectIntProcedure, index);
}
else if (cur != null)
{
objectIntProcedure.value((V) this.table[i + 1], index++);
}
}
}
private int chainedForEachValueWithIndex(Object[] chain, ObjectIntProcedure super V> objectIntProcedure, int index)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return index;
}
objectIntProcedure.value((V) chain[i + 1], index++);
}
return index;
}
@Override
public void forEachWith(Procedure2 super V, ? super P> procedure, P parameter)
{
for (int i = 0; i < this.table.length; i += 2)
{
Object cur = this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachValueWith((Object[]) this.table[i + 1], procedure, parameter);
}
else if (cur != null)
{
procedure.value((V) this.table[i + 1], parameter);
}
}
}
private
void chainedForEachValueWith(
Object[] chain,
Procedure2 super V, ? super P> procedure,
P parameter)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
procedure.value((V) chain[i + 1], parameter);
}
}
@Override
public MutableMap collectValues(Function2 super K, ? super V, ? extends R> function)
{
UnifiedMapWithHashingStrategy target = UnifiedMapWithHashingStrategy.newMap(this.hashingStrategy);
target.loadFactor = this.loadFactor;
target.occupied = this.occupied;
target.allocate(this.table.length >> 1);
for (int i = 0; i < target.table.length; i += 2)
{
target.table[i] = this.table[i];
if (this.table[i] == CHAINED_KEY)
{
Object[] chainedTable = (Object[]) this.table[i + 1];
Object[] chainedTargetTable = new Object[chainedTable.length];
for (int j = 0; j < chainedTargetTable.length; j += 2)
{
if (chainedTable[j] != null)
{
chainedTargetTable[j] = chainedTable[j];
chainedTargetTable[j + 1] = function.value(this.nonSentinel(chainedTable[j]), (V) chainedTable[j + 1]);
}
}
target.table[i + 1] = chainedTargetTable;
}
else if (this.table[i] != null)
{
target.table[i + 1] = function.value(this.nonSentinel(this.table[i]), (V) this.table[i + 1]);
}
}
return target;
}
protected class KeySet implements Set, Serializable, BatchIterable
{
private static final long serialVersionUID = 1L;
public boolean add(K key)
{
throw new UnsupportedOperationException("Cannot call add() on " + this.getClass().getSimpleName());
}
public boolean addAll(Collection extends K> collection)
{
throw new UnsupportedOperationException("Cannot call addAll() on " + this.getClass().getSimpleName());
}
public void clear()
{
UnifiedMapWithHashingStrategy.this.clear();
}
public boolean contains(Object o)
{
return UnifiedMapWithHashingStrategy.this.containsKey(o);
}
public boolean containsAll(Collection> collection)
{
for (Object aCollection : collection)
{
if (!UnifiedMapWithHashingStrategy.this.containsKey(aCollection))
{
return false;
}
}
return true;
}
public boolean isEmpty()
{
return UnifiedMapWithHashingStrategy.this.isEmpty();
}
public Iterator iterator()
{
return new KeySetIterator();
}
public boolean remove(Object key)
{
int oldSize = UnifiedMapWithHashingStrategy.this.occupied;
UnifiedMapWithHashingStrategy.this.remove(key);
return UnifiedMapWithHashingStrategy.this.occupied != oldSize;
}
public boolean removeAll(Collection> collection)
{
int oldSize = UnifiedMapWithHashingStrategy.this.occupied;
for (Object object : collection)
{
UnifiedMapWithHashingStrategy.this.remove(object);
}
return oldSize != UnifiedMapWithHashingStrategy.this.occupied;
}
public void putIfFound(Object key, Map other)
{
int index = UnifiedMapWithHashingStrategy.this.index((K) key);
Object cur = UnifiedMapWithHashingStrategy.this.table[index];
if (cur != null)
{
Object val = UnifiedMapWithHashingStrategy.this.table[index + 1];
if (cur == CHAINED_KEY)
{
this.putIfFoundFromChain((Object[]) val, (K) key, other);
return;
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(cur, (K) key))
{
other.put(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) val);
}
}
}
private void putIfFoundFromChain(Object[] chain, K key, Map other)
{
for (int i = 0; i < chain.length; i += 2)
{
Object k = chain[i];
if (k == null)
{
return;
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(k, key))
{
other.put(UnifiedMapWithHashingStrategy.this.nonSentinel(k), (V) chain[i + 1]);
}
}
}
public boolean retainAll(Collection> collection)
{
int retainedSize = collection.size();
UnifiedMapWithHashingStrategy retainedCopy = new UnifiedMapWithHashingStrategy(
UnifiedMapWithHashingStrategy.this.hashingStrategy, retainedSize, UnifiedMapWithHashingStrategy.this.loadFactor);
for (Object key : collection)
{
this.putIfFound(key, retainedCopy);
}
if (retainedCopy.size() < this.size())
{
UnifiedMapWithHashingStrategy.this.maxSize = retainedCopy.maxSize;
UnifiedMapWithHashingStrategy.this.occupied = retainedCopy.occupied;
UnifiedMapWithHashingStrategy.this.table = retainedCopy.table;
return true;
}
return false;
}
public int size()
{
return UnifiedMapWithHashingStrategy.this.size();
}
public void forEach(Procedure super K> procedure)
{
UnifiedMapWithHashingStrategy.this.forEachKey(procedure);
}
public int getBatchCount(int batchSize)
{
return UnifiedMapWithHashingStrategy.this.getBatchCount(batchSize);
}
public void batchForEach(Procedure super K> procedure, int sectionIndex, int sectionCount)
{
Object[] map = UnifiedMapWithHashingStrategy.this.table;
int sectionSize = map.length / sectionCount;
int start = sectionIndex * sectionSize;
int end = sectionIndex == sectionCount - 1 ? map.length : start + sectionSize;
if (start % 2 != 0)
{
start++;
}
for (int i = start; i < end; i += 2)
{
Object cur = map[i];
if (cur == CHAINED_KEY)
{
UnifiedMapWithHashingStrategy.this.chainedForEachKey((Object[]) map[i + 1], procedure);
}
else if (cur != null)
{
procedure.value(UnifiedMapWithHashingStrategy.this.nonSentinel(cur));
}
}
}
protected void copyKeys(Object[] result)
{
Object[] table = UnifiedMapWithHashingStrategy.this.table;
int count = 0;
for (int i = 0; i < table.length; i += 2)
{
Object x = table[i];
if (x != null)
{
if (x == CHAINED_KEY)
{
Object[] chain = (Object[]) table[i + 1];
for (int j = 0; j < chain.length; j += 2)
{
Object cur = chain[j];
if (cur == null)
{
break;
}
result[count++] = UnifiedMapWithHashingStrategy.this.nonSentinel(cur);
}
}
else
{
result[count++] = UnifiedMapWithHashingStrategy.this.nonSentinel(x);
}
}
}
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Set)
{
Set> other = (Set>) obj;
if (other.size() == this.size())
{
return this.containsAll(other);
}
}
return false;
}
@Override
public int hashCode()
{
int hashCode = 0;
Object[] table = UnifiedMapWithHashingStrategy.this.table;
for (int i = 0; i < table.length; i += 2)
{
Object x = table[i];
if (x != null)
{
if (x == CHAINED_KEY)
{
Object[] chain = (Object[]) table[i + 1];
for (int j = 0; j < chain.length; j += 2)
{
Object cur = chain[j];
if (cur == null)
{
break;
}
hashCode += UnifiedMapWithHashingStrategy.this.hashingStrategy.computeHashCode(UnifiedMapWithHashingStrategy.this.nonSentinel(cur));
}
}
else
{
hashCode += UnifiedMapWithHashingStrategy.this.hashingStrategy.computeHashCode(UnifiedMapWithHashingStrategy.this.nonSentinel(x));
}
}
}
return hashCode;
}
@Override
public String toString()
{
return Iterate.makeString(this, "[", ", ", "]");
}
public Object[] toArray()
{
int size = UnifiedMapWithHashingStrategy.this.size();
Object[] result = new Object[size];
this.copyKeys(result);
return result;
}
public T[] toArray(T[] result)
{
int size = UnifiedMapWithHashingStrategy.this.size();
if (result.length < size)
{
result = (T[]) Array.newInstance(result.getClass().getComponentType(), size);
}
this.copyKeys(result);
if (size < result.length)
{
result[size] = null;
}
return result;
}
protected Object writeReplace()
{
UnifiedSetWithHashingStrategy replace = UnifiedSetWithHashingStrategy.newSet(
UnifiedMapWithHashingStrategy.this.hashingStrategy, UnifiedMapWithHashingStrategy.this.size());
for (int i = 0; i < UnifiedMapWithHashingStrategy.this.table.length; i += 2)
{
Object cur = UnifiedMapWithHashingStrategy.this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedAddToSet((Object[]) UnifiedMapWithHashingStrategy.this.table[i + 1], replace);
}
else if (cur != null)
{
replace.add(UnifiedMapWithHashingStrategy.this.nonSentinel(cur));
}
}
return replace;
}
private void chainedAddToSet(Object[] chain, UnifiedSetWithHashingStrategy replace)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
replace.add(UnifiedMapWithHashingStrategy.this.nonSentinel(cur));
}
}
}
protected abstract class PositionalIterator implements Iterator
{
protected int count;
protected int position;
protected int chainPosition;
protected boolean lastReturned;
public boolean hasNext()
{
return this.count < UnifiedMapWithHashingStrategy.this.size();
}
public void remove()
{
if (!this.lastReturned)
{
throw new IllegalStateException("next() must be called as many times as remove()");
}
this.count--;
UnifiedMapWithHashingStrategy.this.occupied--;
if (this.chainPosition != 0)
{
this.removeFromChain();
return;
}
int pos = this.position - 2;
Object cur = UnifiedMapWithHashingStrategy.this.table[pos];
if (cur == CHAINED_KEY)
{
this.removeLastFromChain((Object[]) UnifiedMapWithHashingStrategy.this.table[pos + 1], pos);
return;
}
UnifiedMapWithHashingStrategy.this.table[pos] = null;
UnifiedMapWithHashingStrategy.this.table[pos + 1] = null;
this.position = pos;
this.lastReturned = false;
}
protected void removeFromChain()
{
Object[] chain = (Object[]) UnifiedMapWithHashingStrategy.this.table[this.position + 1];
int pos = this.chainPosition - 2;
int replacePos = this.chainPosition;
while (replacePos < chain.length - 2 && chain[replacePos + 2] != null)
{
replacePos += 2;
}
chain[pos] = chain[replacePos];
chain[pos + 1] = chain[replacePos + 1];
chain[replacePos] = null;
chain[replacePos + 1] = null;
this.chainPosition = pos;
this.lastReturned = false;
}
protected void removeLastFromChain(Object[] chain, int tableIndex)
{
int pos = chain.length - 2;
while (chain[pos] == null)
{
pos -= 2;
}
if (pos == 0)
{
UnifiedMapWithHashingStrategy.this.table[tableIndex] = null;
UnifiedMapWithHashingStrategy.this.table[tableIndex + 1] = null;
}
else
{
chain[pos] = null;
chain[pos + 1] = null;
}
this.lastReturned = false;
}
}
protected class KeySetIterator extends PositionalIterator
{
protected K nextFromChain()
{
Object[] chain = (Object[]) UnifiedMapWithHashingStrategy.this.table[this.position + 1];
Object cur = chain[this.chainPosition];
this.chainPosition += 2;
if (this.chainPosition >= chain.length
|| chain[this.chainPosition] == null)
{
this.chainPosition = 0;
this.position += 2;
}
this.lastReturned = true;
return UnifiedMapWithHashingStrategy.this.nonSentinel(cur);
}
public K next()
{
if (!this.hasNext())
{
throw new NoSuchElementException("next() called, but the iterator is exhausted");
}
this.count++;
Object[] table = UnifiedMapWithHashingStrategy.this.table;
if (this.chainPosition != 0)
{
return this.nextFromChain();
}
while (table[this.position] == null)
{
this.position += 2;
}
Object cur = table[this.position];
if (cur == CHAINED_KEY)
{
return this.nextFromChain();
}
this.position += 2;
this.lastReturned = true;
return UnifiedMapWithHashingStrategy.this.nonSentinel(cur);
}
}
private static boolean nullSafeEquals(Object value, Object other)
{
if (value == null)
{
if (other == null)
{
return true;
}
}
else if (other == value || value.equals(other))
{
return true;
}
return false;
}
protected class EntrySet implements Set>, Serializable, BatchIterable>
{
private static final long serialVersionUID = 1L;
private transient WeakReference> holder = new WeakReference>(UnifiedMapWithHashingStrategy.this);
public boolean add(Entry entry)
{
throw new UnsupportedOperationException("Cannot call add() on " + this.getClass().getSimpleName());
}
public boolean addAll(Collection extends Entry> collection)
{
throw new UnsupportedOperationException("Cannot call addAll() on " + this.getClass().getSimpleName());
}
public void clear()
{
UnifiedMapWithHashingStrategy.this.clear();
}
public boolean containsEntry(Entry, ?> entry)
{
return this.getEntry(entry) != null;
}
private Entry getEntry(Entry, ?> entry)
{
K key = (K) entry.getKey();
V value = (V) entry.getValue();
int index = UnifiedMapWithHashingStrategy.this.index(key);
Object cur = UnifiedMapWithHashingStrategy.this.table[index];
Object curValue = UnifiedMapWithHashingStrategy.this.table[index + 1];
if (cur == CHAINED_KEY)
{
return this.chainGetEntry((Object[]) curValue, key, value);
}
if (cur == null)
{
return null;
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(cur, key))
{
if (UnifiedMapWithHashingStrategy.nullSafeEquals(value, curValue))
{
return ImmutableEntry.of(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) curValue);
}
}
return null;
}
private Entry chainGetEntry(Object[] chain, K key, V value)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return null;
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(cur, key))
{
Object curValue = chain[i + 1];
if (UnifiedMapWithHashingStrategy.nullSafeEquals(value, curValue))
{
return ImmutableEntry.of(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) curValue);
}
}
}
return null;
}
public boolean contains(Object o)
{
return o instanceof Entry && this.containsEntry((Entry, ?>) o);
}
public boolean containsAll(Collection> collection)
{
for (Object obj : collection)
{
if (!this.contains(obj))
{
return false;
}
}
return true;
}
public boolean isEmpty()
{
return UnifiedMapWithHashingStrategy.this.isEmpty();
}
public Iterator> iterator()
{
return new EntrySetIterator(this.holder);
}
public boolean remove(Object e)
{
if (!(e instanceof Entry))
{
return false;
}
Entry, ?> entry = (Entry, ?>) e;
K key = (K) entry.getKey();
V value = (V) entry.getValue();
int index = UnifiedMapWithHashingStrategy.this.index(key);
Object cur = UnifiedMapWithHashingStrategy.this.table[index];
if (cur != null)
{
Object val = UnifiedMapWithHashingStrategy.this.table[index + 1];
if (cur == CHAINED_KEY)
{
return this.removeFromChain((Object[]) val, key, value, index);
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(cur, key) && UnifiedMapWithHashingStrategy.nullSafeEquals(value, val))
{
UnifiedMapWithHashingStrategy.this.table[index] = null;
UnifiedMapWithHashingStrategy.this.table[index + 1] = null;
UnifiedMapWithHashingStrategy.this.occupied--;
return true;
}
}
return false;
}
private boolean removeFromChain(Object[] chain, K key, V value, int index)
{
for (int i = 0; i < chain.length; i += 2)
{
Object k = chain[i];
if (k == null)
{
return false;
}
if (UnifiedMapWithHashingStrategy.this.nonNullTableObjectEquals(k, key))
{
V val = (V) chain[i + 1];
if (UnifiedMapWithHashingStrategy.nullSafeEquals(val, value))
{
UnifiedMapWithHashingStrategy.this.overwriteWithLastElementFromChain(chain, index, i);
return true;
}
}
}
return false;
}
public boolean removeAll(Collection> collection)
{
boolean changed = false;
for (Object obj : collection)
{
if (this.remove(obj))
{
changed = true;
}
}
return changed;
}
public boolean retainAll(Collection> collection)
{
int retainedSize = collection.size();
UnifiedMapWithHashingStrategy retainedCopy = new UnifiedMapWithHashingStrategy(
UnifiedMapWithHashingStrategy.this.hashingStrategy, retainedSize, UnifiedMapWithHashingStrategy.this.loadFactor);
for (Object obj : collection)
{
if (obj instanceof Entry)
{
Entry, ?> otherEntry = (Entry, ?>) obj;
Entry thisEntry = this.getEntry(otherEntry);
if (thisEntry != null)
{
retainedCopy.put(thisEntry.getKey(), thisEntry.getValue());
}
}
}
if (retainedCopy.size() < this.size())
{
UnifiedMapWithHashingStrategy.this.maxSize = retainedCopy.maxSize;
UnifiedMapWithHashingStrategy.this.occupied = retainedCopy.occupied;
UnifiedMapWithHashingStrategy.this.table = retainedCopy.table;
return true;
}
return false;
}
public int size()
{
return UnifiedMapWithHashingStrategy.this.size();
}
public void forEach(Procedure super Entry> procedure)
{
for (int i = 0; i < UnifiedMapWithHashingStrategy.this.table.length; i += 2)
{
Object cur = UnifiedMapWithHashingStrategy.this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachEntry((Object[]) UnifiedMapWithHashingStrategy.this.table[i + 1], procedure);
}
else if (cur != null)
{
procedure.value(ImmutableEntry.of(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) UnifiedMapWithHashingStrategy.this.table[i + 1]));
}
}
}
private void chainedForEachEntry(Object[] chain, Procedure super Entry> procedure)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
procedure.value(ImmutableEntry.of(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) chain[i + 1]));
}
}
public int getBatchCount(int batchSize)
{
return UnifiedMapWithHashingStrategy.this.getBatchCount(batchSize);
}
public void batchForEach(Procedure super Entry> procedure, int sectionIndex, int sectionCount)
{
Object[] map = UnifiedMapWithHashingStrategy.this.table;
int sectionSize = map.length / sectionCount;
int start = sectionIndex * sectionSize;
int end = sectionIndex == sectionCount - 1 ? map.length : start + sectionSize;
if (start % 2 != 0)
{
start++;
}
for (int i = start; i < end; i += 2)
{
Object cur = map[i];
if (cur == CHAINED_KEY)
{
this.chainedForEachEntry((Object[]) map[i + 1], procedure);
}
else if (cur != null)
{
procedure.value(ImmutableEntry.of(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) map[i + 1]));
}
}
}
protected void copyEntries(Object[] result)
{
Object[] table = UnifiedMapWithHashingStrategy.this.table;
int count = 0;
for (int i = 0; i < table.length; i += 2)
{
Object x = table[i];
if (x != null)
{
if (x == CHAINED_KEY)
{
Object[] chain = (Object[]) table[i + 1];
for (int j = 0; j < chain.length; j += 2)
{
Object cur = chain[j];
if (cur == null)
{
break;
}
result[count++] =
new WeakBoundEntry(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) chain[j + 1], this.holder,
UnifiedMapWithHashingStrategy.this.hashingStrategy);
}
}
else
{
result[count++] = new WeakBoundEntry(UnifiedMapWithHashingStrategy.this.nonSentinel(x), (V) table[i + 1], this.holder,
UnifiedMapWithHashingStrategy.this.hashingStrategy);
}
}
}
}
public Object[] toArray()
{
Object[] result = new Object[UnifiedMapWithHashingStrategy.this.size()];
this.copyEntries(result);
return result;
}
public T[] toArray(T[] result)
{
int size = UnifiedMapWithHashingStrategy.this.size();
if (result.length < size)
{
result = (T[]) Array.newInstance(result.getClass().getComponentType(), size);
}
this.copyEntries(result);
if (size < result.length)
{
result[size] = null;
}
return result;
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
this.holder = new WeakReference>(UnifiedMapWithHashingStrategy.this);
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Set)
{
Set> other = (Set>) obj;
if (other.size() == this.size())
{
return this.containsAll(other);
}
}
return false;
}
@Override
public int hashCode()
{
return UnifiedMapWithHashingStrategy.this.hashCode();
}
}
protected class EntrySetIterator extends PositionalIterator>
{
private final WeakReference> holder;
protected EntrySetIterator(WeakReference> holder)
{
this.holder = holder;
}
protected Entry nextFromChain()
{
Object[] chain = (Object[]) UnifiedMapWithHashingStrategy.this.table[this.position + 1];
Object cur = chain[this.chainPosition];
Object value = chain[this.chainPosition + 1];
this.chainPosition += 2;
if (this.chainPosition >= chain.length
|| chain[this.chainPosition] == null)
{
this.chainPosition = 0;
this.position += 2;
}
this.lastReturned = true;
return new WeakBoundEntry(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) value, this.holder,
UnifiedMapWithHashingStrategy.this.hashingStrategy);
}
public Entry next()
{
if (!this.hasNext())
{
throw new NoSuchElementException("next() called, but the iterator is exhausted");
}
this.count++;
Object[] table = UnifiedMapWithHashingStrategy.this.table;
if (this.chainPosition != 0)
{
return this.nextFromChain();
}
while (table[this.position] == null)
{
this.position += 2;
}
Object cur = table[this.position];
Object value = table[this.position + 1];
if (cur == CHAINED_KEY)
{
return this.nextFromChain();
}
this.position += 2;
this.lastReturned = true;
return new WeakBoundEntry(UnifiedMapWithHashingStrategy.this.nonSentinel(cur), (V) value, this.holder,
UnifiedMapWithHashingStrategy.this.hashingStrategy);
}
}
protected static class WeakBoundEntry implements Map.Entry
{
protected final K key;
protected V value;
protected final WeakReference> holder;
protected final HashingStrategy super K> hashingStrategy;
protected WeakBoundEntry(K key, V value, WeakReference> holder,
HashingStrategy super K> hashingStrategy)
{
this.key = key;
this.value = value;
this.holder = holder;
this.hashingStrategy = hashingStrategy;
}
public K getKey()
{
return this.key;
}
public V getValue()
{
return this.value;
}
public V setValue(V value)
{
this.value = value;
UnifiedMapWithHashingStrategy map = this.holder.get();
if (map != null && map.containsKey(this.key))
{
return map.put(this.key, value);
}
return null;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Entry)
{
Entry, ?> other = (Entry, ?>) obj;
K otherKey = (K) other.getKey();
V otherValue = (V) other.getValue();
return this.hashingStrategy.equals(this.key, otherKey)
&& UnifiedMapWithHashingStrategy.nullSafeEquals(this.value, otherValue);
}
return false;
}
@Override
public int hashCode()
{
return this.hashingStrategy.computeHashCode(this.key)
^ (this.value == null ? 0 : this.value.hashCode());
}
@Override
public String toString()
{
return this.key + "=" + this.value;
}
}
protected class ValuesCollection extends ValuesCollectionCommon
implements Serializable, BatchIterable
{
private static final long serialVersionUID = 1L;
public void clear()
{
UnifiedMapWithHashingStrategy.this.clear();
}
public boolean contains(Object o)
{
return UnifiedMapWithHashingStrategy.this.containsValue(o);
}
public boolean containsAll(Collection> collection)
{
// todo: this is N^2. if c is large, we should copy the values to a set.
return Iterate.allSatisfy(collection, Predicates.in(this));
}
public boolean isEmpty()
{
return UnifiedMapWithHashingStrategy.this.isEmpty();
}
public Iterator iterator()
{
return new ValuesIterator();
}
public boolean remove(Object o)
{
// this is so slow that the extra overhead of the iterator won't be noticeable
if (o == null)
{
for (Iterator it = this.iterator(); it.hasNext(); )
{
if (it.next() == null)
{
it.remove();
return true;
}
}
}
else
{
for (Iterator it = this.iterator(); it.hasNext(); )
{
V o2 = it.next();
if (o == o2 || o2.equals(o))
{
it.remove();
return true;
}
}
}
return false;
}
public boolean removeAll(Collection> collection)
{
// todo: this is N^2. if c is large, we should copy the values to a set.
boolean changed = false;
for (Object obj : collection)
{
if (this.remove(obj))
{
changed = true;
}
}
return changed;
}
public boolean retainAll(Collection> collection)
{
boolean modified = false;
Iterator e = this.iterator();
while (e.hasNext())
{
if (!collection.contains(e.next()))
{
e.remove();
modified = true;
}
}
return modified;
}
public int size()
{
return UnifiedMapWithHashingStrategy.this.size();
}
public void forEach(Procedure super V> procedure)
{
UnifiedMapWithHashingStrategy.this.forEachValue(procedure);
}
public int getBatchCount(int batchSize)
{
return UnifiedMapWithHashingStrategy.this.getBatchCount(batchSize);
}
public void batchForEach(Procedure super V> procedure, int sectionIndex, int sectionCount)
{
UnifiedMapWithHashingStrategy.this.batchForEach(procedure, sectionIndex, sectionCount);
}
protected void copyValues(Object[] result)
{
int count = 0;
for (int i = 0; i < UnifiedMapWithHashingStrategy.this.table.length; i += 2)
{
Object x = UnifiedMapWithHashingStrategy.this.table[i];
if (x != null)
{
if (x == CHAINED_KEY)
{
Object[] chain = (Object[]) UnifiedMapWithHashingStrategy.this.table[i + 1];
for (int j = 0; j < chain.length; j += 2)
{
Object cur = chain[j];
if (cur == null)
{
break;
}
result[count++] = chain[j + 1];
}
}
else
{
result[count++] = UnifiedMapWithHashingStrategy.this.table[i + 1];
}
}
}
}
public Object[] toArray()
{
int size = UnifiedMapWithHashingStrategy.this.size();
Object[] result = new Object[size];
this.copyValues(result);
return result;
}
public T[] toArray(T[] result)
{
int size = UnifiedMapWithHashingStrategy.this.size();
if (result.length < size)
{
result = (T[]) Array.newInstance(result.getClass().getComponentType(), size);
}
this.copyValues(result);
if (size < result.length)
{
result[size] = null;
}
return result;
}
protected Object writeReplace()
{
FastList replace = FastList.newList(UnifiedMapWithHashingStrategy.this.size());
for (int i = 0; i < UnifiedMapWithHashingStrategy.this.table.length; i += 2)
{
Object cur = UnifiedMapWithHashingStrategy.this.table[i];
if (cur == CHAINED_KEY)
{
this.chainedAddToList((Object[]) UnifiedMapWithHashingStrategy.this.table[i + 1], replace);
}
else if (cur != null)
{
replace.add((V) UnifiedMapWithHashingStrategy.this.table[i + 1]);
}
}
return replace;
}
private void chainedAddToList(Object[] chain, FastList replace)
{
for (int i = 0; i < chain.length; i += 2)
{
Object cur = chain[i];
if (cur == null)
{
return;
}
replace.add((V) chain[i + 1]);
}
}
@Override
public String toString()
{
return Iterate.makeString(this, "[", ", ", "]");
}
}
protected class ValuesIterator extends PositionalIterator
{
protected V nextFromChain()
{
Object[] chain = (Object[]) UnifiedMapWithHashingStrategy.this.table[this.position + 1];
V val = (V) chain[this.chainPosition + 1];
this.chainPosition += 2;
if (this.chainPosition >= chain.length
|| chain[this.chainPosition] == null)
{
this.chainPosition = 0;
this.position += 2;
}
this.lastReturned = true;
return val;
}
public V next()
{
if (!this.hasNext())
{
throw new NoSuchElementException("next() called, but the iterator is exhausted");
}
this.count++;
Object[] table = UnifiedMapWithHashingStrategy.this.table;
if (this.chainPosition != 0)
{
return this.nextFromChain();
}
while (table[this.position] == null)
{
this.position += 2;
}
Object cur = table[this.position];
Object val = table[this.position + 1];
if (cur == CHAINED_KEY)
{
return this.nextFromChain();
}
this.position += 2;
this.lastReturned = true;
return (V) val;
}
}
private K nonSentinel(Object key)
{
return key == NULL_KEY ? null : (K) key;
}
private static Object toSentinelIfNull(Object key)
{
if (key == null)
{
return NULL_KEY;
}
return key;
}
private boolean nonNullTableObjectEquals(Object cur, K key)
{
return cur == key || (cur == NULL_KEY ? key == null : this.hashingStrategy.equals(this.nonSentinel(cur), key));
}
@Override
public ImmutableMap toImmutable()
{
return HashingStrategyMaps.immutable.withAll(this);
}
}