package de.skuzzle.polly.tools.iterators;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Iterator class to iterate over arrays. This {@link Iterator} implementation provides
* additional convenience methods for replacing the current value and iterating backwards.
* Using a for...each loop, this class will intentionally iterate a given array from
* the beginning to the end.
*
* @author Simon
* @param <T> Type of array elements.
*/
public class ArrayIterator<T> implements Iterable<T>, Iterator<T> {
public static <T> ArrayIterator<T> get(T[] array) {
return new ArrayIterator<T>(array, 0, array.length);
}
public static <T> ArrayIterator<T> forRange(T[] array, int start) {
return new ArrayIterator<T>(array, start, array.length);
}
public static <T> ArrayIterator<T> forRange(T[] array, int start, int end) {
return new ArrayIterator<T>(array, start, end);
}
private int i;
private int start;
private int end;
private T[] array;
/**
* Creates a new ArrayIterator
* @param array The array to iterate over
* @param start The inclusive index to begin iteration at.
* @param end The exclusive index to end iteration at.
* @throws IllegalArgumentException If start and end indices are out of array range.
*/
private ArrayIterator(T[] array, int start, int end) {
if (end > array.length) {
throw new IllegalArgumentException("end > length");
} else if (start < 0) {
throw new IllegalArgumentException("start < 0");
} else if (start > end) {
throw new IllegalArgumentException("start > end");
}
this.array = array;
this.start = start;
this.i = start;
this.end = end;
}
/**
* Moves iteration to the first element, so the next call to {@link #next()} returns
* the first element.
*/
public void first() {
this.i = this.start;
}
/**
* Moves iteration to the last element, so the next call to {@link #hasNext()}
* returns false.
*/
public void last() {
this.i = this.end;
}
/**
* Returns the next element without moving the iteration pointer.
* @return The element which will be returned by the next call to {@link #next()}.
*/
public T peekNext() {
if (!this.hasNext()) {
throw new NoSuchElementException("index " + this.i);
}
return this.array[this.i];
}
/**
* Determines whether there is an element next to current iteration position.
* @return <code>true</code> if iteration has not reached the end of the array.
*/
@Override
public boolean hasNext() {
return this.i < this.end;
}
/**
* Determines whether there is an element previous to current iteration position.
* @return <code>true</code> if iteration is not at the beginning and the array
* contains at least one element.
*/
public boolean hasPrevious() {
return this.i > 0;
}
/**
* Returns the next element in this iteration. If there are no more elements,
* an exception is thrown.
* @return The next array element.
* @throws NoSuchElementException If there are no more elements.
*/
@Override
public T next() {
if (!this.hasNext()) {
throw new NoSuchElementException("index " + this.i);
}
return this.array[this.i++];
}
/**
* Returns the previous element in this iteration, decreasing the current iteration
* pointer by one.
* @return The previous element.
* @throws NoSuchElementException If there is no previous element.
*/
public T previous() {
if (!this.hasPrevious()) {
throw new NoSuchElementException("index " + this.i);
}
return this.array[--i];
}
/**
* Returns the current element of the iteration. That is the same as returned by the
* preceded call to {@link #next()}.
*
* @return The current element of iteration.
*/
public T current() {
int index = Math.max(this.start, this.i - 1);
return this.array[index];
}
/**
* Replaces the element at the current position with the given value.
* @param other The new value to replace the current elements value with.
*/
public void replace(T other) {
int index = Math.max(this.start, i - 1);
this.array[index] = other;
}
/**
* This method is not supported, thus it will always throw an
* {@link UnsupportedOperationException}.
*/
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* Moves current iteration to given index. The next call of {@link #next()} will
* return the value at position i.
*
* @param i The new iteration position.
* @throws IllegalArgumentException If the given number is not in the range specified
* in the constructor.
*/
public void move(int i) {
this.checkRange(i);
this.i = i;
}
/**
* Returns the index of the current iteration.
* @return The iteration index.
*/
public int getIndex() {
return this.i;
}
/**
* Swaps the element at the current position with the next element.
* @throws NoSuchElementException If there is no next element.
*/
public void swapNext() {
if (!this.hasNext()) {
throw new NoSuchElementException("index " + (this.i + 1));
}
this.swap(this.i, this.i + 1);
}
/**
* Swaps the element at the current position with the previous element.
* @throws NoSuchElementException If there is no previous element.
*/
public void swapPrevious() {
if (!this.hasPrevious()) {
throw new NoSuchElementException("index " + (this.i - 1));
}
this.swap(this.i, Math.max(this.i - 1, this.start));
}
private void swap(int from, int with) {
this.checkRange(from);
this.checkRange(with);
T tmp = this.array[from];
this.array[from] = this.array[with];
this.array[with] = tmp;
}
private void checkRange(int i) {
if (i < this.start || i >= this.end) {
throw new IllegalArgumentException("Out of range. " + i);
}
}
@Override
public Iterator<T> iterator() {
return this;
}
}