org.jtrim2.collections.RefLinkedList Maven / Gradle / Ivy
package org.jtrim2.collections;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractSequentialList;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.jtrim2.collections.RefList.ElementRef;
/**
* A doubly-linked list implementation of the {@link RefList} interface. This
* implementation is very similar to {@link java.util.LinkedList} but implements
* the more powerful {@code RefList} interface which allows users to take full
* advantage of the linked list. The performance is roughly the same as the
* the performance of {@code java.util.LinkedList}.
*
* This implementation allows {@code null} elements to be stored and implements
* all optional operations.
*
*
Thread safety
* Instances of this class are not safe to share across multiple concurrent
* threads if at least one of those threads modify the list. To modify this
* class concurrently, access to instances must be synchronized.
*
* Synchronization transparency
* Methods of this class are synchronization transparent, so they can be
* called in any context (e.g.: while holding a lock).
*
* Implementation notes
* This class does not implement fail-fast behaviour like most collection
* implementation. This will be fixed in the future.
*
* @param the type of the elements in this list
*/
public final class RefLinkedList
extends
AbstractSequentialList
implements
RefList, Deque, Serializable {
private static final long serialVersionUID = -5796509969934177884L;
private static final String REMOVED_REF
= "The reference was detached from the list.";
private static class LinkedRef implements ElementRef {
private RefLinkedList list;
private E element;
private LinkedRef prev;
private LinkedRef next;
public LinkedRef(RefLinkedList list, E element) {
this.list = list;
this.element = element;
}
@Override
public int getIndex() {
assert !isGuardElement()
: "The head and the tail element does not have an index.";
if (isRemoved()) {
throw new IllegalStateException(REMOVED_REF);
}
int index = 0;
LinkedRef currentRef = prev;
while (currentRef != list.head) {
currentRef = currentRef.prev;
index++;
}
return index;
}
@Override
public E setElement(E newElement) {
assert !isGuardElement()
: "The head and tail of the list cannot have an element.";
E oldElement = element;
element = newElement;
return oldElement;
}
@Override
public E getElement() {
assert !isGuardElement()
: "The head and tail of the list cannot have an element.";
return element;
}
@Override
public ListIterator getIterator() {
if (!isRemoved()) {
return new ReferenceIterator<>(list, this);
} else {
throw new IllegalStateException(REMOVED_REF);
}
}
private LinkedRef getNext() {
return next != list.tail ? next : null;
}
private LinkedRef getPrevious() {
return prev != list.head ? prev : null;
}
@Override
public LinkedRef getNext(int step) {
if (isRemoved()) {
return null;
}
if (step < 0) {
// Math.abs does not work on Integer.MIN_VALUE
if (step == Integer.MIN_VALUE) {
LinkedRef result = getPrevious(Integer.MAX_VALUE);
if (result != null) {
result = result.getPrevious();
}
return result;
}
return getPrevious(Math.abs(step));
} else {
LinkedRef result = this;
for (int i = 0; i < step && result != null; i++) {
result = result.getNext();
}
return result;
}
}
@Override
public LinkedRef getPrevious(int step) {
if (isRemoved()) {
return null;
}
if (step < 0) {
// Math.abs does not work on Integer.MIN_VALUE
if (step == Integer.MIN_VALUE) {
LinkedRef result = getNext(Integer.MAX_VALUE);
if (result != null) {
result = result.getNext();
}
return result;
}
return getNext(Math.abs(step));
} else {
LinkedRef result = this;
for (int i = 0; i < step && result != null; i++) {
result = result.getPrevious();
}
return result;
}
}
@Override
public void moveLast() {
if (isRemoved()) {
throw new IllegalStateException(REMOVED_REF);
}
if (next != list.tail) {
// detach
next.prev = prev;
prev.next = next;
// insert
prev = list.tail.prev;
next = list.tail;
list.tail.prev.next = this;
list.tail.prev = this;
}
}
@Override
public void moveFirst() {
if (isRemoved()) {
throw new IllegalStateException(REMOVED_REF);
}
if (prev != list.head) {
// detach
next.prev = prev;
prev.next = next;
// insert
prev = list.head;
next = list.head.next;
list.head.next.prev = this;
list.head.next = this;
}
}
private boolean moveBackwardOne() {
if (prev == list.head) {
return false;
} else {
LinkedRef prevRef = prev;
// detach
next.prev = prevRef;
prevRef.next = next;
// insert
prev = prevRef.prev;
next = prevRef;
prev.next = this;
next.prev = this;
return true;
}
}
@Override
public int moveBackward(int count) {
if (isRemoved()) {
throw new IllegalStateException(REMOVED_REF);
}
if (count < 0) {
// Math.abs does not work on Integer.MIN_VALUE
if (count == Integer.MIN_VALUE) {
int moveF1 = moveForward(Integer.MAX_VALUE);
int moveF2 = 0;
if (moveF1 == Integer.MAX_VALUE) {
moveF2 = moveForwardOne() ? 1 : 0;
}
return -moveF1 - moveF2;
}
return -moveForward(Math.abs(count));
}
for (int i = 0; i < count; i++) {
if (!moveBackwardOne()) {
return i;
}
}
return count;
}
private boolean moveForwardOne() {
if (next == list.tail) {
return false;
} else {
LinkedRef nextRef = next;
// detach
prev.next = nextRef;
nextRef.prev = prev;
// insert
prev = nextRef;
next = nextRef.next;
prev.next = this;
next.prev = this;
return true;
}
}
@Override
public int moveForward(int count) {
if (isRemoved()) {
throw new IllegalStateException(REMOVED_REF);
}
if (count < 0) {
// Math.abs does not work on Integer.MIN_VALUE
if (count == Integer.MIN_VALUE) {
int moveB1 = moveBackward(Integer.MAX_VALUE);
int moveB2 = 0;
if (moveB1 == Integer.MAX_VALUE) {
moveB2 = moveBackwardOne() ? 1 : 0;
}
return -moveB1 - moveB2;
}
return -moveBackward(Math.abs(count));
}
for (int i = 0; i < count; i++) {
if (!moveForwardOne()) {
return i;
}
}
return count;
}
@Override
public LinkedRef addAfter(E newElement) {
// Do not use isRemoved() because this method might be
// called on the head
if (list == null) {
throw new IllegalStateException(REMOVED_REF);
}
LinkedRef newRef = new LinkedRef<>(list, newElement);
newRef.next = next;
newRef.prev = this;
next.prev = newRef;
next = newRef;
list.size++;
return newRef;
}
@Override
public LinkedRef addBefore(E newElement) {
// Do not use isRemoved() because this method might be
// called on the tail
if (list == null) {
throw new IllegalStateException(REMOVED_REF);
}
LinkedRef newRef = new LinkedRef<>(list, newElement);
newRef.next = this;
newRef.prev = prev;
prev.next = newRef;
prev = newRef;
list.size++;
return newRef;
}
private boolean isConsistent() {
return (next != null && prev != null) || (next == prev);
}
private boolean isGuardElement() {
return list != null && (this == list.head || this == list.tail);
}
@Override
public boolean isRemoved() {
assert !isGuardElement()
: "isRemoved() is not defined on the head and tail"
+ " of the list.";
assert isConsistent()
: "Either next and previous element must be"
+ " null or neither of them.";
return next == null;
}
@Override
public void remove() {
assert !isGuardElement()
: "The head and the tail of the list cannot be removed.";
if (!isRemoved()) {
prev.next = next;
next.prev = prev;
prev = null;
next = null;
list.size--;
list = null;
}
}
}
private int size;
private final LinkedRef head;
private final LinkedRef tail;
/**
* Creates an empty list.
*/
public RefLinkedList() {
size = 0;
head = new LinkedRef<>(this, null);
tail = new LinkedRef<>(this, null);
head.prev = null;
head.next = tail;
tail.prev = head;
tail.next = null;
}
/**
* Creates a list containing the elements of the specified collection
* in the order its iterator returns them.
*
* @param collection the elements to be copied into the new list. This
* argument cannot be {@code null}.
*
* @throws NullPointerException thrown if the specified collection is
* {@code null}
*/
public RefLinkedList(Collection extends E> collection) {
Objects.requireNonNull(collection, "collection");
size = 0;
head = new LinkedRef<>(this, null);
tail = new LinkedRef<>(this, null);
head.prev = null;
head.next = tail;
tail.prev = head;
tail.next = null;
addAll(collection);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public int size() {
return size;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public boolean isEmpty() {
return size == 0;
}
private ElementRef findRawFirstReference(Object o) {
for (LinkedRef element = head.next;
element != tail;
element = element.next) {
E currentElement = element.element;
if (o == currentElement || o.equals(currentElement)) {
return element;
}
}
return null;
}
private ElementRef findRawLastReferece(Object o) {
for (LinkedRef element = tail.prev;
element != head;
element = element.prev) {
E currentElement = element.element;
if (o == currentElement || o.equals(currentElement)) {
return element;
}
}
return null;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public ElementRef findFirstReference(E element) {
return findRawFirstReference(element);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public ElementRef findLastReferece(E element) {
return findRawLastReferece(element);
}
/**
* Returns the reference to the element equivalent (based on the
* {@code equals} method) to the given element with the lowest index.
* This method is equivalent to the
* {@link #findFirstReference(java.lang.Object) findFirstReference} method.
*
* Implementation note: This operation may require linear time in the size
* of this list.
*
* @param element the reference to the element equivalent (based on the
* to the given element with the lowest index or {@code null} if it cannot
* be found
* @return {@inheritDoc }
*/
@Override
public ElementRef findReference(E element) {
return findFirstReference(element);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ElementRef getFirstReference() {
LinkedRef result = head.next;
if (result == tail) {
result = null;
}
return result;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ElementRef getLastReference() {
LinkedRef result = tail.prev;
if (result == head) {
result = null;
}
return result;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public ElementRef getReference(int index) {
return getInternalRef(index);
}
private LinkedRef getInternalRef(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(index
+ " is not within [0, " + (size - 1) + "]");
}
LinkedRef result;
if (index < size / 2) {
result = head;
for (int i = 0; i <= index; i++) {
result = result.next;
}
} else {
result = tail;
for (int i = size; i > index; i--) {
result = result.prev;
}
}
return result;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public boolean contains(Object o) {
return findRawFirstReference(o) != null;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public Iterator iterator() {
return new ReferenceIterator<>(this, head.next, 0);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public boolean add(E e) {
tail.addBefore(e);
return true;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public boolean remove(Object o) {
ElementRef ref = findRawFirstReference(o);
if (ref != null) {
ref.remove();
return true;
} else {
return false;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in the size
* of this list.
*/
@Override
public void clear() {
size = 0;
// We have to remove the references from the list
// so even if someoneelse calls remove() on them it does not
// corrupt this list.
LinkedRef ref = head.next;
while (ref != tail) {
LinkedRef nextRef = ref.next;
ref.next = null;
ref.prev = null;
ref = nextRef;
}
head.next = tail;
tail.prev = head;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public E get(int index) {
return getInternalRef(index).element;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public E set(int index, E element) {
LinkedRef ref = getInternalRef(index);
E oldValue = ref.element;
ref.element = element;
return oldValue;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ElementRef addFirstGetReference(E element) {
return head.addAfter(element);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ElementRef addLastGetReference(E element) {
return tail.addBefore(element);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ElementRef addGetReference(E element) {
return addLastGetReference(element);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public ElementRef addGetReference(int index, E element) {
if (index == size) {
return tail.addBefore(element);
} else {
return getReference(index).addBefore(element);
}
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public void add(int index, E element) {
addGetReference(index, element);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public E remove(int index) {
LinkedRef ref = getInternalRef(index);
ref.remove();
return ref.element;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public ListIterator listIterator() {
return new ReferenceIterator<>(this, head.next, 0);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation requires linear time in {@code index}
* or {@code size() - index} whichever is lower.
*/
@Override
public ListIterator listIterator(int index) {
LinkedRef startRef = index != size ? getInternalRef(index) : tail;
return new ReferenceIterator<>(this, startRef, index);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public void addFirst(E e) {
head.addAfter(e);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public void addLast(E e) {
tail.addBefore(e);
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public boolean offerFirst(E e) {
head.addAfter(e);
return true;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public boolean offerLast(E e) {
tail.addBefore(e);
return true;
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E removeFirst() {
LinkedRef first = head.next;
if (first != tail) {
first.remove();
return first.element;
} else {
throw new NoSuchElementException("The list is empty.");
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E removeLast() {
LinkedRef last = tail.prev;
if (last != head) {
last.remove();
return last.element;
} else {
throw new NoSuchElementException("The list is empty.");
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E pollFirst() {
LinkedRef first = head.next;
if (first != tail) {
first.remove();
return first.element;
} else {
return null;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E pollLast() {
LinkedRef last = tail.prev;
if (last != head) {
last.remove();
return last.element;
} else {
return null;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E getFirst() {
LinkedRef first = head.next;
if (first != tail) {
return first.element;
} else {
throw new NoSuchElementException("The list is empty.");
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E getLast() {
LinkedRef last = tail.prev;
if (last != head) {
return last.element;
} else {
throw new NoSuchElementException("The list is empty.");
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E peekFirst() {
LinkedRef first = head.next;
if (first != tail) {
return first.element;
} else {
return null;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This is constant time operation.
*/
@Override
public E peekLast() {
LinkedRef last = tail.prev;
if (last != head) {
return last.element;
} else {
return null;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
@SuppressWarnings("element-type-mismatch")
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public boolean removeLastOccurrence(Object o) {
ElementRef ref = findRawLastReferece(o);
if (ref != null) {
ref.remove();
return true;
} else {
return false;
}
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public boolean offer(E e) {
add(e);
return true;
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public E remove() {
return removeFirst();
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public E poll() {
return pollFirst();
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public E element() {
return getFirst();
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public E peek() {
return peekFirst();
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public void push(E e) {
addFirst(e);
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public E pop() {
return removeFirst();
}
/**
* {@inheritDoc }
*
* Implementation note: This operation may require linear time in the size
* of this list.
*/
@Override
public Iterator descendingIterator() {
return new DescItr<>(new ReferenceIterator<>(this, tail, size));
}
private Object writeReplace() throws ObjectStreamException {
return new SerializedFormat(toArray());
}
private void writeObject(ObjectOutputStream out) {
throw new UnsupportedOperationException("Only serializable through its proxy.");
}
private void readObject(ObjectInputStream in) {
throw new UnsupportedOperationException("Only serializable through its proxy.");
}
private static final class SerializedFormat implements Serializable {
private static final long serialVersionUID = -5787097173231889818L;
private final Object[] elements;
public SerializedFormat(Object[] elements) {
this.elements = elements;
}
// Although it is not safe, there is nothing we can do about it.
@SuppressWarnings("unchecked")
public RefLinkedList toList() {
RefLinkedList list = new RefLinkedList<>();
for (Object element: elements) {
list.addLast((E) element);
}
return list;
}
private Object readResolve() throws ObjectStreamException {
return toList();
}
}
private static class DescItr implements Iterator {
private final ListIterator listItr;
public DescItr(ListIterator listItr) {
this.listItr = listItr;
}
@Override
public boolean hasNext() {
return listItr.hasPrevious();
}
@Override
public T next() {
return listItr.previous();
}
@Override
public void remove() {
listItr.remove();
}
}
private static class ReferenceIterator implements ListIterator {
private final RefLinkedList list;
private LinkedRef lastRef;
private LinkedRef nextRef;
private int nextIndex;
private boolean mayRemove;
public ReferenceIterator(RefLinkedList list, LinkedRef startRef) {
this.list = list;
this.lastRef = null;
this.mayRemove = false;
this.nextRef = startRef;
this.nextIndex = startRef != list.tail
? startRef.getIndex()
: list.size();
}
public ReferenceIterator(
RefLinkedList list, LinkedRef startRef, int startIndex) {
this.list = list;
this.lastRef = null;
this.nextRef = startRef;
this.nextIndex = startIndex;
assert (startRef == list.tail && startIndex == list.size())
|| startIndex == startRef.getIndex();
}
@Override
public boolean hasNext() {
return nextRef != list.tail;
}
@Override
public E next() {
if (nextRef != list.tail) {
lastRef = nextRef;
nextRef = nextRef.next;
nextIndex++;
mayRemove = true;
return lastRef.getElement();
} else {
throw new NoSuchElementException("Last element was reached.");
}
}
@Override
public boolean hasPrevious() {
return nextRef.getPrevious() != null;
}
@Override
public E previous() {
if (hasPrevious()) {
lastRef = nextRef.prev;
nextRef = lastRef;
nextIndex--;
mayRemove = true;
return lastRef.getElement();
} else {
throw new NoSuchElementException("First element was reached.");
}
}
@Override
public int nextIndex() {
return nextIndex;
}
@Override
public int previousIndex() {
return nextIndex - 1;
}
@Override
public void remove() {
if (lastRef == null) {
throw new IllegalStateException();
}
if (!mayRemove) {
throw new IllegalStateException("add has been called since the last next/previous call.");
}
if (lastRef == nextRef) {
// if previous() was called
assert nextRef != list.tail;
nextRef = nextRef.next;
lastRef.remove();
} else {
// if next() was called
lastRef.remove();
nextIndex--;
}
lastRef = null;
}
@Override
public void set(E e) {
if (lastRef == null) {
throw new IllegalStateException();
}
lastRef.element = e;
}
@Override
public void add(E e) {
mayRemove = false;
nextRef.addBefore(e);
nextIndex++;
}
}
}