/*
* 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.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
/**
* Bucket array list without random access. This is best used as stack.
*
* 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 adding, removing and traversal.
* - Speed of all operations (except clear()) independent of size.
*
* Limitations:
* - Only the last element can be removed!
* - Elements can only be added to the end.
*
* @author Tilmann Zaeschke
*/
public class BucketStack<E>
implements RandomAccess, java.io.Serializable, Iterable<E>
{
private static final long serialVersionUID = 8683452581122892189L;
//TODO use bucket List!
private final LinkedList<E[]> buckets = new LinkedList<E[]>();
/**
* The size of the BucketList (the number of elements it contains).
*/
private int size = 0;
private int cntInBucket;
private final int bucketSize;
//TODO this is not used much...
private int modCount = 0;
/**
* Constructs an empty list with an initial capacity of ten.
*/
public BucketStack() {
this(1000);
}
/**
* Constructs an empty list with an initial capacity of ten.
* @param bucketSize
*/
public BucketStack(int bucketSize) {
this.bucketSize = bucketSize;
cntInBucket = bucketSize; //special value for empty list
}
/**
* 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;
}
/**
* 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})
*/
@SuppressWarnings("unchecked")
public boolean push(E e) {
if (cntInBucket >= bucketSize) {
buckets.add((E[]) new Object[bucketSize]);
cntInBucket = 0;
}
buckets.getLast()[cntInBucket++] = e;
size++;
return true;
}
public E peek() {
return buckets.getLast()[cntInBucket-1];
}
/**
* 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 NoSuchElementException
*/
public E pop() {
modCount++;
if (size == 0) {
throw new NoSuchElementException();
}
size--;
if (cntInBucket == 1) {
cntInBucket = bucketSize;
return buckets.removeLast()[0];
}
return buckets.getLast()[--cntInBucket];
}
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
buckets.clear();
cntInBucket = bucketSize;
size = 0;
}
@Override
public Iterator<E> iterator() {
return new BucketIterator(buckets.iterator(), size % bucketSize, modCount);
}
/**
* 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 = bucketSize;
private E[] currentBucket;
private final Iterator<E[]> buckets;
private final int cntLast;
private final int modCountI;
/**
* @param max Maximum index = (size-1)
*/
private BucketIterator(Iterator<E[]> buckets, int cntLast, int modCountI) {
this.buckets = buckets;
this.cntLast = cntLast == 0 ? bucketSize : cntLast;
this.modCountI = modCountI;
}
@Override
public boolean hasNext() {
if (buckets.hasNext()) {
return true;
}
return pos < cntLast;
}
@Override
public E next() {
if (modCountI != modCount) {
throw new ConcurrentModificationException();
}
if (!hasNext()) {
throw new NoSuchElementException();
}
if (pos >= bucketSize) {
pos = 0;
currentBucket = buckets.next();
}
return currentBucket[pos++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}