/*
* xtc - The eXTensible Compiler
* Copyright (C) 2011 New York University
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.util;
import java.util.AbstractSequentialList;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* A doubly-linked list. This implementation allows users to access
* the list element's containers which is analogous to working with
* pointers to linked-list nodes.
*
* @author Paul Gazzillo
* @version $Revision: 1.8 $
*
*/
public class LinkedList<D> extends AbstractSequentialList<D> {
/** The beginning of the list. */
final private Element head;
/** The end of the list. */
final private Element tail;
/** The current size of the list. */
private int size;
/** Construct a new doubly-linked list. */
public LinkedList() {
this.size = 0;
this.head = new Element(null, null, null);
this.tail = new Element(null, null, null);
this.head.next = this.tail;
this.tail.previous = this.head;
}
/**
* The state of iteration in an anonymous ListIterator class. Used
* for set() and remove() which cannot be used until next() or
* previous() has been called and not after remove() or add() has
* been called.
*/
private static enum IteratorState {
// Neither next() or previous() has been called yet, or
// remove() or set() was called.
NONE,
// The next() method was called last.
NEXT,
// The previous() method was called last.
PREVIOUS,
}
/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
*
* See {@link java.util.AbstractSequentialList#listIterator(int)}
* for more information.
*
* @param index The first element returned by iterator.next(). Note
* that index is marked "final" for use in the anonymous class
* returned by this method.
* @return A list iterator over the elements in this list.
*/
public ListIterator<D> listIterator(final int index) {
if (index < 0 || index > size) throw new IndexOutOfBoundsException();
// Find the container starting at the given index.
Element c = head;
for (int i = 0; i < index; i++) {
c = c.next;
}
// The element on which the iterator begin. It's "final" for the
// following anonymous class below.
final Element startCursor = c;
return new ListIterator<D>() {
/**
* The cursor position. It is the index of the element just
* before the next() element.
*/
private int cursorPosition = index - 1;
/** The cursor. It points to the container just before next. */
private Element cursor = startCursor;
private IteratorState state = IteratorState.NONE;
public void add(D e) {
// Add an element immediately before the next() element.
Element newElement = new Element(e, cursor, cursor.next);
newElement.previous.next = newElement;
newElement.next.previous = newElement;
size++;
cursor = newElement;
cursorPosition++;
state = IteratorState.NONE;
}
public boolean hasNext() {
return cursor.next != tail;
}
public boolean hasPrevious() {
return cursor != head;
}
public D next() {
// Return next and move cursor position
if (! hasNext()) {
throw new NoSuchElementException();
} else {
cursor = cursor.next;
cursorPosition++;
state = IteratorState.NEXT;
return cursor.data;
}
}
public int nextIndex() {
if (hasNext()) {
return cursorPosition + 1;
} else {
// If there is no next index, return the size per
// ListIterator javadoc.
return size;
}
}
public D previous() {
if (! hasPrevious()) {
throw new NoSuchElementException();
} else {
Element previous = cursor;
cursor = cursor.previous;
cursorPosition--;
state = IteratorState.PREVIOUS;
return previous.data;
}
}
public int previousIndex() {
if (hasPrevious()) {
return cursorPosition;
} else {
// If there is no next index, return -1 per the
// ListIterator javadoc.
return -1;
}
}
public void remove() {
switch (state) {
case NEXT:
// "cursor" holds the last element that was returned by
// next()
cursor.previous.next = cursor.next;
cursor.next.previous = cursor.previous;
cursor = cursor.previous;
cursorPosition--;
size--;
state = IteratorState.NONE;
break;
case PREVIOUS:
// "cursor.next" holds the last element that was returned by
// previous()
Element removed = cursor.next;
removed.previous.next = removed.next;
removed.next.previous = removed.previous;
cursor = removed.previous;
size--;
state = IteratorState.NONE;
break;
default:
// An illegal state exception per the ListIterator
// specification.
throw new IllegalStateException();
}
}
public void set(D e) {
switch (state) {
case NEXT:
// "cursor" holds the last element that was returned by
// next()
cursor.data = e;
break;
case PREVIOUS:
// "cursor.next" holds the last element that was returned by
// previous()
cursor.next.data = e;
break;
default:
// An illegal state exception per the ListIterator
// specification.
throw new IllegalStateException();
}
}
};
}
public int size() {
return size;
}
/**
* Get the first element's container.
*
* @return The first element's container.
*/
public Element getFirst() {
if (size <= 0) throw new NoSuchElementException();
return head.next;
}
/**
* Get the last element's container.
*
* @return The last element's container.
*/
public Element getLast() {
if (size <= 0) throw new NoSuchElementException();
return tail.previous;
}
/**
* Add a new element to the end of the list.
*
* @param data The new element.
* @return The container for the new element.
*/
public Element addLast(D data) {
Element added = new Element(data, tail.previous, tail);
added.previous.next = added;
added.next.previous = added;
size++;
return added;
}
/**
* Add a new element to the beginning of the list.
*
* @param data The new element.
* @return The container for the new element.
*/
public Element addFirst(D data) {
Element added = new Element(data, head, head.next);
added.previous.next = added;
added.next.previous = added;
size++;
return added;
}
/**
* Remove the first element in the list.
*
* @return The element.
*/
public D removeFirst() throws NoSuchElementException {
if (size <= 0) throw new NoSuchElementException();
Element removed = head.next;
removed.next.previous = removed.previous;
removed.previous.next = removed.next;
removed.next = null;
removed.previous = null;
size--;
return removed.data;
}
/**
* Remove the last element in the list.
*
* @return The element.
*/
public D removeLast() throws NoSuchElementException {
if (size <= 0) throw new NoSuchElementException();
Element removed = tail.previous;
removed.next.previous = removed.previous;
removed.previous.next = removed.next;
removed.next = null;
removed.previous = null;
size--;
return removed.data;
}
/**
* A container for one element of the doubly-linked list.
*/
public class Element {
/** The element's data. */
private D data;
/** The previous element in the list. */
private Element previous;
/** The next element in the list. */
private Element next;
/**
* Create a new container for a list element. New containers are
* only created by the list modifier methods such as add.
*
* @param data The element data.
* @param previous The previous element in the list.
* @param next The next element in the list.
*/
private Element(D data, Element previous, Element next) {
this.data = data;
this.previous = previous;
this.next = next;
}
/**
* Return the container's data.
*
* @return The container's data.
*/
public D data() {
return this.data;
}
/**
* Get the list that this container is a part of.
*
* @return The list.
*/
public LinkedList<D> list() {
return LinkedList.this;
}
/**
* Add a new element after this one.
*
* @param data The element's value.
* @return The container for the new element.
*/
public Element addAfter(D data) {
Element added = new Element(data, this, this.next);
added.previous.next = added;
added.next.previous = added;
size++;
return added;
}
/**
* Add a new element before this one.
*
* @param data The element's value.
* @return The container for the new element.
*/
public Element addBefore(D data) {
Element added = new Element(data, this.previous, this);
added.previous.next = added;
added.next.previous = added;
size++;
return added;
}
/**
* Get the next container in the list.
*
* @return The next container.
*/
public Element next() throws NoSuchElementException {
if (size <= 0
|| tail == next) {
throw new NoSuchElementException();
}
return next;
}
/**
* Get the previous container in the list.
*
* @return The previous container.
*/
public Element previous() throws NoSuchElementException {
if (size <= 0
|| head == previous) {
throw new NoSuchElementException();
}
return previous;
}
/**
* Returns true if this is the last element in the list.
*
* @return true if this is the last element in the list.
*/
public boolean isLast() {
return tail == this.next;
}
/**
* Returns true if this is the first element in the list.
*
* @return true if this is the first element in the list.
*/
public boolean isFirst() {
return head == this.previous;
}
/**
* Remove the container from the list.
*/
public void remove() {
Element next = this.next;
this.previous.next = this.next;
this.next.previous = this.previous;
this.next = null;
this.previous = null;
size--;
}
}
}