package jdk.nashorn.internal.runtime.arrays;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ScriptRuntime;
class SparseArrayData extends ArrayData {
static final int MAX_DENSE_LENGTH = 128 * 1024;
private ArrayData underlying;
private final long maxDenseLength;
private TreeMap<Long, Object> sparseMap;
SparseArrayData(final ArrayData underlying, final long length) {
this(underlying, length, new TreeMap<>());
}
private SparseArrayData(final ArrayData underlying, final long length, final TreeMap<Long, Object> sparseMap) {
super(length);
assert underlying.length() <= length;
this.underlying = underlying;
this.maxDenseLength = underlying.length();
this.sparseMap = sparseMap;
}
@Override
public ArrayData copy() {
return new SparseArrayData(underlying.copy(), length(), new TreeMap<>(sparseMap));
}
@Override
public Object[] asObjectArray() {
final int len = (int)Math.min(length(), Integer.MAX_VALUE);
final int underlyingLength = (int)Math.min(len, underlying.length());
final Object[] objArray = new Object[len];
for (int i = 0; i < underlyingLength; i++) {
objArray[i] = underlying.getObject(i);
}
Arrays.fill(objArray, underlyingLength, len, ScriptRuntime.UNDEFINED);
for (final Map.Entry<Long, Object> entry : sparseMap.entrySet()) {
final long key = entry.getKey();
if (key < Integer.MAX_VALUE) {
objArray[(int)key] = entry.getValue();
} else {
break;
}
}
return objArray;
}
@Override
public ArrayData shiftLeft(final int by) {
underlying = underlying.shiftLeft(by);
final TreeMap<Long, Object> newSparseMap = new TreeMap<>();
for (final Map.Entry<Long, Object> entry : sparseMap.entrySet()) {
final long newIndex = entry.getKey() - by;
if (newIndex >= 0) {
if (newIndex < maxDenseLength) {
final long oldLength = underlying.length();
underlying = underlying.ensure(newIndex)
.set((int) newIndex, entry.getValue(), false)
.safeDelete(oldLength, newIndex - 1, false);
} else {
newSparseMap.put(newIndex, entry.getValue());
}
}
}
sparseMap = newSparseMap;
setLength(Math.max(length() - by, 0));
return sparseMap.isEmpty() ? underlying : this;
}
@Override
public ArrayData shiftRight(final int by) {
final TreeMap<Long, Object> newSparseMap = new TreeMap<>();
final long len = underlying.length();
if (len + by > maxDenseLength) {
final long tempLength = Math.max(0, maxDenseLength - by);
for (long i = tempLength; i < len; i++) {
if (underlying.has((int) i)) {
newSparseMap.put(i + by, underlying.getObject((int) i));
}
}
underlying = underlying.shrink((int) tempLength);
underlying.setLength(tempLength);
}
underlying = underlying.shiftRight(by);
for (final Map.Entry<Long, Object> entry : sparseMap.entrySet()) {
final long newIndex = entry.getKey() + by;
newSparseMap.put(newIndex, entry.getValue());
}
sparseMap = newSparseMap;
setLength(length() + by);
return this;
}
@Override
public ArrayData ensure(final long safeIndex) {
if (safeIndex >= length()) {
setLength(safeIndex + 1);
}
return this;
}
@Override
public ArrayData shrink(final long newLength) {
if (newLength < underlying.length()) {
underlying = underlying.shrink(newLength);
underlying.setLength(newLength);
sparseMap.clear();
setLength(newLength);
}
sparseMap.subMap(newLength, Long.MAX_VALUE).clear();
setLength(newLength);
return this;
}
@Override
public ArrayData set(final int index, final Object value, final boolean strict) {
if (index >= 0 && index < maxDenseLength) {
final long oldLength = underlying.length();
underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict);
setLength(Math.max(underlying.length(), length()));
} else {
final Long longIndex = indexToKey(index);
sparseMap.put(longIndex, value);
setLength(Math.max(longIndex + 1, length()));
}
return this;
}
@Override
public ArrayData set(final int index, final int value, final boolean strict) {
if (index >= 0 && index < maxDenseLength) {
final long oldLength = underlying.length();
underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict);
setLength(Math.max(underlying.length(), length()));
} else {
final Long longIndex = indexToKey(index);
sparseMap.put(longIndex, value);
setLength(Math.max(longIndex + 1, length()));
}
return this;
}
@Override
public ArrayData set(final int index, final double value, final boolean strict) {
if (index >= 0 && index < maxDenseLength) {
final long oldLength = underlying.length();
underlying = underlying.ensure(index).set(index, value, strict).safeDelete(oldLength, index - 1, strict);
setLength(Math.max(underlying.length(), length()));
} else {
final Long longIndex = indexToKey(index);
sparseMap.put(longIndex, value);
setLength(Math.max(longIndex + 1, length()));
}
return this;
}
@Override
public ArrayData setEmpty(final int index) {
underlying.setEmpty(index);
return this;
}
@Override
public ArrayData setEmpty(final long lo, final long hi) {
underlying.setEmpty(lo, hi);
return this;
}
@Override
public Type getOptimisticType() {
return underlying.getOptimisticType();
}
@Override
public int getInt(final int index) {
if (index >= 0 && index < maxDenseLength) {
return underlying.getInt(index);
}
return JSType.toInt32(sparseMap.get(indexToKey(index)));
}
@Override
public int getIntOptimistic(final int index, final int programPoint) {
if (index >= 0 && index < maxDenseLength) {
return underlying.getIntOptimistic(index, programPoint);
}
return JSType.toInt32Optimistic(sparseMap.get(indexToKey(index)), programPoint);
}
@Override
public double getDouble(final int index) {
if (index >= 0 && index < maxDenseLength) {
return underlying.getDouble(index);
}
return JSType.toNumber(sparseMap.get(indexToKey(index)));
}
@Override
public double getDoubleOptimistic(final int index, final int programPoint) {
if (index >= 0 && index < maxDenseLength) {
return underlying.getDouble(index);
}
return JSType.toNumberOptimistic(sparseMap.get(indexToKey(index)), programPoint);
}
@Override
public Object getObject(final int index) {
if (index >= 0 && index < maxDenseLength) {
return underlying.getObject(index);
}
final Long key = indexToKey(index);
if (sparseMap.containsKey(key)) {
return sparseMap.get(key);
}
return ScriptRuntime.UNDEFINED;
}
@Override
public boolean has(final int index) {
if (index >= 0 && index < maxDenseLength) {
return index < underlying.length() && underlying.has(index);
}
return sparseMap.containsKey(indexToKey(index));
}
@Override
public ArrayData delete(final int index) {
if (index >= 0 && index < maxDenseLength) {
if (index < underlying.length()) {
underlying = underlying.delete(index);
}
} else {
sparseMap.remove(indexToKey(index));
}
return this;
}
@Override
public ArrayData delete(final long fromIndex, final long toIndex) {
if (fromIndex < maxDenseLength && fromIndex < underlying.length()) {
underlying = underlying.delete(fromIndex, Math.min(toIndex, underlying.length() - 1));
}
if (toIndex >= maxDenseLength) {
sparseMap.subMap(fromIndex, true, toIndex, true).clear();
}
return this;
}
private static Long indexToKey(final int index) {
return ArrayIndex.toLongIndex(index);
}
@Override
public ArrayData convert(final Class<?> type) {
underlying = underlying.convert(type);
return this;
}
@Override
public Object pop() {
final long len = length();
final long underlyingLen = underlying.length();
if (len == 0) {
return ScriptRuntime.UNDEFINED;
}
if (len == underlyingLen) {
final Object result = underlying.pop();
setLength(underlying.length());
return result;
}
setLength(len - 1);
final Long key = len - 1;
return sparseMap.containsKey(key) ? sparseMap.remove(key) : ScriptRuntime.UNDEFINED;
}
@Override
public ArrayData slice(final long from, final long to) {
assert to <= length();
final long start = from < 0 ? (from + length()) : from;
final long newLength = to - start;
final long underlyingLength = underlying.length();
if (start >= 0 && to <= maxDenseLength) {
if (newLength <= underlyingLength) {
return underlying.slice(from, to);
}
return underlying.slice(from, to).ensure(newLength - 1).delete(underlyingLength, newLength);
}
ArrayData sliced = EMPTY_ARRAY;
sliced = sliced.ensure(newLength - 1);
for (long i = start; i < to; i = nextIndex(i)) {
if (has((int)i)) {
sliced = sliced.set((int)(i - start), getObject((int)i), false);
}
}
assert sliced.length() == newLength;
return sliced;
}
@Override
public long nextIndex(final long index) {
if (index < underlying.length() - 1) {
return underlying.nextIndex(index);
}
final Long nextKey = sparseMap.higherKey(index);
if (nextKey != null) {
return nextKey;
}
return length();
}
}