org.neo4j.collection.trackable.HeapTrackingArrayList Maven / Gradle / Ivy
Show all versions of neo4j-collections Show documentation
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.collection.trackable;
import static org.neo4j.internal.helpers.ArrayUtil.MAX_ARRAY_SIZE;
import static org.neo4j.memory.HeapEstimator.shallowSizeOfInstance;
import static org.neo4j.memory.HeapEstimator.shallowSizeOfObjectArray;
import static org.neo4j.util.Preconditions.requireNonNegative;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import org.neo4j.memory.MemoryTracker;
/**
* A heap tracking array list. It only tracks the internal structure, not the elements within.
*
* This is mostly a copy of {@link ArrayList} to expose the {@link #grow(int)} method.
*
* @param element type
*/
@SuppressWarnings("unchecked")
public class HeapTrackingArrayList implements List, AutoCloseable {
private static final long SHALLOW_SIZE = shallowSizeOfInstance(HeapTrackingArrayList.class);
private final MemoryTracker memoryTracker;
private long trackedSize;
private int size;
private int modCount;
private Object[] elementData;
/**
* @return a new heap tracking array list with initial size 1
*/
public static HeapTrackingArrayList newArrayList(MemoryTracker memoryTracker) {
return newArrayListWithInitialTrackedSize(1, memoryTracker, 0L);
}
/**
* @return a new heap tracking array list with initial size 1 and the specified initial tracked memory size
*/
public static HeapTrackingArrayList newArrayListWithInitialTrackedSize(
MemoryTracker memoryTracker, long initialTrackedSize) {
return newArrayListWithInitialTrackedSize(1, memoryTracker, initialTrackedSize);
}
/**
* @return a new heap tracking array list with the specified initial capacity
*/
public static HeapTrackingArrayList newArrayList(int initialCapacity, MemoryTracker memoryTracker) {
return newArrayListWithInitialTrackedSize(initialCapacity, memoryTracker, 0L);
}
/**
* @return a new heap tracking array list with the specified exact size, filled with nulls
*/
public static HeapTrackingArrayList newEmptyArrayList(int exactSize, MemoryTracker memoryTracker) {
HeapTrackingArrayList list = newArrayListWithInitialTrackedSize(exactSize, memoryTracker, exactSize);
list.size = exactSize;
return list;
}
/**
* @return a new heap tracking array list with the specified initial size and initial tracked memory size
*/
public static HeapTrackingArrayList newArrayListWithInitialTrackedSize(
int initialCapacity, MemoryTracker memoryTracker, long initialTrackedSize) {
requireNonNegative(initialCapacity);
long trackedSize = shallowSizeOfObjectArray(initialCapacity) + initialTrackedSize;
memoryTracker.allocateHeap(SHALLOW_SIZE + trackedSize);
return new HeapTrackingArrayList<>(initialCapacity, memoryTracker, trackedSize);
}
@SuppressWarnings("CopyConstructorMissesField")
private HeapTrackingArrayList(HeapTrackingArrayList other) {
int otherSize = other.size;
this.size = otherSize;
this.elementData = new Object[otherSize];
System.arraycopy(other.elementData, 0, this.elementData, 0, otherSize);
this.memoryTracker = other.memoryTracker;
this.trackedSize = shallowSizeOfObjectArray(otherSize);
memoryTracker.allocateHeap(SHALLOW_SIZE + trackedSize);
}
private HeapTrackingArrayList(int initialSize, MemoryTracker memoryTracker, long trackedSize) {
this.elementData = new Object[initialSize];
this.memoryTracker = memoryTracker;
this.trackedSize = trackedSize;
}
/*
* Compacts the elementData list of the original array
*/
@Override
public HeapTrackingArrayList clone() {
return new HeapTrackingArrayList<>(this);
}
@Override
public boolean add(E item) {
modCount++;
add(item, elementData, size);
return true;
}
@Override
public boolean containsAll(Collection> c) {
for (Object e : c) {
if (!contains(e)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0) {
return false;
}
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size)) {
elementData = grow(s + numNew);
}
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
@Override
public boolean addAll(int index, Collection extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0) {
return false;
}
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size)) {
elementData = grow(s + numNew);
}
int numMoved = s - index;
if (numMoved > 0) {
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
}
System.arraycopy(a, 0, elementData, index, numNew);
size = s + numNew;
return true;
}
@Override
public boolean removeAll(Collection> c) {
return batchRemove(c, false, 0, size);
}
@Override
public boolean retainAll(Collection> c) {
return batchRemove(c, true, 0, size);
}
@Override
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
public E last() {
return get(size - 1);
}
@Override
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length) {
elementData = grow(size + 1);
}
System.arraycopy(elementData, index, elementData, index + 1, s - index);
elementData[index] = element;
size = s + 1;
}
@Override
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
public E removeLast() {
return remove(size - 1);
}
@Override
public int indexOf(Object o) {
Object[] es = elementData;
int size = this.size;
if (o == null) {
for (int i = 0; i < size; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
@Override
public int lastIndexOf(Object o) {
Object[] es = elementData;
int size = this.size;
if (o == null) {
for (int i = size - 1; i >= 0; i--) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = size - 1; i >= 0; i--) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
@Override
public ListIterator listIterator() {
return new ListItr(0);
}
@Override
public ListIterator listIterator(int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
@Override
public List subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
public void truncate(int newSize) {
if (newSize >= size) {
return;
}
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
this.size = newSize;
}
@Override
public Iterator iterator() {
return new Itr();
}
@Override
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
@Override
public T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
{
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size) {
a[size] = null;
}
return a;
}
@Override
public void close() {
if (elementData != null) {
memoryTracker.releaseHeap(trackedSize + SHALLOW_SIZE);
elementData = null;
}
}
public Iterator autoClosingIterator() {
return new Iterator<>() {
int index;
@Override
public boolean hasNext() {
if (index >= size) {
close();
return false;
}
return true;
}
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elementData(index++);
}
};
}
@Override
public void sort(Comparator super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
public boolean notEmpty() {
return size != 0;
}
@Override
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
@Override
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found:
{
if (o == null) {
for (; i < size; i++) {
if (es[i] == null) {
break found;
}
}
} else {
for (; i < size; i++) {
if (o.equals(es[i])) {
break found;
}
}
}
return false;
}
fastRemove(es, i);
return true;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof List)) {
return false;
}
final int expectedModCount = modCount;
boolean equal = (o.getClass() == HeapTrackingArrayList.class)
? equalsArrayList((HeapTrackingArrayList>) o)
: equalsRange((List>) o, 0, size);
checkForComodification(expectedModCount);
return equal;
}
@Override
public int hashCode() {
int expectedModCount = modCount;
int hash = hashCodeRange(0, size);
checkForComodification(expectedModCount);
return hash;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
forEach(elem -> sb.append(elem).append(","));
if (size() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("]");
return sb.toString();
}
@Override
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++) {
es[i] = null;
}
}
@Override
public void forEach(Consumer super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++) {
action.accept(elementAt(es, i));
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Grow and report size change to tracker
*/
private Object[] grow(int minimumCapacity) {
int newCapacity = newCapacity(minimumCapacity, elementData.length);
long oldHeapUsage = trackedSize;
trackedSize = shallowSizeOfObjectArray(newCapacity);
memoryTracker.allocateHeap(trackedSize);
Object[] newItems = new Object[newCapacity];
System.arraycopy(elementData, 0, newItems, 0, Math.min(size, newCapacity));
elementData = newItems;
memoryTracker.releaseHeap(oldHeapUsage);
return elementData;
}
static int newCapacity(int minimumCapacity, int oldCapacity) {
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minimumCapacity <= 0) {
if (minimumCapacity < 0) // overflow
{
throw new OutOfMemoryError();
}
return minimumCapacity;
}
return newCapacity - MAX_ARRAY_SIZE <= 0 ? newCapacity : hugeCapacity(minimumCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
{
throw new OutOfMemoryError();
}
return minCapacity > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
@SuppressWarnings("unchecked")
private E elementData(int index) {
return (E) elementData[index];
}
@SuppressWarnings("unchecked")
private static E elementAt(Object[] es, int index) {
return (E) es[index];
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) {
elementData = grow(size + 1);
}
elementData[s] = e;
size = s + 1;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i) {
System.arraycopy(es, i + 1, es, i, newSize - i);
}
es[size = newSize] = null;
}
private void checkForComodification(final int expectedModCount) {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private boolean equalsRange(List> other, int from, int to) {
final Object[] es = elementData;
if (to > es.length) {
throw new ConcurrentModificationException();
}
var oit = other.iterator();
for (; from < to; from++) {
if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
return false;
}
}
return !oit.hasNext();
}
private boolean equalsArrayList(HeapTrackingArrayList> other) {
final int otherModCount = other.modCount;
final int s = size;
boolean equal;
if (equal = s == other.size) {
final Object[] otherEs = other.elementData;
final Object[] es = elementData;
if (s > es.length || s > otherEs.length) {
throw new ConcurrentModificationException();
}
for (int i = 0; i < s; i++) {
if (!Objects.equals(es[i], otherEs[i])) {
equal = false;
break;
}
}
}
other.checkForComodification(otherModCount);
return equal;
}
private int hashCodeRange(int from, int to) {
final Object[] es = elementData;
if (to > es.length) {
throw new ConcurrentModificationException();
}
int hashCode = 1;
for (int i = from; i < to; i++) {
Object e = es[i];
hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
}
return hashCode;
}
private boolean batchRemove(Collection> c, boolean complement, final int from, final int end) {
Objects.requireNonNull(c);
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
for (r = from; ; r++) {
if (r == end) {
return false;
}
if (c.contains(es[r]) != complement) {
break;
}
}
int w = r++;
try {
for (Object e; r < end; r++) {
if (c.contains(e = es[r]) == complement) {
es[w++] = e;
}
}
} catch (Throwable ex) {
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
modCount += end - w;
shiftTailOverGap(es, w, end);
}
return true;
}
private void shiftTailOverGap(Object[] es, int lo, int hi) {
System.arraycopy(es, hi, es, lo, size - hi);
for (int to = size, i = size -= hi - lo; i < to; i++) {
es[i] = null;
}
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
private class Itr implements Iterator {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
Itr() {}
@Override
public boolean hasNext() {
return cursor != size;
}
@Override
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) {
throw new NoSuchElementException();
}
Object[] elementData = HeapTrackingArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
cursor = i + 1;
return (E) elementData[lastRet = i];
}
@Override
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
HeapTrackingArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
final int size = HeapTrackingArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length) {
throw new ConcurrentModificationException();
}
for (; i < size && modCount == expectedModCount; i++) {
action.accept(elementAt(es, i));
}
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
private class ListItr extends Itr implements ListIterator {
ListItr(int index) {
super();
cursor = index;
}
@Override
public boolean hasPrevious() {
return cursor != 0;
}
@Override
public int nextIndex() {
return cursor;
}
@Override
public int previousIndex() {
return cursor - 1;
}
@Override
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0) {
throw new NoSuchElementException();
}
Object[] elementData = HeapTrackingArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
cursor = i;
return (E) elementData[lastRet = i];
}
@Override
public void set(E e) {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
HeapTrackingArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void add(E e) {
checkForComodification();
try {
int i = cursor;
HeapTrackingArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}