/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB 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.
*
* ZooDB 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 ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
/**
* Bucket array list with fast random access by index. It has concurrency potential.
*
* See PerfIterator for performance considerations.
*
* Works like an ArrayList but is much faster when getting big, because the bucket architecture
* avoids copying the existing list to a new array as happens in the ArrayList.
*
* Features:
* - Fast insert, traversal and access by index.
* - Some potential as CONCURRENT collection, because an existing tree is only changed by adding
* elements to the end or by inserting a higher order root. But an existing section of the tree
* never changes (except clean() and remove(), see remove()), so iterators are inherently stable.
*
* Limitations:
* - Only the last element can be removed!
* - Elements can only be added to the end.
*
* @author Tilmann Zaeschke
*/
public class BucketTreeStack<E>
implements RandomAccess,
//java.io.Serializable,
Iterable<E>
{
// private static final long serialVersionUID = 8683452581122892189L;
private transient Object[] bucket;
/**
* The size of the BucketList (the number of elements it contains).
*/
private int size;
private final int bucketExp;
private final int bucketSize;
private int bucketDepth;
private int modCount = 0;
/**
* Constructs an empty list with an initial capacity of ten.
*/
public BucketTreeStack() {
this((byte) 10); //1024
}
/**
* Constructs an empty list with an initial capacity of ten.
* @param bucketExponent List buckets will contain 2^bucketExponent elements.
*/
public BucketTreeStack(byte bucketExponent) {
this.bucketExp = bucketExponent;
this.bucketSize = 1 << bucketExponent;
bucket = new Object[bucketSize];
}
/**
* Trims the capacity of this <tt>BucketArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>BucketArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
while ((bucketDepth > 0) && (bucketSize << ((bucketDepth-1)*bucketExp) >= size)) {
bucket = (Object[]) bucket[0];
}
}
/**
* Increases the capacity of this <tt>BucketArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
modCount++;
while (bucketSize << (bucketDepth*bucketExp) < minCapacity) {
Object oldData[] = bucket;
bucket = new Object[bucketSize];
bucket[0] = oldData;
bucketDepth++;
}
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}
/**
* Returns <tt>true</tt> if this list contains no elements.
*
* @return <tt>true</tt> if this list contains no elements
*/
public boolean isEmpty() {
return size == 0;
}
// Positional Access Operations
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException
*/
public E get(int index) {
rangeCheck(index);
return getElement(bucket, bucketDepth, index);
}
@SuppressWarnings("unchecked")
private E getElement(Object[] parent, int depth, int index) {
if (depth > 0) {
int pos = index >> (depth*bucketExp);
int index2 = ((1 << (depth*bucketExp))-1) & index;
return getElement((Object[]) parent[pos], depth-1, index2);
}
return (E) parent[index];
}
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param e element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException
*/
public E set(int index, E e) {
rangeCheck(index);
return addElement(bucket, bucketDepth, index, e);
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
addElement(bucket, bucketDepth, size++, e);
return true;
}
@SuppressWarnings("unchecked")
private E addElement(Object[] parent, int depth, int index, E e) {
if (depth > 0) {
int pos = index >> (depth*bucketExp);
int index2 = ((1 << (depth*bucketExp))-1) & index;
if (parent[pos] == null) {
parent[pos] = new Object[bucketSize];
}
return addElement((Object[]) parent[pos], depth-1, index2, e);
}
E old = (E) parent[index];
parent[index] = e;
return old;
}
/**
* Removes the element at the specified position in this list.
* Only the last element in the list can be removed.
*
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException
*/
public E removeLast() {
modCount++;
//TODO clean up reference?
//If we don't clean it up, we get a (small?) memory leak.
//If we clean it up, the iterator may not be stable anymore.
E obj = get(size - 1);
size--;
return obj;
// rangeCheck(index);
//
// modCount++;
//
// if (index == size - 1) {
// E obj = get(index);
// size--;
// return obj;
// }
// throw new UnsupportedOperationException();
}
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// this does not allocate more memory:
//TODO possibly faster: create new empty bucket? -> test speeds create vs set-to-null
for (int i = 0; i < bucketSize; i++) {
bucket[i] = null;
}
bucketDepth = 0;
size = 0;
}
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an IndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
@Override
public Iterator<E> iterator() {
return new BucketIterator(size - 1);
}
/**
* Iterator for BucketArrayList.
* Concurrency:
* - If clear() and remove() are not used, concurrency is simple, we just remember the current
* max and iterator to it. This is effectively a COW iterator. If clear() and remove() are
* used, we would have to make sure that the references are not deleted. For example:
* remove() keeps references; only clear() removes the whole tree (replace by new empty
* bucket); but the iterator could keep a reference to the tree as long as required.
* - We could make the iterator adaptive, so it only iterates to the end of the current list.
* This works nicely with clear() and remove(), but results in a somewhat unusual concurrency
* policy.
* TODO choose and implement.
*
* TODO
* Other improvement: may it be faster to store a stack here and keep a reference to the
* current bucket? Iteration should be faster for large pages, but Iterator creation is slower.
* But for large pages, using get to find them should be fast as well.
*/
private class BucketIterator implements Iterator<E> {
private int pos = 0;
private final int max;
/**
* @param max Maximum index = (size-1)
*/
private BucketIterator(int max) {
this.max = max;
}
@Override
public boolean hasNext() {
return pos <= max;
}
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return get(pos++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}