blob: 527c5a69dfdabf9e71733982543063bdc0a9ef37 [file] [log] [blame]
/*
* Copyright (C) 2011, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.util;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Random access list that allocates entries in blocks.
* <p>
* Unlike {@link java.util.ArrayList}, this type does not need to reallocate the
* internal array in order to expand the capacity of the list. Access to any
* element is constant time, but requires two array lookups instead of one.
* <p>
* To handle common usages, {@link #add(Object)} and {@link #iterator()} use
* internal code paths to amortize out the second array lookup, making addition
* and simple iteration closer to one array operation per element processed.
* <p>
* Similar to {@code ArrayList}, adding or removing from any position except the
* end of the list requires O(N) time to copy all elements between the
* modification point and the end of the list. Applications are strongly
* encouraged to not use this access pattern with this list implementation.
*
* @param <T>
* type of list element.
*/
public class BlockList<T> extends AbstractList<T> {
private static final int BLOCK_BITS = 10;
static final int BLOCK_SIZE = 1 << BLOCK_BITS;
private static final int BLOCK_MASK = BLOCK_SIZE - 1;
T[][] directory;
int size;
private int tailDirIdx;
private int tailBlkIdx;
private T[] tailBlock;
/**
* Initialize an empty list.
*/
public BlockList() {
directory = BlockList.<T> newDirectory(256);
directory[0] = BlockList.<T> newBlock();
tailBlock = directory[0];
}
/**
* Initialize an empty list with an expected capacity.
*
* @param capacity
* number of elements expected to be in the list.
*/
public BlockList(int capacity) {
int dirSize = toDirectoryIndex(capacity);
if ((capacity & BLOCK_MASK) != 0 || dirSize == 0)
dirSize++;
directory = BlockList.<T> newDirectory(dirSize);
directory[0] = BlockList.<T> newBlock();
tailBlock = directory[0];
}
/** {@inheritDoc} */
@Override
public int size() {
return size;
}
/** {@inheritDoc} */
@Override
public void clear() {
for (T[] block : directory) {
if (block != null)
Arrays.fill(block, null);
}
size = 0;
tailDirIdx = 0;
tailBlkIdx = 0;
tailBlock = directory[0];
}
/** {@inheritDoc} */
@Override
public T get(int index) {
if (index < 0 || size <= index)
throw new IndexOutOfBoundsException(String.valueOf(index));
return directory[toDirectoryIndex(index)][toBlockIndex(index)];
}
/** {@inheritDoc} */
@Override
public T set(int index, T element) {
if (index < 0 || size <= index)
throw new IndexOutOfBoundsException(String.valueOf(index));
T[] blockRef = directory[toDirectoryIndex(index)];
int blockIdx = toBlockIndex(index);
T old = blockRef[blockIdx];
blockRef[blockIdx] = element;
return old;
}
/**
* Quickly append all elements of another BlockList.
*
* @param src
* the list to copy elements from.
*/
public void addAll(BlockList<T> src) {
if (src.size == 0)
return;
int srcDirIdx = 0;
for (; srcDirIdx < src.tailDirIdx; srcDirIdx++)
addAll(src.directory[srcDirIdx], 0, BLOCK_SIZE);
if (src.tailBlkIdx != 0)
addAll(src.tailBlock, 0, src.tailBlkIdx);
}
/**
* Quickly append all elements from an array.
*
* @param src
* the source array.
* @param srcIdx
* first index to copy.
* @param srcCnt
* number of elements to copy.
*/
public void addAll(T[] src, int srcIdx, int srcCnt) {
while (0 < srcCnt) {
int i = tailBlkIdx;
int n = Math.min(srcCnt, BLOCK_SIZE - i);
if (n == 0) {
// Our tail is full, expand by one.
add(src[srcIdx++]);
srcCnt--;
continue;
}
System.arraycopy(src, srcIdx, tailBlock, i, n);
tailBlkIdx += n;
size += n;
srcIdx += n;
srcCnt -= n;
}
}
/** {@inheritDoc} */
@Override
public boolean add(T element) {
int i = tailBlkIdx;
if (i < BLOCK_SIZE) {
// Fast-path: Append to current tail block.
tailBlock[i] = element;
tailBlkIdx = i + 1;
size++;
return true;
}
// Slow path: Move to the next block, expanding if necessary.
if (++tailDirIdx == directory.length) {
T[][] newDir = BlockList.<T> newDirectory(directory.length << 1);
System.arraycopy(directory, 0, newDir, 0, directory.length);
directory = newDir;
}
T[] blockRef = directory[tailDirIdx];
if (blockRef == null) {
blockRef = BlockList.<T> newBlock();
directory[tailDirIdx] = blockRef;
}
blockRef[0] = element;
tailBlock = blockRef;
tailBlkIdx = 1;
size++;
return true;
}
/** {@inheritDoc} */
@Override
public void add(int index, T element) {
if (index == size) {
// Fast-path: append onto the end of the list.
add(element);
} else if (index < 0 || size < index) {
throw new IndexOutOfBoundsException(String.valueOf(index));
} else {
// Slow-path: the list needs to expand and insert.
// Do this the naive way, callers shouldn't abuse
// this class by entering this code path.
//
add(null); // expand the list by one
for (int oldIdx = size - 2; index <= oldIdx; oldIdx--)
set(oldIdx + 1, get(oldIdx));
set(index, element);
}
}
/** {@inheritDoc} */
@Override
public T remove(int index) {
if (index == size - 1) {
// Fast-path: remove the last element.
T[] blockRef = directory[toDirectoryIndex(index)];
int blockIdx = toBlockIndex(index);
T old = blockRef[blockIdx];
blockRef[blockIdx] = null;
size--;
if (0 < tailBlkIdx)
tailBlkIdx--;
else
resetTailBlock();
return old;
} else if (index < 0 || size <= index) {
throw new IndexOutOfBoundsException(String.valueOf(index));
} else {
// Slow-path: the list needs to contract and remove.
// Do this the naive way, callers shouldn't abuse
// this class by entering this code path.
//
T old = get(index);
for (; index < size - 1; index++)
set(index, get(index + 1));
set(size - 1, null);
size--;
resetTailBlock();
return old;
}
}
private void resetTailBlock() {
tailDirIdx = toDirectoryIndex(size);
tailBlkIdx = toBlockIndex(size);
tailBlock = directory[tailDirIdx];
}
/** {@inheritDoc} */
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
static final int toDirectoryIndex(int index) {
return index >>> BLOCK_BITS;
}
static final int toBlockIndex(int index) {
return index & BLOCK_MASK;
}
@SuppressWarnings("unchecked")
private static <T> T[][] newDirectory(int size) {
return (T[][]) new Object[size][];
}
@SuppressWarnings("unchecked")
private static <T> T[] newBlock() {
return (T[]) new Object[BLOCK_SIZE];
}
private class MyIterator implements Iterator<T> {
private int index;
private int dirIdx;
private int blkIdx;
private T[] block = directory[0];
@Override
public boolean hasNext() {
return index < size;
}
@Override
public T next() {
if (size <= index)
throw new NoSuchElementException();
T res = block[blkIdx];
if (++blkIdx == BLOCK_SIZE) {
if (++dirIdx < directory.length)
block = directory[dirIdx];
else
block = null;
blkIdx = 0;
}
index++;
return res;
}
@Override
public void remove() {
if (index == 0)
throw new IllegalStateException();
BlockList.this.remove(--index);
dirIdx = toDirectoryIndex(index);
blkIdx = toBlockIndex(index);
block = directory[dirIdx];
}
}
}