blob: c435417253392c9d4938b1dedd3d19c6bb6c55cd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.apache.pivot.collections;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.pivot.util.ListenerList;
import org.apache.pivot.util.Utils;
/**
* Implementation of the {@link List} interface that is backed by a linked list.
* <p> NOTE This class is not thread-safe. For concurrent access, use a
* {@link org.apache.pivot.collections.concurrent.SynchronizedList}.
*/
public class LinkedList<T> implements List<T>, Serializable {
private static final long serialVersionUID = 2100691224732602812L;
private static class Node<T> implements Serializable {
private static final long serialVersionUID = -848937850230412572L;
private Node<T> previous;
private Node<T> next;
private T item;
public Node(Node<T> previous, Node<T> next, T item) {
this.previous = previous;
this.next = next;
this.item = item;
}
}
private class LinkedListItemIterator implements ItemIterator<T> {
private int index = 0;
private Node<T> current = null;
private boolean forward = false;
private int modificationCountLocal;
public LinkedListItemIterator() {
modificationCountLocal = LinkedList.this.modificationCount;
}
@Override
public boolean hasNext() {
if (modificationCountLocal != LinkedList.this.modificationCount) {
throw new ConcurrentModificationException();
}
return (index < length);
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (current == null) {
current = first;
} else {
if (forward) {
current = current.next;
}
}
index++;
forward = true;
return current.item;
}
@Override
public boolean hasPrevious() {
if (modificationCountLocal != LinkedList.this.modificationCount) {
throw new ConcurrentModificationException();
}
return (index > 0);
}
@Override
public T previous() {
if (!hasPrevious()) {
throw new NoSuchElementException();
}
if (!forward) {
current = current.previous;
}
index--;
forward = false;
return current.item;
}
@Override
public void toStart() {
index = 0;
current = null;
forward = true;
}
@Override
public void toEnd() {
index = length;
current = last;
forward = true;
}
@Override
public void insert(T item) {
Node<T> next = null;
Node<T> previous = null;
if (length > 0) {
if (index == 0) {
// Insert at head
next = first;
// previous = null; // previous has already this value
} else if (index < length) {
if (forward) {
// Insert after current
next = current.next;
previous = current;
} else {
// Insert before current
next = current;
previous = current.previous;
}
} else {
// Insert at tail
// next = null; // next has already this value
previous = last;
}
verifyLocation(item, previous, next);
if (!forward) {
index++;
}
}
LinkedList.this.insert(item, previous, next);
length++;
modificationCountLocal++;
LinkedList.this.modificationCount++;
if (listListeners != null) {
listListeners.itemInserted(LinkedList.this, index);
}
}
@Override
public void update(T item) {
if (current == null) {
throw new IllegalStateException();
}
T previousItem = current.item;
if (previousItem != item) {
// Verify that the item is allowed at the specified index
verifyLocation(item, current.previous, current.next);
current.item = item;
modificationCountLocal++;
LinkedList.this.modificationCount++;
if (listListeners != null) {
listListeners.itemUpdated(LinkedList.this, index, previousItem);
}
}
}
@Override
public void remove() {
if (current == null) {
throw new IllegalStateException();
}
// Unlink the current node from its predecessor and successor
T item = current.item;
if (current.previous == null) {
first = current.next;
} else {
current.previous.next = current.next;
}
if (current.next == null) {
last = current.previous;
} else {
current.next.previous = current.previous;
}
if (forward) {
current = current.previous;
index--;
}
length--;
modificationCountLocal++;
LinkedList.this.modificationCount++;
if (listListeners != null) {
@SuppressWarnings("unchecked")
// or it will generate a warning during build with Java 7
LinkedList<T> removed = new LinkedList<>(item);
listListeners.itemsRemoved(LinkedList.this, index, removed);
}
}
}
private Node<T> first = null;
private Node<T> last = null;
private int length = 0;
private Comparator<T> comparator = null;
private transient int modificationCount = 0;
private transient ListListenerList<T> listListeners;
public LinkedList() {
}
public LinkedList(Comparator<T> comparator) {
this.comparator = comparator;
}
@SafeVarargs
public LinkedList(T... items) {
for (T item : items) {
add(item);
}
}
public LinkedList(Sequence<T> items) {
Utils.checkNull(items, "items");
for (int i = 0, n = items.getLength(); i < n; i++) {
add(items.get(i));
}
}
@Override
public int add(T item) {
int index;
if (comparator == null) {
index = length;
insert(item, index);
} else {
index = 0;
Node<T> next = null;
Node<T> previous = null;
if (length > 0) {
next = first;
while (next != null && comparator.compare(item, next.item) > 0) {
next = next.next;
index++;
}
previous = (next == null) ? last : next.previous;
}
insert(item, previous, next);
length++;
modificationCount++;
if (listListeners != null) {
listListeners.itemInserted(this, index);
}
}
return index;
}
@Override
public void insert(T item, int index) {
Utils.checkIndexBounds(index, 0, length);
Node<T> next = null;
Node<T> previous = null;
if (length > 0) {
next = (index == length) ? null : getNode(index);
previous = (next == null) ? last : next.previous;
verifyLocation(item, previous, next);
}
insert(item, previous, next);
length++;
modificationCount++;
if (listListeners != null) {
listListeners.itemInserted(this, index);
}
}
private void insert(T item, Node<T> previous, Node<T> next) {
Node<T> node = new Node<>(previous, next, item);
if (previous == null) {
first = node;
} else {
previous.next = node;
}
if (next == null) {
last = node;
} else {
next.previous = node;
}
}
@Override
public T update(int index, T item) {
Utils.checkIndexBounds(index, 0, length - 1);
// Get the previous item at index
Node<T> node = getNode(index);
T previousItem = node.item;
if (previousItem != item) {
// Verify that the item is allowed at the specified index
verifyLocation(item, node.previous, node.next);
// Update the item
node.item = item;
modificationCount++;
if (listListeners != null) {
listListeners.itemUpdated(this, index, previousItem);
}
}
return previousItem;
}
private void verifyLocation(T item, Node<T> previous, Node<T> next) {
if (comparator != null) {
// Ensure that the new item is greater or equal to its
// predecessor and less than or equal to its successor
if ((previous != null && comparator.compare(item, previous.item) < 0)
|| (next != null && comparator.compare(item, next.item) > 0)) {
throw new IllegalArgumentException("Updated or inserted item is not in correct sorted order.");
}
}
}
@Override
public int remove(T item) {
int index = 0;
LinkedListItemIterator nodeIterator = new LinkedListItemIterator();
while (nodeIterator.hasNext()) {
if (nodeIterator.next() == item) {
nodeIterator.remove();
break;
}
index++;
}
if (!nodeIterator.hasNext()) {
index = -1;
}
return index;
}
@Override
public Sequence<T> remove(int index, int count) {
Utils.checkIndexBounds(index, count, 0, length);
LinkedList<T> removed = new LinkedList<>();
if (count > 0) {
// Identify the bounding nodes and build the removed item list
Node<T> start = getNode(index);
Node<T> end = start;
for (int i = 0; i < count; i++) {
removed.add(end.item);
end = end.next;
}
if (end == null) {
end = last;
} else {
end = end.previous;
}
// Decouple the nodes from the list
if (start.previous != null) {
start.previous.next = end.next;
}
if (index + count == length) {
last = start.previous;
}
if (end.next != null) {
end.next.previous = start.previous;
}
if (index == 0) {
first = end.next;
}
start.previous = null;
end.next = null;
// Update length and notify listeners
length -= count;
modificationCount++;
if (listListeners != null) {
listListeners.itemsRemoved(this, index, removed);
}
}
return removed;
}
@Override
public void clear() {
if (length > 0) {
first = null;
last = null;
length = 0;
modificationCount++;
if (listListeners != null) {
listListeners.listCleared(this);
}
}
}
@Override
public T get(int index) {
Utils.checkIndexBounds(index, 0, length - 1);
Node<T> node = getNode(index);
return node.item;
}
private Node<T> getNode(int index) {
Node<T> node;
if (index == 0) {
node = first;
} else if (index == length - 1) {
node = last;
} else {
if (index < length / 2) {
node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
} else {
node = last;
for (int i = length - 1; i > index; i--) {
node = node.previous;
}
}
}
return node;
}
@Override
public int indexOf(T item) {
int index = 0;
Node<T> node = first;
while (node != null) {
if (item == null) {
if (node.item == null) {
break;
}
} else {
if (item.equals(node.item)) {
break;
}
}
node = node.next;
index++;
}
if (node == null) {
index = -1;
}
return index;
}
@Override
public boolean isEmpty() {
return (length == 0);
}
@Override
public int getLength() {
return length;
}
@Override
public Comparator<T> getComparator() {
return comparator;
}
@SuppressWarnings("unchecked")
@Override
public void setComparator(Comparator<T> comparator) {
Comparator<T> previousComparator = this.comparator;
if (comparator != null) {
// Copy the nodes into an array and sort
T[] array = (T[]) new Object[length];
int i = 0;
for (T item : this) {
array[i++] = item;
}
Arrays.sort(array, comparator);
// Rebuild the node list
first = null;
Node<T> node = null;
for (i = 0; i < length; i++) {
Node<T> previousNode = node;
node = new Node<>(previousNode, null, array[i]);
if (previousNode == null) {
first = node;
} else {
previousNode.next = node;
}
}
last = node;
modificationCount++;
}
// Set the new comparator
this.comparator = comparator;
if (listListeners != null) {
listListeners.comparatorChanged(this, previousComparator);
}
}
@Override
public ItemIterator<T> iterator() {
return new LinkedListItemIterator();
}
@Override
public ListenerList<ListListener<T>> getListListeners() {
if (listListeners == null) {
listListeners = new ListListenerList<>();
}
return listListeners;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
boolean equals = false;
if (this == o) {
equals = true;
} else if (o instanceof List) {
List<T> otherList = (List<T>) o;
if (length == otherList.getLength()) {
Iterator<T> iterator = otherList.iterator();
equals = true;
for (T element : this) {
if (!(iterator.hasNext() && element.equals(iterator.next()))) {
equals = false;
break;
}
}
}
}
return equals;
}
@Override
public int hashCode() {
int hashCode = 1;
for (T item : this) {
hashCode = 31 * hashCode + (item == null ? 0 : item.hashCode());
}
return hashCode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName());
sb.append(" [");
int i = 0;
for (T item : this) {
if (i > 0) {
sb.append(", ");
}
sb.append(item);
i++;
}
sb.append("]");
return sb.toString();
}
}