/*
* Copyright 2005 Red Hat, Inc. and/or its affiliates.
*
* 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.drools.core.util;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.NoSuchElementException;
/**
* This is a simple linked linked implementation. Each node must implement </code>LinkedListNode<code> so that it references
* the node before and after it. This way a node can be removed without having to scan the list to find it. This class
* does not provide an Iterator implementation as its designed for efficiency and not genericity. There are a number of
* ways to iterate the list.
* <p>
* Simple iterator:
* <pre>
* for ( LinkedListNode node = list.getFirst(); node != null; node = node.remove() ) {
* }
* </pre>
*
* Iterator that pops the first entry:
* <pre>
* for ( LinkedListNode node = list.removeFirst(); node != null; node = list.removeFirst() ) {
* }
* </pre>
*/
public class LinkedList<T extends LinkedListNode<T>>
implements
Externalizable {
private static final long serialVersionUID = 510l;
private T firstNode;
private T lastNode;
private int size;
public static final FastIterator fastIterator = new LinkedListFastIterator(); // contains no state, so ok to be static
/**
* Construct an empty <code>LinkedList</code>
*/
public LinkedList() {
}
public LinkedList(final T node) {
this.firstNode = node;
this.lastNode = node;
this.size++;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstNode = (T)in.readObject();
lastNode = (T)in.readObject();
size = in.readInt();
T current = firstNode;
T previous = null;
while ( current != lastNode) {
T next = (T) in.readObject();
current.setPrevious(previous);
current.setNext(next);
previous = current;
current = next;
}
// current equals last Node, so set previous (this avoids the null writting in stream
current.setPrevious( previous );
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(firstNode);
out.writeObject(lastNode);
out.writeInt(size);
if ( firstNode == lastNode ) {
// no other nodes
return;
}
for (T node = firstNode; node != null; node = node.getNext()) {
out.writeObject(node.getNext());
}
}
/**
* Add a <code>LinkedListNode</code> to the list. If the <code>LinkedList</code> is empty then the first and
* last nodes are set to the added node.
*
* @param node
* The <code>LinkedListNode</code> to be added
*/
public void add(final T node) {
if ( this.firstNode == null ) {
this.firstNode = node;
this.lastNode = node;
} else {
this.lastNode.setNext( node );
node.setPrevious( this.lastNode );
this.lastNode = node;
}
this.size++;
}
/**
* Add a <code>LinkedListNode</code> to the end of the list. If the <code>LinkedList</code> is empty then the first and
* last nodes are set to the added node.
*
* @param node
* The <code>LinkedListNode</code> to be added
*/
public void addLast(final T node) {
if ( this.firstNode == null ) {
this.firstNode = node;
this.lastNode = node;
} else {
T currentLast = this.lastNode;
currentLast.setNext( node );
node.setPrevious( currentLast );
this.lastNode = node;
}
this.size++;
}
public void addFirst(final T node) {
if ( this.firstNode == null ) {
this.firstNode = node;
this.lastNode = node;
} else {
T currentFirst = this.firstNode;
currentFirst.setPrevious( node );
node.setNext( currentFirst );
this.firstNode = node;
}
this.size++;
}
/**
* Removes a <code>LinkedListNode</code> from the list. This works by attach the previous reference to the child reference.
* When the node to be removed is the first node it calls <code>removeFirst()</code>. When the node to be removed is the last node
* it calls <code>removeLast()</code>.
*
* @param node
* The <code>LinkedListNode</code> to be removed.
*/
public void remove(final T node) {
if ( this.firstNode == node ) {
removeFirst();
} else if ( this.lastNode == node ) {
removeLast();
} else {
node.getPrevious().setNext( node.getNext() );
(node.getNext()).setPrevious( node.getPrevious() );
this.size--;
node.setPrevious( null );
node.setNext( null );
}
}
public boolean contains(T node) {
for (T currentNode = firstNode; currentNode != null; currentNode = currentNode.getNext()) {
if (currentNode == node) {
return true;
}
}
return false;
}
/**
* Return the first node in the list
* @return
* The first <code>LinkedListNode</code>.
*/
public final T getFirst() {
return this.firstNode;
}
/**
* Return the last node in the list
* @return
* The last <code>LinkedListNode</code>.
*/
public final T getLast() {
return this.lastNode;
}
/**
* Remove the first node from the list. The next node then becomes the first node. If this is the last
* node then both first and last node references are set to null.
*
* @return
* The first <code>LinkedListNode</code>.
*/
public T removeFirst() {
if ( this.firstNode == null ) {
return null;
}
final T node = this.firstNode;
this.firstNode = node.getNext();
node.setNext( null );
if ( this.firstNode != null ) {
this.firstNode.setPrevious( null );
} else {
this.lastNode = null;
}
this.size--;
return node;
}
public void insertAfter(final T existingNode,
final T newNode) {
if ( newNode.getPrevious() != null || newNode.getNext() != null ) {
//do nothing if this node is already inserted somewhere
return;
}
if ( existingNode == null ) {
if ( this.isEmpty() ) {
this.firstNode = newNode;
this.lastNode = newNode;
} else {
// if existing node is null, then insert it as a first node
final T node = this.firstNode;
node.setPrevious( newNode );
newNode.setNext( node );
this.firstNode = newNode;
}
} else if ( existingNode == this.lastNode ) {
existingNode.setNext( newNode );
newNode.setPrevious( existingNode );
this.lastNode = newNode;
} else {
(existingNode.getNext()).setPrevious( newNode );
newNode.setNext( existingNode.getNext() );
existingNode.setNext( newNode );
newNode.setPrevious( existingNode );
}
this.size++;
}
/**
* Remove the last node from the list. The previous node then becomes the last node. If this is the last
* node then both first and last node references are set to null.
*
* @return
* The first <code>LinkedListNode</code>.
*/
public T removeLast() {
if ( this.lastNode == null ) {
return null;
}
final T node = this.lastNode;
this.lastNode = node.getPrevious();
node.setPrevious( null );
if ( this.lastNode != null ) {
this.lastNode.setNext( null );
} else {
this.firstNode = null;
}
this.size--;
return node;
}
public T get(int i) {
T current = getFirst();
for ( int j = 0; j < i; j++ ) {
current = current.getNext();
}
return current;
}
/**
* @return
* boolean value indicating the empty status of the list
*/
public final boolean isEmpty() {
return (this.firstNode == null);
}
/**
* Iterates the list removing all the nodes until there are no more nodes to remove.
*/
public void clear() {
while ( removeFirst() != null ) {
}
}
/**
* @return
* return size of the list as an int
*/
public final int size() {
return this.size;
}
public int hashCode() {
final int PRIME = 31;
int result = 1;
for ( T node = this.firstNode; node != null; node = node.getNext() ) {
result = PRIME * result + node.hashCode();
}
return result;
}
public boolean equals(final Object object) {
if ( object == this ) {
return true;
}
if ( !(object instanceof LinkedList) ) {
return false;
}
final LinkedList<T> other = (LinkedList<T>) object;
if ( this.size() != other.size() ) {
return false;
}
for ( T thisNode = this.firstNode, otherNode = other.firstNode; thisNode != null && otherNode != null; thisNode = thisNode.getNext(), otherNode = otherNode.getNext() ) {
if ( !thisNode.equals( otherNode ) ) {
return false;
}
}
return true;
}
public FastIterator iterator() {
return fastIterator();
}
public FastIterator fastIterator() {
return fastIterator;
}
public static class LinkedListFastIterator implements FastIterator {
public Entry next(Entry object) {
return object.getNext();
}
public boolean isFullIterator() {
return false;
}
}
public java.util.Iterator<T> javaUtilIterator() {
return new JavaUtilIterator<T>( this );
}
/**
* Returns a list iterator
* @return
*/
public static class LinkedListIterator<T extends LinkedListNode<T>>
implements
Iterator<T>,
Externalizable {
private LinkedList<T> list;
private T current;
public void reset(final LinkedList<T> list) {
this.list = list;
this.current = this.list.firstNode;
}
public T next() {
if ( this.current == null ) {
return null;
}
final T node = this.current;
this.current = this.current.getNext();
return node;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
list = (LinkedList<T>)in.readObject();
current = (T)in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(list);
out.writeObject(current);
}
}
public static class JavaUtilIterator<T extends LinkedListNode<T>>
implements
java.util.Iterator<T>,
Externalizable {
private LinkedList<T> list;
private T currentNode;
private T nextNode;
private boolean immutable;
public JavaUtilIterator() {
}
public JavaUtilIterator(final LinkedList<T> list) {
this( list,
true );
}
public JavaUtilIterator(final LinkedList<T> list,
final boolean immutable) {
this.list = list;
this.nextNode = this.list.getFirst();
this.immutable = immutable;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
list = (LinkedList<T>)in.readObject();
currentNode = (T)in.readObject();
nextNode = (T)in.readObject();
immutable = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(list);
out.writeObject(currentNode);
out.writeObject(nextNode);
out.writeBoolean(immutable);
}
public boolean hasNext() {
return (this.nextNode != null);
}
public T next() {
this.currentNode = this.nextNode;
if ( this.currentNode != null ) {
this.nextNode = this.currentNode.getNext();
} else {
throw new NoSuchElementException( "No more elements to return" );
}
return this.currentNode;
}
public void remove() {
if ( this.immutable ) {
throw new UnsupportedOperationException( "This Iterator is immutable, you cannot call remove()" );
}
if ( this.currentNode != null ) {
this.list.remove( this.currentNode );
this.currentNode = null;
} else {
throw new IllegalStateException( "No item to remove. Call next() before calling remove()." );
}
}
}
}