/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.utilities.collection;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A circular/cyclic/ring buffer. Adding elements is allowed only at the end of the buffer.
* Removing elements can be done through {@link #popFirst()} and {@link #popLast()} or {@link #remove(int)}.
*/
public final class CircularBuffer<T> extends AbstractList<T> {
private final T[] buffer;
private int startIndex;
private int occupancy;
private CircularBuffer(int length) {
buffer = (T[]) new Object[length];
}
public static <T> CircularBuffer<T> create(int length) {
return new CircularBuffer<>(length);
}
@Override
public T get(int index) {
if (index < 0 || index >= occupancy) {
throw new IndexOutOfBoundsException();
}
return buffer[calculateIndex(index)];
}
@Override
public T set(int index, T element) {
if (index < 0 || index >= occupancy) {
throw new IndexOutOfBoundsException();
}
int bufIndex = calculateIndex(index);
T prev = buffer[bufIndex];
buffer[bufIndex] = element;
return prev;
}
/**
* @return the last element in the buffer
*/
public T getLast() {
return get(occupancy - 1);
}
@Override
public boolean add(T item) {
buffer[(startIndex + occupancy) % buffer.length] = item;
if (occupancy < buffer.length) {
occupancy++;
} else {
startIndex = (startIndex + 1) % buffer.length;
}
return true;
}
@Override
public T remove(int idx) {
if (idx < 0 || idx >= occupancy) {
throw new IndexOutOfBoundsException();
}
T old = buffer[calculateIndex(idx)];
// shift all elements that are on the right side of element by one
for (int i = idx; i < occupancy - 1; i++) {
int thisIdx = calculateIndex(i);
int nextIdx = calculateIndex(i + 1);
buffer[thisIdx] = buffer[nextIdx];
}
buffer[calculateIndex(occupancy - 1)] = null;
occupancy--;
return old;
}
/**
* @return the first element in the buffer
*/
public T getFirst() {
return buffer[startIndex];
}
/**
* Removes the first element from the buffer
* @return the first element
*/
public T popFirst() {
T result = buffer[startIndex];
buffer[startIndex] = null;
startIndex++;
occupancy--;
return result;
}
/**
* Removes the last element from the buffer
* @return the last element
*/
public T popLast() {
int index = calculateIndex(occupancy - 1);
T result = buffer[index];
buffer[index] = null;
occupancy--;
return result;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public int size() {
return occupancy;
}
/**
* @return the maximum size of the buffer
*/
public int capacity() {
return buffer.length;
}
private int calculateIndex(int relativeIndex) {
return (relativeIndex + startIndex) % buffer.length;
}
@Override
public Iterator<T> iterator() {
return new BufferIterator();
}
@Override
public void clear() {
occupancy = 0;
startIndex = 0;
}
private class BufferIterator implements Iterator<T> {
private int index;
private int prevIndex = -1;
@Override
public boolean hasNext() {
return index < occupancy;
}
@Override
public T next() {
if (index >= occupancy) {
throw new NoSuchElementException();
}
prevIndex = index;
return get(index++);
}
@Override
public void remove() {
if (prevIndex >= 0) {
CircularBuffer.this.remove(prevIndex);
index = prevIndex;
prevIndex = -1;
} else {
throw new IllegalStateException();
}
}
}
}