From 4f98c599df42c1a61c6475f1d3b0aad986d6558c Mon Sep 17 00:00:00 2001 From: Speiger Date: Sun, 10 Apr 2022 15:49:16 +0200 Subject: [PATCH] New Features. -Added: ArrayList.of(CLASS, size) function that allows to preallocate the size of the generic list. -Updated: ListTests suppressors only suprres what they need to. -Added: Start of the ConcurrentHashMap implementation (based on Guavas implementation) Note that the ConcurrentHashMap implementation is just started not finished and still needs a lot of work, but the base code - any subclasses is technically finished. The implementation is also using linkedMaps to have faster iteration performance. --- build.gradle | 2 +- .../speiger/src/builder/GlobalVariables.java | 1 + .../builder/PrimitiveCollectionsBuilder.java | 4 +- .../templates/lists/ArrayList.template | 13 + .../concurrent/ConcurrentOpenHashMap.template | 870 ++++++++++++++++++ .../objects/list/ObjectListTests.java | 6 +- 6 files changed, 889 insertions(+), 7 deletions(-) create mode 100644 src/builder/resources/speiger/assets/collections/templates/maps/impl/concurrent/ConcurrentOpenHashMap.template diff --git a/build.gradle b/build.gradle index da5dc0cf..32bf3cfb 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ configurations { } dependencies { - builderImplementation 'de.speiger:Simple-Code-Generator:1.0.6' + builderImplementation 'de.speiger:Simple-Code-Generator:1.0.7' testImplementation 'junit:junit:4.12' testImplementation 'com.google.guava:guava-testlib:31.0.1-jre' } diff --git a/src/builder/java/speiger/src/builder/GlobalVariables.java b/src/builder/java/speiger/src/builder/GlobalVariables.java index 20dae590..cf6c1bb9 100644 --- a/src/builder/java/speiger/src/builder/GlobalVariables.java +++ b/src/builder/java/speiger/src/builder/GlobalVariables.java @@ -174,6 +174,7 @@ public class GlobalVariables addBiClassMapper("LINKED_CUSTOM_HASH_MAP", "LinkedOpenCustomHashMap", "2"); addBiClassMapper("LINKED_HASH_MAP", "LinkedOpenHashMap", "2"); addBiClassMapper("CUSTOM_HASH_MAP", "OpenCustomHashMap", "2"); + addBiClassMapper("CONCURRENT_MAP", "ConcurrentOpenHashMap", "2"); addBiClassMapper("AVL_TREE_MAP", "AVLTreeMap", "2"); addBiClassMapper("RB_TREE_MAP", "RBTreeMap", "2"); addFunctionValueMappers("LINKED_ENUM_MAP", valueType.isObject() ? "LinkedEnum2ObjectMap" : "LinkedEnum2%sMap"); diff --git a/src/builder/java/speiger/src/builder/PrimitiveCollectionsBuilder.java b/src/builder/java/speiger/src/builder/PrimitiveCollectionsBuilder.java index 4a8eac9d..1066531e 100644 --- a/src/builder/java/speiger/src/builder/PrimitiveCollectionsBuilder.java +++ b/src/builder/java/speiger/src/builder/PrimitiveCollectionsBuilder.java @@ -83,7 +83,7 @@ public class PrimitiveCollectionsBuilder extends TemplateProcessor biRequired.put("Pair", ""); biRequired.put("MutablePair", ""); biRequired.put("ImmutablePair", ""); - addBiClass("Function", "Maps", "Map", "SortedMap", "OrderedMap", "NavigableMap", "AbstractMap", "ImmutableOpenHashMap", "OpenHashMap", "LinkedOpenHashMap", "OpenCustomHashMap", "LinkedOpenCustomHashMap", "ArrayMap", "RBTreeMap", "AVLTreeMap"); + addBiClass("Function", "Maps", "Map", "SortedMap", "OrderedMap", "NavigableMap", "AbstractMap", "ConcurrentOpenHashMap", "ImmutableOpenHashMap", "OpenHashMap", "LinkedOpenHashMap", "OpenCustomHashMap", "LinkedOpenCustomHashMap", "ArrayMap", "RBTreeMap", "AVLTreeMap"); nameRemapper.put("BiConsumer", "%sConsumer"); nameRemapper.put("IArray", "I%sArray"); nameRemapper.put("AbstractMap", "Abstract%sMap"); @@ -100,7 +100,7 @@ public class PrimitiveCollectionsBuilder extends TemplateProcessor addBlockage(ClassType.OBJECT, "Consumer", "Comparator", "Stack"); addBlockage(ClassType.BOOLEAN, "ArraySet", "AVLTreeSet", "RBTreeSet", "SortedSet", "OrderedSet", "NavigableSet", "OpenHashSet", "OpenCustomHashSet", "LinkedOpenHashSet", "LinkedOpenCustomHashSet"); - addBlockage(ClassType.BOOLEAN, "ImmutableOpenHashMap", "ImmutableOpenHashSet", "SortedMap", "OrderedMap", "NavigableMap", "OpenHashMap", "LinkedOpenHashMap", "OpenCustomHashMap", "LinkedOpenCustomHashMap", "ArrayMap", "RBTreeMap", "AVLTreeMap"); + addBlockage(ClassType.BOOLEAN, "ConcurrentOpenHashMap", "ImmutableOpenHashMap", "ImmutableOpenHashSet", "SortedMap", "OrderedMap", "NavigableMap", "OpenHashMap", "LinkedOpenHashMap", "OpenCustomHashMap", "LinkedOpenCustomHashMap", "ArrayMap", "RBTreeMap", "AVLTreeMap"); } protected void create(ClassType mainType, ClassType subType) diff --git a/src/builder/resources/speiger/assets/collections/templates/lists/ArrayList.template b/src/builder/resources/speiger/assets/collections/templates/lists/ArrayList.template index 9d8aa8c6..80d3b3e5 100644 --- a/src/builder/resources/speiger/assets/collections/templates/lists/ArrayList.template +++ b/src/builder/resources/speiger/assets/collections/templates/lists/ArrayList.template @@ -187,6 +187,19 @@ public class ARRAY_LIST KEY_GENERIC_TYPE extends ABSTRACT_LIST KEY_GENERIC_TYPE list.data = (KEY_TYPE[])ObjectArrays.newArray(c, 0); return list; } + + /** + * Creates a new ArrayList with a EmptyObject array of the Type requested + * @param c the type of the array + * @param size the initial size of the backing array + * @Type(T) + * @return a typed List + */ + public static GENERIC_KEY_BRACES ARRAY_LIST KEY_GENERIC_TYPE of(Class c, int size) { + ARRAY_LIST KEY_GENERIC_TYPE list = new ARRAY_LISTBRACES(); + list.data = (KEY_TYPE[])ObjectArrays.newArray(c, size); + return list; + } #endif /** diff --git a/src/builder/resources/speiger/assets/collections/templates/maps/impl/concurrent/ConcurrentOpenHashMap.template b/src/builder/resources/speiger/assets/collections/templates/maps/impl/concurrent/ConcurrentOpenHashMap.template new file mode 100644 index 00000000..1b51ad11 --- /dev/null +++ b/src/builder/resources/speiger/assets/collections/templates/maps/impl/concurrent/ConcurrentOpenHashMap.template @@ -0,0 +1,870 @@ +package speiger.src.collections.PACKAGE.maps.impl.concurrent; + +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; + +import speiger.src.collections.VALUE_PACKAGE.functions.VALUE_SUPPLIER; +import speiger.src.collections.VALUE_PACKAGE.functions.function.VALUE_UNARY_OPERATOR; +import speiger.src.collections.PACKAGE.functions.consumer.BI_CONSUMER; +import speiger.src.collections.PACKAGE.functions.function.FUNCTION; +#if !SAME_TYPE +import speiger.src.collections.PACKAGE.functions.function.UNARY_OPERATOR; +#endif +import speiger.src.collections.PACKAGE.maps.abstracts.ABSTRACT_MAP; +import speiger.src.collections.PACKAGE.maps.interfaces.MAP; +import speiger.src.collections.objects.sets.ObjectSet; +import speiger.src.collections.utils.HashUtil; + +@SuppressWarnings("javadoc") +public class CONCURRENT_MAP KEY_VALUE_GENERIC_TYPE extends ABSTRACT_MAP KEY_VALUE_GENERIC_TYPE +{ + private static final int MAX_SEGMENTS = 1 << 16; + + protected transient Segment KEY_VALUE_GENERIC_TYPE[] segments; + protected transient int segmentShift; + protected transient int segmentMask; + + public CONCURRENT_MAP(int minCapacity, float loadFactor, int concurrencyLevel) { + if(minCapacity < 0) throw new IllegalStateException("Minimum Capacity is negative. This is not allowed"); + if(loadFactor <= 0 || loadFactor >= 1F) throw new IllegalStateException("Load Factor is not between 0 and 1"); + if(concurrencyLevel < 0 || concurrencyLevel >= MAX_SEGMENTS) throw new IllegalStateException("concurrencyLevel has to be between 0 and 65536"); + int segmentCount = HashUtil.nextPowerOfTwo(concurrencyLevel); + int shift = Integer.numberOfTrailingZeros(segmentCount); + segments = new Segment[segmentCount]; + segmentShift = 32 - shift; + segmentMask = segmentCount - 1; + int segmentCapacity = minCapacity / segmentCount; + if(segmentCapacity * segmentCount < minCapacity) { + segmentCapacity++; + } + segmentCapacity = HashUtil.arraySize(segmentCapacity, loadFactor); + for(int i = 0;i Integer.MAX_VALUE ? Integer.MAX_VALUE : 0; + } + + @Override + public ObjectSet ENTRY_SET() { + return null; + } + + protected Segment KEY_VALUE_GENERIC_TYPE getSegment(int hash) { + return segments[(hash >>> segmentShift) & segmentMask]; + } + +#if !TYPE_OBJECT + protected int getHashCode(KEY_TYPE key) { + return HashUtil.mix(KEY_TO_HASH(key)); + } + +#endif + protected int getHashCode(Object obj) { + return HashUtil.mix(Objects.hashCode(obj)); + } + + protected static class Segment KEY_VALUE_GENERIC_TYPE extends ReentrantLock + { + private static final long serialVersionUID = -446894977795760975L; + protected final CONCURRENT_MAP KEY_VALUE_GENERIC_TYPE map; + /** The Backing keys array */ + protected transient KEY_TYPE[] keys; + /** The Backing values array */ + protected transient VALUE_TYPE[] values; + /** The Backing array for links between nodes. Left 32 Bits => Previous Entry, Right 32 Bits => Next Entry */ + protected transient long[] links; + /** The First Index in the Map */ + protected int firstIndex = -1; + /** The Last Index in the Map */ + protected int lastIndex = -1; + /** If a null value is present */ + protected transient boolean containsNull; + /** Index of the Null Value */ + protected transient int nullIndex; + /** Maximum amount of Values that can be stored before the array gets expanded usually 75% */ + protected transient int maxFill; + /** Max Index that is allowed to be searched through nullIndex - 1 */ + protected transient int mask; + /** Amount of Elements stored in the HashMap */ + protected int size; + /** Minimum array size the Segment will be */ + protected transient int minCapacity; + /** How full the Arrays are allowed to get before resize */ + protected float loadFactor; + + protected Segment(CONCURRENT_MAP KEY_VALUE_GENERIC_TYPE map, int minCapacity, float loadFactor, boolean isNullContainer) { + this.map = map; + this.minCapacity = minCapacity; + this.loadFactor = loadFactor; + mask = minCapacity - 1; + maxFill = Math.min((int)Math.ceil(minCapacity * loadFactor), minCapacity - 1); + nullIndex = isNullContainer ? minCapacity : -1; + int arraySize = minCapacity + (isNullContainer ? 1 : 0); + keys = NEW_KEY_ARRAY(arraySize); + values = NEW_VALUE_ARRAY(arraySize); + links = new long[arraySize]; + } + + protected VALUE_TYPE getDefaultReturnValue() { + return map.getDefaultReturnValue(); + } + + protected VALUE_TYPE put(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) { + insert(-slot-1, key, value); + return getDefaultReturnValue(); + } + VALUE_TYPE oldValue = values[slot]; + values[slot] = value; + return oldValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE putIfAbsent(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) { + insert(-slot-1, key, value); + return getDefaultReturnValue(); + } + return values[slot]; + } + finally { + unlock(); + } + } + +#if VALUE_PRIMITIVES + protected VALUE_TYPE addTo(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) { + insert(-slot-1, key, value); + return getDefaultReturnValue(); + } + VALUE_TYPE oldValue = values[slot]; + values[slot] += value; + return oldValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE subFrom(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) return getDefaultReturnValue(); + VALUE_TYPE oldValue = values[slot]; + values[slot] -= value; + if(value < 0 ? (values[slot] >= getDefaultReturnValue()) : (values[slot] <= getDefaultReturnValue())) removeIndex(slot); + return oldValue; + } + finally { + unlock(); + } + } +#endif + +#if !TYPE_OBJECT + protected boolean containsKey(int hash, KEY_TYPE key) { + return findIndex(hash, key) >= 0; + } + +#endif + @Deprecated + protected boolean containsKey(int hash, Object key) { + return findIndex(hash, key) >= 0; + } + +#if !VALUE_OBJECT + protected boolean containsValue(VALUE_TYPE value) { + int index = firstIndex; + while(index != -1) { + if(VALUE_EQUALS(values[index], value)) return true; + index = (int)links[index]; + } + return false; + } + +#endif + @Deprecated + protected boolean containsValue(Object value) { + int index = firstIndex; + while(index != -1) { +#if VALUE_OBJECT + if(VALUE_EQUALS(values[index], value)) return true; +#else + if((value == null && values[index] == getDefaultReturnValue()) || EQUALS_VALUE_TYPE(values[index], value)) return true; +#endif + index = (int)links[index]; + } + return false; + } + +#if !TYPE_OBJECT + protected VALUE_TYPE get(int hash, KEY_TYPE key) { + int slot = findIndex(hash, key); + return slot < 0 ? getDefaultReturnValue() : values[slot]; + } + +#endif + protected VALUE_TYPE get(int hash, Object key) { + int slot = findIndex(hash, key); + return slot < 0 ? getDefaultReturnValue() : values[slot]; + } + +#if TYPE_OBJECT && VALUE_OBJECT + public VALUE_TYPE getOrDefault(int hash, Object key, VALUE_TYPE defaultValue) { + int slot = findIndex(hash, key); + return slot < 0 ? defaultValue : values[slot]; + } + +#else + protected VALUE_TYPE getOrDefault(int hash, KEY_TYPE key, VALUE_TYPE defaultValue) { + int slot = findIndex(hash, key); + return slot < 0 ? defaultValue : values[slot]; + } + +#endif + protected void forEach(BI_CONSUMER KEY_VALUE_GENERIC_TYPE action) { + int index = firstIndex; + while(index != -1) { + action.accept(keys[index], values[index]); + index = (int)links[index]; + } + } + +#if !TYPE_OBJECT + protected VALUE_TYPE remove(int hash, KEY_TYPE key) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) return getDefaultReturnValue(); + return removeIndex(slot); + } + finally { + unlock(); + } + } + +#endif + protected VALUE_TYPE removeOrDefault(int hash, KEY_TYPE key, VALUE_TYPE defaultValue) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) return defaultValue; + return removeIndex(slot); + } + finally { + unlock(); + } + } + + protected CLASS_VALUE_TYPE remove(int hash, Object key) { + lock(); + try { + int slot = findIndex(hash, key); + if(slot < 0) return VALUE_TO_OBJ(getDefaultReturnValue()); + return VALUE_TO_OBJ(removeIndex(slot)); + } + finally { + unlock(); + } + } + +#if !TYPE_OBJECT || !VALUE_OBJECT + protected boolean remove(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + if(KEY_EQUALS_NULL(key)) { + if(containsNull && VALUE_EQUALS(value, values[nullIndex])) { + removeNullIndex(); + return true; + } + return false; + } + int pos = hash & mask; + KEY_TYPE current = keys[pos]; + if(KEY_EQUALS_NULL(current)) return false; + if(KEY_EQUALS(current, key) && VALUE_EQUALS(value, values[pos])) { + removeIndex(pos); + return true; + } + while(true) { + if(KEY_EQUALS_NULL((current = keys[pos = (++pos & mask)]))) return false; + else if(KEY_EQUALS(current, key) && VALUE_EQUALS(value, values[pos])) { + removeIndex(pos); + return true; + } + } + } + finally { + unlock(); + } + } + +#endif + protected boolean remove(int hash, Object key, Object value) { + if(key == null) { + if(containsNull && EQUALS_VALUE_TYPE(values[nullIndex], value)) { + removeNullIndex(); + return true; + } + return false; + } + int pos = hash & mask; + KEY_TYPE current = keys[pos]; + if(KEY_EQUALS_NULL(current)) return false; + if(EQUALS_KEY_TYPE(current, key) && EQUALS_VALUE_TYPE(values[pos], value)) { + removeIndex(pos); + return true; + } + while(true) { + if(KEY_EQUALS_NULL((current = keys[pos = (++pos & mask)]))) return false; + else if(EQUALS_KEY_TYPE(current, key) && EQUALS_VALUE_TYPE(values[pos], value)){ + removeIndex(pos); + return true; + } + } + } + + protected boolean replace(int hash, KEY_TYPE key, VALUE_TYPE oldValue, VALUE_TYPE newValue) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0 || values[index] != oldValue) return false; + values[index] = newValue; + return true; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE replace(int hash, KEY_TYPE key, VALUE_TYPE value) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0) return getDefaultReturnValue(); + VALUE_TYPE oldValue = values[index]; + values[index] = value; + return oldValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE compute(int hash, KEY_TYPE key, UNARY_OPERATOR KEY_VALUE_GENERIC_TYPE mappingFunction) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0) { + VALUE_TYPE newValue = mappingFunction.APPLY_VALUE(key, getDefaultReturnValue()); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) return newValue; + insert(-index-1, key, newValue); + return newValue; + } + VALUE_TYPE newValue = mappingFunction.APPLY_VALUE(key, values[index]); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) { + removeIndex(index); + return newValue; + } + values[index] = newValue; + return newValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE computeIfAbsent(int hash, KEY_TYPE key, FUNCTION KEY_VALUE_GENERIC_TYPE mappingFunction) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0) { + VALUE_TYPE newValue = mappingFunction.GET_VALUE(key); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) return newValue; + insert(-index-1, key, newValue); + return newValue; + } + VALUE_TYPE newValue = values[index]; + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) { + newValue = mappingFunction.GET_VALUE(key); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) return newValue; + values[index] = newValue; + } + return newValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE supplyIfAbsent(int hash, KEY_TYPE key, VALUE_SUPPLIER VALUE_GENERIC_TYPE valueProvider) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0) { + VALUE_TYPE newValue = valueProvider.VALUE_GET_KEY(); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) return newValue; + insert(-index-1, key, newValue); + return newValue; + } + VALUE_TYPE newValue = values[index]; + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) { + newValue = valueProvider.VALUE_GET_KEY(); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) return newValue; + values[index] = newValue; + } + return newValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE computeIfPresent(int hash, KEY_TYPE key, UNARY_OPERATOR KEY_VALUE_GENERIC_TYPE mappingFunction) { + lock(); + try { + int index = findIndex(hash, key); + if(index < 0 || VALUE_EQUALS(values[index], getDefaultReturnValue())) return getDefaultReturnValue(); + VALUE_TYPE newValue = mappingFunction.APPLY_VALUE(key, values[index]); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) { + removeIndex(index); + return newValue; + } + values[index] = newValue; + return newValue; + } + finally { + unlock(); + } + } + + protected VALUE_TYPE merge(int hash, KEY_TYPE key, VALUE_TYPE value, VALUE_UNARY_OPERATOR VALUE_VALUE_GENERIC_TYPE mappingFunction) { + lock(); + try { + int index = findIndex(hash, key); + VALUE_TYPE newValue = index < 0 || VALUE_EQUALS(values[index], getDefaultReturnValue()) ? value : mappingFunction.APPLY_VALUE(values[index], value); + if(VALUE_EQUALS(newValue, getDefaultReturnValue())) { + if(index >= 0) + removeIndex(index); + } + else if(index < 0) insert(-index-1, key, newValue); + else values[index] = newValue; + return newValue; + } + finally { + unlock(); + } + } + + protected void clear() { + if(size == 0) return; + lock(); + try { + size = 0; + containsNull = false; + Arrays.fill(keys, EMPTY_KEY_VALUE); + Arrays.fill(values, EMPTY_VALUE); + } + finally { + unlock(); + } + } + + protected boolean trim(int size) { + int request = Math.max(minCapacity, HashUtil.nextPowerOfTwo((int)Math.ceil(size / loadFactor))); + if(request >= size || this.size > Math.min((int)Math.ceil(request * loadFactor), request - 1)) return false; + lock(); + try { + try { + rehash(request); + } + catch(OutOfMemoryError noMemory) { return false; } + return true; + } + finally { + unlock(); + } + } + + protected void clearAndTrim(int size) { + int request = Math.max(minCapacity, HashUtil.nextPowerOfTwo((int)Math.ceil(size / loadFactor))); + if(request >= size) { + clear(); + return; + } + lock(); + try { + if(nullIndex != -1) { + nullIndex = request; + } + mask = request-1; + maxFill = Math.min((int)Math.ceil(request * loadFactor), request - 1); + int arraySize = request + (nullIndex != -1 ? 1 : 0); + keys = NEW_KEY_ARRAY(arraySize); + values = NEW_VALUE_ARRAY(arraySize); + links = new long[arraySize]; + this.size = 0; + containsNull = false; + } + finally { + unlock(); + } + } + + protected void insert(int slot, KEY_TYPE key, VALUE_TYPE value) { + if(slot == nullIndex) containsNull = true; + keys[slot] = key; + values[slot] = value; + if(size == 0) { + firstIndex = lastIndex = slot; + links[slot] = -1L; + } + else { + links[lastIndex] ^= ((links[lastIndex] ^ (slot & 0xFFFFFFFFL)) & 0xFFFFFFFFL); + links[slot] = ((lastIndex & 0xFFFFFFFFL) << 32) | 0xFFFFFFFFL; + lastIndex = slot; + } + if(size++ >= maxFill) rehash(HashUtil.arraySize(size+1, loadFactor)); + } + + protected VALUE_TYPE removeIndex(int pos) { + if(pos == nullIndex) return containsNull ? removeNullIndex() : getDefaultReturnValue(); + VALUE_TYPE value = values[pos]; + keys[pos] = EMPTY_KEY_VALUE; + values[pos] = EMPTY_VALUE; + size--; + onNodeRemoved(pos); + shiftKeys(pos); + if(nullIndex > minCapacity && size < maxFill / 4 && nullIndex > HashUtil.DEFAULT_MIN_CAPACITY) rehash(nullIndex / 2); + return value; + } + + protected VALUE_TYPE removeNullIndex() { + VALUE_TYPE value = values[nullIndex]; + containsNull = false; + keys[nullIndex] = EMPTY_KEY_VALUE; + values[nullIndex] = EMPTY_VALUE; + size--; + onNodeRemoved(nullIndex); + if(nullIndex > minCapacity && size < maxFill / 4 && nullIndex > HashUtil.DEFAULT_MIN_CAPACITY) rehash(nullIndex / 2); + return value; + } + +#if !TYPE_OBJECT + protected int findIndex(int hash, KEY_TYPE key) { + if(KEY_EQUALS_NULL(key)) return containsNull ? nullIndex : -(nullIndex + 1); + int pos = hash & mask; + KEY_TYPE current = keys[pos]; + if(KEY_EQUALS_NOT_NULL(current)) { + if(KEY_EQUALS(current, key)) return pos; + while(KEY_EQUALS_NOT_NULL((current = keys[pos = (++pos & mask)]))) + if(KEY_EQUALS(current, key)) return pos; + } + return -(pos + 1); + } + +#endif + protected int findIndex(int hash, Object key) { + if(key == null) return containsNull ? nullIndex : -(nullIndex + 1); + int pos = hash & mask; + KEY_TYPE current = keys[pos]; + if(KEY_EQUALS_NOT_NULL(current)) { + if(EQUALS_KEY_TYPE(current, key)) return pos; + while(KEY_EQUALS_NOT_NULL((current = keys[pos = (++pos & mask)]))) + if(EQUALS_KEY_TYPE(current, key)) return pos; + } + return -(pos + 1); + } + + protected void shiftKeys(int startPos) { + int slot, last; + KEY_TYPE current; + while(true) { + startPos = ((last = startPos) + 1) & mask; + while(true){ + if(KEY_EQUALS_NULL((current = keys[startPos]))) { + keys[last] = EMPTY_KEY_VALUE; + values[last] = EMPTY_VALUE; + return; + } + slot = HashUtil.mix(KEY_TO_HASH(current)) & mask; + if(last <= startPos ? (last >= slot || slot > startPos) : (last >= slot && slot > startPos)) break; + startPos = ++startPos & mask; + } + keys[last] = current; + values[last] = values[startPos]; + onNodeMoved(startPos, last); + } + } + + protected void rehash(int newSize) { + int newMask = newSize - 1; + int arraySize = newSize + (nullIndex != -1 ? 1 : 0); + KEY_TYPE[] newKeys = NEW_KEY_ARRAY(arraySize); + VALUE_TYPE[] newValues = NEW_VALUE_ARRAY(arraySize); + long[] newLinks = new long[arraySize]; + int i = firstIndex, prev = -1, newPrev = -1, pos; + firstIndex = -1; + for(int j = size; j-- != 0;) { + if(KEY_EQUALS_NULL(keys[i])) pos = newSize; + else { + pos = HashUtil.mix(KEY_TO_HASH(keys[i])) & newMask; + while(KEY_EQUALS_NOT_NULL(newKeys[pos])) pos = ++pos & newMask; + } + newKeys[pos] = keys[i]; + newValues[pos] = values[i]; + if(prev != -1) { + newLinks[newPrev] ^= ((newLinks[newPrev] ^ (pos & 0xFFFFFFFFL)) & 0xFFFFFFFFL); + newLinks[pos] ^= ((newLinks[pos] ^ ((newPrev & 0xFFFFFFFFL) << 32)) & 0xFFFFFFFF00000000L); + newPrev = pos; + } + else { + newPrev = firstIndex = pos; + newLinks[pos] = -1L; + } + i = (int)links[prev = i]; + } + links = newLinks; + lastIndex = newPrev; + if(newPrev != -1) newLinks[newPrev] |= 0xFFFFFFFFL; + if(nullIndex != -1) { + nullIndex = newSize; + } + mask = newMask; + maxFill = Math.min((int)Math.ceil(newSize * loadFactor), newSize - 1); + keys = newKeys; + values = newValues; + } + + protected void onNodeRemoved(int pos) { + if(size == 0) firstIndex = lastIndex = -1; + else if(firstIndex == pos) { + firstIndex = (int)links[pos]; + if(0 <= firstIndex) links[firstIndex] |= 0xFFFFFFFF00000000L; + } + else if(lastIndex == pos) { + lastIndex = (int)(links[pos] >>> 32); + if(0 <= lastIndex) links[lastIndex] |= 0xFFFFFFFFL; + } + else { + long link = links[pos]; + int prev = (int)(link >>> 32); + int next = (int)link; + links[prev] ^= ((links[prev] ^ (link & 0xFFFFFFFFL)) & 0xFFFFFFFFL); + links[next] ^= ((links[next] ^ (link & 0xFFFFFFFF00000000L)) & 0xFFFFFFFF00000000L); + } + } + + protected void onNodeMoved(int from, int to) { + if(size == 1) { + firstIndex = lastIndex = to; + links[to] = -1L; + } + else if(firstIndex == from) { + firstIndex = to; + links[(int)links[from]] ^= ((links[(int)links[from]] ^ ((to & 0xFFFFFFFFL) << 32)) & 0xFFFFFFFF00000000L); + links[to] = links[from]; + } + else if(lastIndex == from) { + lastIndex = to; + links[(int)(links[from] >>> 32)] ^= ((links[(int)(links[from] >>> 32)] ^ (to & 0xFFFFFFFFL)) & 0xFFFFFFFFL); + links[to] = links[from]; + } + else { + long link = links[from]; + int prev = (int)(link >>> 32); + int next = (int)link; + links[prev] ^= ((links[prev] ^ (to & 0xFFFFFFFFL)) & 0xFFFFFFFFL); + links[next] ^= ((links[next] ^ ((to & 0xFFFFFFFFL) << 32)) & 0xFFFFFFFF00000000L); + links[to] = link; + } + } + } +} diff --git a/src/test/java/speiger/src/collections/objects/list/ObjectListTests.java b/src/test/java/speiger/src/collections/objects/list/ObjectListTests.java index eaf57e3e..0aa8ea1c 100644 --- a/src/test/java/speiger/src/collections/objects/list/ObjectListTests.java +++ b/src/test/java/speiger/src/collections/objects/list/ObjectListTests.java @@ -11,7 +11,6 @@ import com.google.common.collect.testing.TestStringListGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; -import com.google.common.collect.testing.testers.CollectionSpliteratorTester; import com.google.common.collect.testing.testers.ListListIteratorTester; import com.google.common.collect.testing.testers.ListSubListTester; @@ -78,8 +77,7 @@ public class ObjectListTests extends TestCase }).named(name).withFeatures(CollectionFeature.ALLOWS_NULL_VALUES, CollectionSize.ANY).createTestSuite(); } - public static Collection suppressForCopyOnWriteArrayList() - { - return Arrays.asList(ListSubListTester.getSubListOriginalListSetAffectsSubListMethod(), ListSubListTester.getSubListOriginalListSetAffectsSubListLargeListMethod(), ListSubListTester.getSubListSubListRemoveAffectsOriginalLargeListMethod(), ListListIteratorTester.getListIteratorFullyModifiableMethod(), CollectionSpliteratorTester.getSpliteratorNotImmutableCollectionAllowsAddMethod(), CollectionSpliteratorTester.getSpliteratorNotImmutableCollectionAllowsRemoveMethod()); + public static Collection suppressForCopyOnWriteArrayList() { + return Arrays.asList(ListSubListTester.getSubListSubListRemoveAffectsOriginalLargeListMethod(), ListListIteratorTester.getListIteratorFullyModifiableMethod()); } }