
com.hazelcast.query.impl.bitmap.SparseIntArray Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* 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.hazelcast.query.impl.bitmap;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.capacityDeltaInt;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.capacityDeltaShort;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.denseCapacityDeltaInt;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.denseCapacityDeltaShort;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.toUnsignedInt;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.toUnsignedLong;
import static com.hazelcast.query.impl.bitmap.BitmapUtils.unsignedBinarySearch;
import static java.lang.System.arraycopy;
import static java.util.Arrays.copyOf;
/**
* Stores values of type {@code E} indexable by {@code int} indexes interpreted
* as unsigned.
*
* Internally, uses top-level storage ({@link Storage32 Storage32}) which goes
* in two flavors:
*
* - {@link ArrayStorage32 ArrayStorage32} which manages arrays of index-value
* pairs and can operate in two modes: dense mode with directly indexable values
* array and sparse mode with sorted indexes and values arrays.
*
- {@link PrefixStorage32 PrefixStorage32} which splits 32-bit indexes
* into 16-bit prefixes and 16-bit postfixes. The high 16 bits are stored in
* sorted array and used to lookup a storage ({@link Storage16 Storage16}) for
* the low 16 bits.
*
*
* {@link Storage16 Storage16} also manages arrays of index-value pairs and can
* operate in two modes: dense and sparse.
*
* The implementation switches between various storage flavors once certain
* thresholds on storage size are reached.
*
* Empty storages are never stored by the implementation.
*/
class SparseIntArray {
/**
* The size at which ArrayStorage32 is converted to PrefixStorage32.
* Inferred empirically: at this size we are not penalized too much for
* doing binary searches.
*/
public static final int ARRAY_STORAGE_32_MAX_SPARSE_SIZE = 513;
/**
* Sets a limit on maximum dense ArrayStorage32 size to avoid stressing GC
* too much.
*/
private static final int ARRAY_STORAGE_32_MAX_DENSE_SIZE = 262145;
private static final int STORAGE_16_MAX_DENSE_SIZE = 64 * 1024;
private static final int STORAGE_16_MAX_SPARSE_SIZE = 64 * 1024;
private static final long SHORT_PREFIX_MASK_LONG = 0xFFFF0000L;
private static final int SHORT_PREFIX_MASK_INT = 0xFFFF0000;
private Storage32 storage = new ArrayStorage32();
/**
* Iterates over values of a sparse array in ascending index order.
*/
public static class Iterator {
/**
* Identifies an iterator end.
*/
public static final long END = -1;
// the current position of Storage32
private int position32;
// the current Storage16
private Storage16 storage16;
// its position
private int position16;
private T value;
/**
* Returns a value this iterator is currently at. If this iterator is
* already reached its end, the return value is undefined.
*
* Just after the creation, iterators are positioned at their first value.
*/
public final T getValue() {
return value;
}
}
/**
* @return the value stored inside this sparse array at the given index or
* {@code null} if nothing stored at it.
*/
@SuppressWarnings("unchecked")
public E get(int index) {
return (E) storage.get(index);
}
/**
* Sets or replaces a value at the given index in this sparse array to the
* new given value.
*
* @param index the index to set value at.
* @param value the value to set.
*/
public void set(int index, E value) {
assert value != null;
Storage32 newStorage = storage.set(index, value);
if (newStorage != storage) {
storage = newStorage;
}
}
/**
* Clears a value stored at the given index in this sparse array.
*
* @param index the index to clear a value at.
* @return {@code true} if this sparse array is emptied, {@code false} if
* this sparse array still contains some values.
*/
public boolean clear(int index) {
Storage32 newStorage = storage.clear(index);
if (newStorage == null) {
return true;
}
if (newStorage != storage) {
storage = newStorage;
}
return false;
}
/**
* Clears this sparse array entirely.
*/
public void clear() {
storage = new ArrayStorage32();
}
/**
* Starts iteration on this sparse array using the given iterator.
*
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at or {@link
* Iterator#END END} if this sparse array is empty.
*/
public long iterate(Iterator iterator) {
return storage.iterate(iterator);
}
/**
* Advances the given iterator on this sparse array.
*
* @param iterator the iterator to advance.
* @return an index at which this iterator was positioned before the
* advancement or {@link Iterator#END END} if this iterator already was at
* its end.
*/
public long advance(int current, Iterator iterator) {
return storage.advance(current, iterator);
}
/**
* Starts iteration on this sparse array starting at least from the given
* index using the given iterator.
*
* @param index the index to start the iteration from.
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at; or {@link
* Iterator#END END} if no index greater than or equal to the given index
* exists in this array.
*/
public long iterateAtLeastFrom(int index, Iterator iterator) {
return storage.iterateAtLeastFrom(index, iterator);
}
/**
* Advances the given iterator to the given index; or, if the index is
* not present in this sparse array, to an index immediately following it
* and present in this array.
*
* @param index the index to advance at least to. The index must be
* greater than the index this iterator is currently at.
* @param iterator the iterator to advance.
* @return an index at which this iterator was advanced to or {@link
* Iterator#END END} if this iterator reached its end.
*/
public long advanceAtLeastTo(int index, int current, Iterator iterator) {
return storage.advanceAtLeastTo(index, current, iterator);
}
/**
* Defines internal contract of storages responsible for working on full
* 32-bit indexes.
*/
private interface Storage32 {
/**
* Sets or replaces a value at the given index to the new given value.
*
* @param index the index to set value at.
* @param value the value to set.
* @return a new storage instance if this storage was converted to
* another storage flavor; this storage otherwise.
*/
Storage32 set(int index, Object value);
/**
* Clears a value stored at the given index in this storage.
*
* @param index the index to clear a value at.
* @return {@code null} if this storage array is emptied, a new storage
* instance if this storage was converted to another storage flavor;
* this storage otherwise.
*/
Storage32 clear(int index);
/**
* @return the value stored inside this storage at the given index or
* {@code null} if nothing stored at it.
*/
Object get(int index);
/**
* Starts iteration on this storage using the given iterator.
*
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at or {@link
* Iterator#END END} if this storage is empty.
*/
long iterate(Iterator iterator);
/**
* Advances the given iterator on this storage.
*
* @param iterator the iterator to advance.
* @return an index at which this iterator was positioned before the
* advancement or {@link Iterator#END END} if this iterator already was
* at its end.
*/
long advance(int current, Iterator iterator);
/**
* Starts iteration on this storage starting at least from the given
* index using the given iterator.
*
* @param index the index to start the iteration from.
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at; or {@link
* Iterator#END END} if no index greater than or equal to the given
* index exists in this storage.
*/
long iterateAtLeastFrom(int index, Iterator iterator);
/**
* Advances the given iterator to the given index; or, if the index is
* not present in this storage, to an index immediately following it
* and present in this storage.
*
* @param index the index to advance at least to. The index must be
* greater than the index this iterator is currently at.
* @param iterator the iterator to advance.
* @return an index at which thee iterator was advanced to or {@link
* Iterator#END END} if the iterator reached its end.
*/
long advanceAtLeastTo(int index, int current, Iterator iterator);
}
/**
* Manages ordered arrays of index-value pairs for 32-bit indexes.
*
* Works in two modes:
*
* - Dense mode with directly indexable values array.
*
- Sparse mode with sorted indexes and values arrays.
*
*/
private static final class ArrayStorage32 implements Storage32 {
private static final int MIN_CAPACITY = 1;
private int size;
private int[] indexes;
private Object[] values = new Object[MIN_CAPACITY];
@Override
public Storage32 set(int index, Object value) {
if (indexes == null) {
return setDense(index, value);
} else {
return setSparse(index, value);
}
}
@SuppressWarnings("checkstyle:npathcomplexity")
private Storage32 setDense(int index, Object value) {
long unsignedIndex = toUnsignedLong(index);
if (unsignedIndex < values.length) {
// The index is inside the bounds: just set the corresponding
// array slot.
if (values[index] == null) {
++size;
}
values[index] = value;
return this;
}
// We need to grow the array.
int delta = denseCapacityDeltaInt(size, values.length);
int newCapacity = Math.min(ARRAY_STORAGE_32_MAX_DENSE_SIZE, size + delta);
if (unsignedIndex < newCapacity) {
// We are good: just grow the array and store the value.
values = copyOf(values, newCapacity);
values[index] = value;
++size;
return this;
}
// We would be wasting too much space on dense representation:
// convert to sparse representation.
if (size >= ARRAY_STORAGE_32_MAX_SPARSE_SIZE) {
// Too big: convert to prefix storage.
return new PrefixStorage32(values, size, index, value);
}
Object[] oldValues = values;
if (size == values.length) {
// No space left: reallocate the values and allocate the
// indexes array.
indexes = new int[newCapacity];
values = new Object[newCapacity];
} else {
// Some space left, its size is bellow the sparse
// representation threshold (we adjusted for it in
// denseCapacityDeltaInt call): reuse the values array and
// allocate indexes array.
indexes = new int[values.length];
}
// Iterate dense records one-by-one and represent them as sparse
// ones.
int count = 0;
for (int i = 0; i < values.length; ++i) {
Object storedValue = oldValues[i];
if (storedValue != null) {
indexes[count] = i;
values[count] = storedValue;
++count;
if (count == size) {
// Everything converted: stop.
break;
}
}
}
// Store the original value being inserted and grow the size.
indexes[size] = index;
values[size] = value;
++size;
return this;
}
private Storage32 setSparse(int index, Object value) {
assert size > 0;
long unsignedIndex = toUnsignedLong(index);
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position >= 0) {
// Already there: just overwrite it.
values[position] = value;
return this;
}
position = -(position + 1);
if (size == indexes.length) {
// We are full: either grow the array or try to convert to
// dense representation.
int delta = capacityDeltaInt(indexes.length);
long lastIndex = toUnsignedLong(indexes[indexes.length - 1]);
long denseCapacity = Math.max(unsignedIndex, lastIndex) + 1;
if (denseCapacity <= size + delta) {
// The wasted space is bellow the threshold: convert to
// dense representation.
Object[] newValues = new Object[(int) denseCapacity];
for (int i = 0; i < indexes.length; ++i) {
newValues[indexes[i]] = values[i];
}
newValues[index] = value;
indexes = null;
values = newValues;
++size;
return this;
}
// We were unable to convert to dense representation.
if (size >= ARRAY_STORAGE_32_MAX_SPARSE_SIZE) {
// Too big: convert to prefix storage.
return new PrefixStorage32(indexes, values, position, index, value);
}
// Reallocate the arrays to gain the space for the new value.
int newCapacity = Math.min(ARRAY_STORAGE_32_MAX_SPARSE_SIZE, size + delta);
int[] newIndexes = new int[newCapacity];
arraycopy(indexes, 0, newIndexes, 0, position);
arraycopy(indexes, position, newIndexes, position + 1, size - position);
indexes = newIndexes;
Object[] newValues = new Object[newCapacity];
arraycopy(values, 0, newValues, 0, position);
arraycopy(values, position, newValues, position + 1, size - position);
values = newValues;
} else {
// There is some space left: just shift the values to the right.
arraycopy(indexes, position, indexes, position + 1, size - position);
arraycopy(values, position, values, position + 1, size - position);
}
// Finally insert the value.
indexes[position] = index;
values[position] = value;
++size;
return this;
}
@Override
public Storage32 clear(int index) {
if (indexes == null) {
return clearDense(index);
} else {
return clearSparse(index);
}
}
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
private Storage32 clearDense(int index) {
long unsignedIndex = toUnsignedLong(index);
if (unsignedIndex >= values.length) {
// Out of bounds.
return this;
}
if (values[index] == null) {
// No such record.
return this;
}
values[index] = null;
--size;
if (size == 0) {
// Emptied: just report the storage is empty by returning null.
return null;
}
int delta = capacityDeltaInt(values.length);
int wasted = values.length - size;
if (wasted < delta) {
return this;
}
int newCapacity = values.length - delta;
if (newCapacity < MIN_CAPACITY) {
// never reached with the default MIN_CAPACITY of 1
return this;
}
assert wasted == delta;
assert newCapacity == size;
// Shrink the capacity while trying to keep the representation dense.
Object[] newValues = new Object[newCapacity];
int left = size;
for (int i = values.length - 1; i >= 0; --i) {
Object value = values[i];
if (value != null) {
if (i >= newCapacity && indexes == null) {
// The index-value pair is outside of the dense
// representation bounds: convert to sparse by
// allocating indexes array.
if (size > ARRAY_STORAGE_32_MAX_SPARSE_SIZE) {
return new PrefixStorage32(values, size);
}
indexes = new int[newCapacity];
}
--left;
if (indexes != null) {
// store sparse representation index
indexes[left] = i;
}
newValues[left] = value;
if (left == 0) {
break;
}
}
}
assert left == 0;
values = newValues;
return this;
}
private Storage32 clearSparse(int index) {
assert size > 0;
long unsignedIndex = toUnsignedLong(index);
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position < 0) {
return this;
}
--size;
if (size == 0) {
// Emptied: just null out the value and convert to dense
// representation by setting indexes to null.
values[position] = null;
indexes = null;
return null;
}
int delta = capacityDeltaInt(indexes.length);
int wasted = indexes.length - size;
int newCapacity = indexes.length - delta;
if (wasted >= delta && newCapacity >= MIN_CAPACITY) {
// We are wasting too much: shrink the arrays.
int[] newIndexes = new int[newCapacity];
arraycopy(indexes, 0, newIndexes, 0, position);
arraycopy(indexes, position + 1, newIndexes, position, size - position);
indexes = newIndexes;
Object[] newValues = new Object[newCapacity];
arraycopy(values, 0, newValues, 0, position);
arraycopy(values, position + 1, newValues, position, size - position);
values = newValues;
} else {
// shift left to fill the gap
arraycopy(indexes, position + 1, indexes, position, size - position);
arraycopy(values, position + 1, values, position, size - position);
values[size] = null;
}
return this;
}
@Override
public Object get(int index) {
long unsignedIndex = toUnsignedLong(index);
if (indexes == null) {
// Dense representation.
return unsignedIndex < values.length ? values[index] : null;
} else {
// Sparse representation. Size is always greater than zero since
// empty arrays are represented as dense ones.
assert size > 0;
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
return position >= 0 ? values[position] : null;
}
}
@Override
public long iterate(Iterator iterator) {
if (size > 0) {
iterator.position32 = 0;
long index = advance(0, iterator);
assert index != Iterator.END;
return index;
} else {
return Iterator.END;
}
}
@Override
public long advance(int current, Iterator iterator) {
int position = iterator.position32;
if (indexes == null) {
// Dense representation.
while (position < values.length) {
Object value = values[position];
if (value != null) {
iterator.value = value;
iterator.position32 = position + 1;
return position;
}
++position;
}
return Iterator.END;
} else {
// Sparse representation. Size is always greater than zero since
// empty arrays are represented as dense ones.
assert size > 0;
if (position < size) {
iterator.value = values[position];
iterator.position32 = position + 1;
return toUnsignedLong(indexes[position]);
} else {
return Iterator.END;
}
}
}
@Override
public long iterateAtLeastFrom(int index, Iterator iterator) {
long unsignedIndex = toUnsignedLong(index);
if (indexes == null) {
// Dense representation.
long position = unsignedIndex;
while (position < values.length) {
Object value = values[(int) position];
if (value != null) {
iterator.value = value;
iterator.position32 = (int) position + 1;
return position;
}
++position;
}
return Iterator.END;
} else {
// Sparse representation. Size is always greater than zero since
// empty arrays are represented as dense ones.
assert size > 0;
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position < 0) {
position = -(position + 1);
if (position == size) {
return Iterator.END;
}
unsignedIndex = toUnsignedLong(indexes[position]);
}
iterator.position32 = position + 1;
iterator.value = values[position];
return unsignedIndex;
}
}
@Override
public long advanceAtLeastTo(int index, int current, Iterator iterator) {
long unsignedIndex = toUnsignedLong(index);
assert toUnsignedLong(current) < unsignedIndex;
if (indexes == null) {
// Dense representation.
long position = unsignedIndex;
while (position < values.length) {
Object value = values[(int) position];
if (value != null) {
iterator.value = value;
iterator.position32 = (int) position + 1;
return position;
}
++position;
}
return Iterator.END;
} else {
// Sparse representation. Size is always greater than zero since
// empty arrays are represented as dense ones.
assert size > 0;
int position = iterator.position32;
if (position == size) {
return Iterator.END;
}
position = unsignedBinarySearch(indexes, position, size, unsignedIndex);
if (position < 0) {
position = -(position + 1);
if (position == size) {
return Iterator.END;
}
unsignedIndex = toUnsignedLong(indexes[position]);
}
iterator.value = values[position];
iterator.position32 = position + 1;
return unsignedIndex;
}
}
}
/**
* Manages sorted array of 16-bit prefixes to lookup storages for 16-bit
* postfixes.
*/
private static final class PrefixStorage32 implements Storage32 {
private static final int MIN_CAPACITY = 2;
private static final int MAX_CAPACITY = 64 * 1024;
private int size;
private short[] prefixes;
private Storage16[] storages;
// used for caching of the last resolved 16-bit storage
private int lastPrefix = -1;
private Storage16 lastStorage;
/**
* Constructs a new prefix storage by converting from the given dense
* values and inserting the new given index-value pair.
*/
PrefixStorage32(Object[] values, int size, int index, Object value) {
assert size <= values.length;
assert index >= values.length;
this.prefixes = new short[MIN_CAPACITY];
this.storages = new Storage16[MIN_CAPACITY];
int count = 0;
for (int i = 0; i < values.length; ++i) {
Object storedValue = values[i];
if (storedValue != null) {
append(i, storedValue);
++count;
if (count == size) {
break;
}
}
}
append(index, value);
}
/**
* Constructs a new prefix storage by converting from the given dense
* values.
*/
PrefixStorage32(Object[] values, int size) {
assert size <= values.length;
this.prefixes = new short[MIN_CAPACITY];
this.storages = new Storage16[MIN_CAPACITY];
int count = 0;
for (int i = 0; i < values.length; ++i) {
Object storedValue = values[i];
if (storedValue != null) {
append(i, storedValue);
++count;
if (count == size) {
break;
}
}
}
}
/**
* Constructs a new prefix storage by converting from the given sparse
* index-value pairs and inserting the new given index-value pair at the
* given position.
*/
PrefixStorage32(int[] indexes, Object[] values, int position, int index, Object value) {
this.prefixes = new short[MIN_CAPACITY];
this.storages = new Storage16[MIN_CAPACITY];
for (int i = 0; i < position; ++i) {
append(indexes[i], values[i]);
}
append(index, value);
for (int i = position; i < indexes.length; ++i) {
append(indexes[i], values[i]);
}
}
@Override
public Storage32 set(int index, Object value) {
short prefix = (short) (index >>> Short.SIZE);
int unsignedPrefix = toUnsignedInt(prefix);
// Try to resolve the corresponding 16-bit postfix storage by its
// 16-bit prefix.
if (unsignedPrefix == lastPrefix) {
// We are lucky: just set the index-value pair in the cached
// storage.
lastStorage.set((short) index, value);
return this;
}
int position = size == 0 ? -1 : unsignedBinarySearch(prefixes, size, unsignedPrefix);
if (position >= 0) {
// The storage already exists: just add the index-value pair to
// it.
Storage16 storage = storages[position];
storage.set((short) index, value);
lastPrefix = unsignedPrefix;
lastStorage = storage;
return this;
}
position = -(position + 1);
// No storage exists yet: we need to allocate a new postfix storage
// and insert it into this prefix storage.
if (size == prefixes.length) {
// Grow the arrays.
int newCapacity = Math.min(MAX_CAPACITY, size + capacityDeltaShort(prefixes.length));
short[] newPrefixes = new short[newCapacity];
arraycopy(prefixes, 0, newPrefixes, 0, position);
arraycopy(prefixes, position, newPrefixes, position + 1, size - position);
prefixes = newPrefixes;
Storage16[] newStorages = new Storage16[newCapacity];
arraycopy(storages, 0, newStorages, 0, position);
arraycopy(storages, position, newStorages, position + 1, size - position);
storages = newStorages;
} else {
// Shift the arrays right to free a slot.
arraycopy(prefixes, position, prefixes, position + 1, size - position);
arraycopy(storages, position, storages, position + 1, size - position);
}
Storage16 createdStorage = new Storage16((short) index, value);
prefixes[position] = prefix;
storages[position] = createdStorage;
lastPrefix = unsignedPrefix;
lastStorage = createdStorage;
++size;
return this;
}
@Override
public Storage32 clear(int index) {
short prefix = (short) (index >>> Short.SIZE);
int unsignedPrefix = toUnsignedInt(prefix);
// Try to resolve the corresponding 16-bit postfix storage by its
// 16-bit prefix.
int position;
if (unsignedPrefix == lastPrefix) {
// We are lucky: just remove the index-value pair from the
// cached storage.
Storage16 storage = lastStorage;
if (!storage.clear((short) index)) {
return this;
}
// To handle the storage removal we need to know its prefix index.
position = unsignedBinarySearch(prefixes, size, unsignedPrefix);
assert position >= 0;
} else {
if (size == 0) {
// no storages, no problems
return this;
}
position = unsignedBinarySearch(prefixes, size, unsignedPrefix);
if (position < 0) {
// no storage, no problems
return this;
}
Storage16 storage = storages[position];
if (!storage.clear((short) index)) {
lastStorage = storage;
lastPrefix = unsignedPrefix;
return this;
}
}
// The 16-bit postfix storage is emptied this point: remove it from
// this prefix storage.
--size;
lastStorage = null;
lastPrefix = -1;
if (size == 0) {
// this prefix storage is also emptied
storages[position] = null;
return null;
}
int delta = capacityDeltaShort(prefixes.length);
int wasted = prefixes.length - size;
int newCapacity = prefixes.length - delta;
if (wasted >= delta && newCapacity >= MIN_CAPACITY) {
// Wasting too much: shrink the arrays.
short[] newPrefixes = new short[newCapacity];
arraycopy(prefixes, 0, newPrefixes, 0, position);
arraycopy(prefixes, position + 1, newPrefixes, position, size - position);
prefixes = newPrefixes;
Storage16[] newStorages = new Storage16[newCapacity];
arraycopy(storages, 0, newStorages, 0, position);
arraycopy(storages, position + 1, newStorages, position, size - position);
storages = newStorages;
} else {
// Shift the arrays left to fill the gap.
arraycopy(prefixes, position + 1, prefixes, position, size - position);
arraycopy(storages, position + 1, storages, position, size - position);
storages[size] = null;
}
return this;
}
@Override
public Object get(int index) {
short prefix = (short) (index >>> Short.SIZE);
int unsignedPrefix = toUnsignedInt(prefix);
// Try to resolve the corresponding 16-bit postfix storage by its
// 16-bit prefix.
if (unsignedPrefix == lastPrefix) {
// We are lucky: just consult the cached postfix storage.
return lastStorage.get((short) index);
}
if (size == 0) {
// no storages, no problems
return null;
}
int position = unsignedBinarySearch(prefixes, size, unsignedPrefix);
if (position < 0) {
// no storage, no problems
return null;
}
Storage16 storage = storages[position];
lastPrefix = unsignedPrefix;
lastStorage = storage;
return storage.get((short) index);
}
@Override
public long iterate(Iterator iterator) {
if (size > 0) {
iterator.position32 = 1;
iterator.storage16 = storages[0];
return toUnsignedLong(prefixes[0]) << Short.SIZE | iterator.storage16.iterate(iterator);
} else {
return Iterator.END;
}
}
@Override
public long advance(int current, Iterator iterator) {
// Try advance the current postfix storage.
int postfix = iterator.storage16.advance(iterator);
if (postfix != Storage16.END) {
return toUnsignedLong(current) & SHORT_PREFIX_MASK_LONG | postfix;
}
// Try to advance to the next postfix storage and iterate it.
int index = iterator.position32;
if (index < size) {
iterator.storage16 = storages[index];
iterator.position32 = index + 1;
postfix = iterator.storage16.iterate(iterator);
return toUnsignedLong(prefixes[index]) << Short.SIZE | postfix;
} else {
return Iterator.END;
}
}
@Override
public long iterateAtLeastFrom(int index, Iterator iterator) {
if (size == 0) {
return Iterator.END;
}
return iterateAtLeastFrom(index, 0, iterator);
}
@Override
public long advanceAtLeastTo(int index, int current, Iterator iterator) {
short prefix = (short) (index >>> Short.SIZE);
int unsignedPrefix = toUnsignedInt(prefix);
int currentUnsignedPrefix = (current & SHORT_PREFIX_MASK_INT) >>> Short.SIZE;
assert currentUnsignedPrefix <= unsignedPrefix;
if (unsignedPrefix == currentUnsignedPrefix) {
// The requested index is within the current 16-bit postfix
// storage.
int postfix = iterator.storage16.advanceAtLeastTo((short) index, (short) current, iterator);
if (postfix != Storage16.END) {
// found in the current postfix storage
return toUnsignedLong(prefix) << Short.SIZE | postfix;
}
int position = iterator.position32;
if (position == size) {
// the end
return Iterator.END;
}
// Iterate the next prefix.
Storage16 storage = storages[position];
iterator.storage16 = storage;
iterator.position32 = position + 1;
postfix = storage.iterate(iterator);
assert postfix != Storage16.END;
return toUnsignedLong(prefixes[position]) << Short.SIZE | postfix;
}
// Resolve and iterate the requested prefix.
int position = iterator.position32;
if (position == size) {
return Iterator.END;
}
return iterateAtLeastFrom(index, position, iterator);
}
private void append(int index, Object value) {
short prefix = (short) (index >>> Short.SIZE);
if (size != 0 && prefix == prefixes[size - 1]) {
storages[size - 1].set((short) index, value);
return;
}
if (size == prefixes.length) {
int newCapacity = Math.min(MAX_CAPACITY, size + capacityDeltaShort(prefixes.length));
prefixes = copyOf(prefixes, newCapacity);
storages = copyOf(storages, newCapacity);
}
prefixes[size] = prefix;
storages[size] = new Storage16((short) index, value);
++size;
}
private long iterateAtLeastFrom(int index, int startFrom, Iterator iterator) {
short prefix = (short) (index >>> Short.SIZE);
int position = unsignedBinarySearch(prefixes, startFrom, size, toUnsignedInt(prefix));
Storage16 storage;
int postfix;
if (position < 0) {
position = -(position + 1);
if (position == size) {
// no such index
return Iterator.END;
}
storage = storages[position];
prefix = prefixes[position];
postfix = storage.iterate(iterator);
assert postfix != Storage16.END;
} else {
storage = storages[position];
postfix = storage.iterateAtLeastFrom((short) index, iterator);
}
if (postfix != Storage16.END) {
// The postfix storage corresponding to the requested index is
// successfully advanced at least to the requested index.
iterator.storage16 = storage;
iterator.position32 = position + 1;
return toUnsignedLong(prefix) << Short.SIZE | postfix;
} else {
// The postfix storage corresponding to the requested index
// doesn't contain the requested index or any indexes greater
// than it: try to iterate from the next prefix storage.
++position;
if (position == size) {
return Iterator.END;
}
storage = storages[position];
iterator.storage16 = storage;
iterator.position32 = position + 1;
postfix = storage.iterate(iterator);
assert postfix != Storage16.END;
return toUnsignedLong(prefixes[position]) << Short.SIZE | postfix;
}
}
}
/**
* Manages ordered arrays of index-value pairs for 16-bit indexes (postfixes).
*
* Works in two modes:
*
* - Dense mode with directly indexable values array.
*
- Sparse mode with sorted indexes and values arrays.
*
*/
private static final class Storage16 {
/**
* Identifies an iterator end.
*/
public static final int END = -1;
private static final int MIN_CAPACITY = 2;
private int size;
private short[] indexes;
private Object[] values = new Object[MIN_CAPACITY];
Storage16(short index, Object value) {
set(index, value);
}
/**
* Sets or replaces a value at the given index to the new given value.
*
* @param index the index to set value at.
* @param value the value to set.
*/
public void set(short index, Object value) {
if (indexes == null) {
setDense(index, value);
} else {
setSparse(index, value);
}
}
private void setDense(short index, Object value) {
int unsignedIndex = toUnsignedInt(index);
if (unsignedIndex < values.length) {
// The index is inside the bounds: just set the corresponding
// array slot.
if (values[unsignedIndex] == null) {
++size;
}
values[unsignedIndex] = value;
return;
}
// We need to grow the array.
int delta = denseCapacityDeltaShort(size, values.length);
int newCapacity = Math.min(STORAGE_16_MAX_DENSE_SIZE, size + delta);
if (unsignedIndex < newCapacity) {
// We are good: just grow the array and store the value.
values = copyOf(values, newCapacity);
values[unsignedIndex] = value;
++size;
return;
}
// We would be wasting too much space on dense representation:
// convert to sparse representation.
Object[] oldValues = values;
if (size == values.length) {
// No space left: reallocate the values and allocate the
// indexes array.
indexes = new short[newCapacity];
values = new Object[newCapacity];
} else {
// Some space left, its size is bellow the sparse
// representation threshold (we adjusted for it in
// denseCapacityDeltaShort call): reuse the values array and
// allocate indexes array.
indexes = new short[values.length];
}
// Iterate dense records one-by-one and represent them as sparse
// ones.
int count = 0;
for (int i = 0; i < values.length; ++i) {
Object storedValue = oldValues[i];
if (storedValue != null) {
indexes[count] = (short) i;
values[count] = storedValue;
++count;
if (count == size) {
break;
}
}
}
// Store the original value being inserted and grow the size.
indexes[size] = index;
values[size] = value;
++size;
}
private void setSparse(short index, Object value) {
int unsignedIndex = toUnsignedInt(index);
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position >= 0) {
// Already there: just overwrite it.
values[position] = value;
return;
}
position = -(position + 1);
if (size == indexes.length) {
// We are full: either grow the array or try to convert to
// dense representation.
int delta = capacityDeltaShort(indexes.length);
int lastIndex = toUnsignedInt(indexes[indexes.length - 1]);
int denseCapacity = Math.max(unsignedIndex, lastIndex) + 1;
if (denseCapacity <= size + delta) {
// The wasted space is bellow the threshold: convert to
// dense representation.
Object[] newValues = new Object[denseCapacity];
for (int i = 0; i < indexes.length; ++i) {
newValues[toUnsignedInt(indexes[i])] = values[i];
}
newValues[unsignedIndex] = value;
indexes = null;
values = newValues;
++size;
return;
}
// We were unable to convert to dense representation: reallocate
// the arrays to gain the space for the new value.
int newCapacity = Math.min(STORAGE_16_MAX_SPARSE_SIZE, size + capacityDeltaShort(indexes.length));
short[] newIndexes = new short[newCapacity];
arraycopy(indexes, 0, newIndexes, 0, position);
arraycopy(indexes, position, newIndexes, position + 1, size - position);
indexes = newIndexes;
Object[] newValues = new Object[newCapacity];
arraycopy(values, 0, newValues, 0, position);
arraycopy(values, position, newValues, position + 1, size - position);
values = newValues;
} else {
// There is some space left: just shift the values to the right.
arraycopy(indexes, position, indexes, position + 1, size - position);
arraycopy(values, position, values, position + 1, size - position);
}
// Finally insert the value.
indexes[position] = index;
values[position] = value;
++size;
}
/**
* Clears a value stored at the given index in this storage.
*
* @param index the index to clear a value at.
* @return {@code true} if this storage array is emptied, {@code false}
* otherwise.
*/
public boolean clear(short index) {
if (indexes == null) {
return clearDense(index);
} else {
return clearSparse(index);
}
}
@SuppressWarnings("checkstyle:npathcomplexity")
private boolean clearDense(short index) {
int unsignedIndex = toUnsignedInt(index);
if (unsignedIndex >= values.length) {
// Out of bounds.
return false;
}
if (values[unsignedIndex] == null) {
// No such record.
return false;
}
--size;
if (size == 0) {
// Emptied: just report the storage is empty by returning null.
return true;
}
values[unsignedIndex] = null;
int delta = capacityDeltaShort(values.length);
int wasted = values.length - size;
if (wasted < delta) {
return false;
}
int newCapacity = values.length - delta;
if (newCapacity < MIN_CAPACITY) {
// never reached with the default MIN_CAPACITY of 2
return false;
}
assert wasted == delta;
assert newCapacity == size;
// Shrink the capacity while trying to keep the representation dense.
Object[] newValues = new Object[newCapacity];
int left = size;
for (int i = values.length - 1; i >= 0; --i) {
Object value = values[i];
if (value != null) {
if (i >= newCapacity && indexes == null) {
// The index-value pair is outside of the dense
// representation bounds: convert to sparse by
// allocating indexes array.
indexes = new short[newCapacity];
}
--left;
if (indexes != null) {
// store sparse representation index
indexes[left] = (short) i;
}
newValues[left] = value;
if (left == 0) {
break;
}
}
}
values = newValues;
return false;
}
private boolean clearSparse(short index) {
int unsignedIndex = toUnsignedInt(index);
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position < 0) {
return false;
}
--size;
if (size == 0) {
// Emptied.
return true;
}
int delta = capacityDeltaShort(indexes.length);
int wasted = indexes.length - size;
int newCapacity = indexes.length - delta;
if (wasted >= delta && newCapacity >= MIN_CAPACITY) {
// We are wasting too much: shrink the arrays.
short[] newIndexes = new short[newCapacity];
arraycopy(indexes, 0, newIndexes, 0, position);
arraycopy(indexes, position + 1, newIndexes, position, size - position);
indexes = newIndexes;
Object[] newValues = new Object[newCapacity];
arraycopy(values, 0, newValues, 0, position);
arraycopy(values, position + 1, newValues, position, size - position);
values = newValues;
} else {
// shift left to fill the gap
arraycopy(indexes, position + 1, indexes, position, size - position);
arraycopy(values, position + 1, values, position, size - position);
values[size] = null;
}
return false;
}
/**
* @return the value stored inside this storage at the given index or
* {@code null} if nothing stored at it.
*/
public Object get(short index) {
int unsignedIndex = toUnsignedInt(index);
if (indexes == null) {
return unsignedIndex < values.length ? values[unsignedIndex] : null;
} else {
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
return position >= 0 ? values[position] : null;
}
}
/**
* Starts iteration on this storage using the given iterator.
*
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at or {@link
* #END} if this storage is empty.
*/
public int iterate(Iterator iterator) {
assert size > 0;
iterator.position16 = 0;
int index = advance(iterator);
assert index != Storage16.END;
return index;
}
/**
* Advances the given iterator on this storage.
*
* @param iterator the iterator to advance.
* @return an index at which this iterator was positioned before the
* advancement or {@link #END} if this iterator already was at its end.
*/
public int advance(Iterator iterator) {
assert size > 0;
int position = iterator.position16;
if (indexes == null) {
// Dense representation.
while (position < values.length) {
Object value = values[position];
if (value != null) {
iterator.value = value;
iterator.position16 = position + 1;
return position;
}
++position;
}
return Storage16.END;
} else {
// Sparse representation.
if (position < size) {
iterator.value = values[position];
iterator.position16 = position + 1;
return toUnsignedInt(indexes[position]);
} else {
return Storage16.END;
}
}
}
/**
* Starts iteration on this storage starting at least from the given
* index using the given iterator.
*
* @param index the index to start the iteration from.
* @param iterator the iterator to iterate with.
* @return an index at which the iterator is currently at; or {@link
* #END} if no index greater than or equal to the given index exists in
* this storage.
*/
public int iterateAtLeastFrom(short index, Iterator iterator) {
assert size > 0;
int unsignedIndex = toUnsignedInt(index);
if (indexes == null) {
// Dense representation.
int position = unsignedIndex;
while (position < values.length) {
Object value = values[position];
if (value != null) {
iterator.value = value;
iterator.position16 = position + 1;
return position;
}
++position;
}
return Storage16.END;
} else {
// Sparse representation.
int position = unsignedBinarySearch(indexes, size, unsignedIndex);
if (position < 0) {
position = -(position + 1);
if (position == size) {
return Storage16.END;
}
unsignedIndex = toUnsignedInt(indexes[position]);
}
iterator.value = values[position];
iterator.position16 = position + 1;
return unsignedIndex;
}
}
/**
* Advances the given iterator to the given index; or, if the index is
* not present in this storage, to an index immediately following it
* and present in this storage.
*
* @param index the index to advance at least to. The index must be
* greater than the index this iterator is currently at.
* @param iterator the iterator to advance.
* @return an index at which thee iterator was advanced to or {@link
* #END} if the iterator reached its end.
*/
public int advanceAtLeastTo(short index, short current, Iterator iterator) {
assert size > 0;
int unsignedIndex = toUnsignedInt(index);
assert toUnsignedInt(current) < unsignedIndex;
if (indexes == null) {
// Dense representation.
int position = unsignedIndex;
while (position < values.length) {
Object value = values[position];
if (value != null) {
iterator.value = value;
iterator.position16 = position + 1;
return position;
}
++position;
}
return Storage16.END;
} else {
// Sparse representation.
int position = iterator.position16;
if (position == size) {
return Storage16.END;
}
position = unsignedBinarySearch(indexes, position, size, unsignedIndex);
if (position < 0) {
position = -(position + 1);
if (position == size) {
return Storage16.END;
}
unsignedIndex = toUnsignedInt(indexes[position]);
}
iterator.value = values[position];
iterator.position16 = position + 1;
return unsignedIndex;
}
}
}
}