/*
* [LinkedList.java]
*
* Summary: Generified Linked List implementation that is similar to but predates Sun's LinkedList.
*
* Copyright: (c) 2006-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.8+
*
* Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
*
* Version History:
* 1.0 1997-05-13 initial release, not validated
* 1.1 1997-05-30 Test harness, enumerator, toVector
* 1.2 1998-03-16 fix bug in insertElement if there is only one element in the
* list already.
* 1.3 1998-06-24 add JavaDOC
* 1.4 1998-11-10 new address
* 1.5 2008-01-01 move to its own package.
* 1.6 2008-02-18 generify
*/
/* LinkedList.java contains LinkedList, Link and LinkedListEnumerator. */
package com.mindprod.linkedlist;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Vector;
import static java.lang.System.*;
/**
* Generified Linked List implementation that is similar to but predates Sun's LinkedList.
*
* A doubly linked list with forward and back References (safe pointers). The LinkedList maintains a cursor pointing
* to the current element. This class behaves much like Vector, except that is implemented differently. LinkedList is
* better designed than Vector for insert/delete, but worse for indexing. LinkedList has the unfortunate overhead of
* creating a little link object for each element. This was written back in Java 1.0 days, before there was a
* Collections or List interface, much less generics, so it does not conform. This is mainly a teaching example how you
* might implement a LinkedList. Sun since implemented their own LinkedList
*
* @author Roedy Green, Canadian Mind Products
* @version 1.6 2008-02-18 generify
* @since 1997-05-13
*/
@SuppressWarnings( { "WeakerAccess" } )
public final class LinkedList
{
private static final int FIRST_COPYRIGHT_YEAR = 2006;
/**
* undisplayed copyright notice
*/
@SuppressWarnings( { "UnusedDeclaration" } )
private static final String EMBEDDED_COPYRIGHT =
"Copyright: (c) 2006-2017 Roedy Green, Canadian Mind Products, http://mindprod.com";
/**
* when package was released.
*
* @noinspection UnusedDeclaration
*/
private static final String RELEASE_DATE = "2008-02-18";
/**
* version of package.
*
* @noinspection UnusedDeclaration
*/
private static final String VERSION_STRING = "1.6";
/**
* Reference to the current item on the list. The only time it can be null is if the list is empty.
*/
protected Link current = null;
/**
* Reference to first element on the list.
*/
protected Link head = null;
/**
* Reference to the last element on the list.
*/
protected Link tail = null;
/**
* The list cursor, "indexes" the current element, though there is no array.
-1 = empty list
0 =
* current is first element on the list
1 = current is second element on the list...
Once the list has
* elements, the cursor always points to an element. It is never -1.
*/
protected int cursor = -1;
/**
* Number of elements in the List. Access publicly via isEmpty() or size().
*/
protected int elementCount = 0;
/**
* LinkedList constructor
*/
public LinkedList()
{
}
/**
* LinkedList constructor, convert from Vector.
*
* @param v a Vector of objects that will be add to this list. The original Vector will be undisturbed.
* We use Vector instead of ArrayList or List because of the extreme age of this code. They did not
* exist at the time.
*/
public LinkedList( Vector v )
{
synchronized ( v )
{
for ( E element : v )
{
addElement( element );
}
}
} // end LinkedList constructor
/**
* Convert the list to Vector form. This does not clone the elements. It just creates a Vector of references. The
* original list is intact. There is no automatic synchronisation between the two if you add more elements to the
* list.
*
* @return a Vector of the list elements in order. The Vector has no spare space.
*/
final synchronized Vector toVector()
{
Vector v = new Vector<>( elementCount );
for ( Enumeration e = elements(); e.hasMoreElements(); )
{
v.addElement( e.nextElement() );
}
return v;
} // end toVector
/**
* Append an element to the end of the list. When done, the cursor points to the newly added element.
*
* @param obj element to add to the list.
*/
public final synchronized void addElement( E obj )
{
elementCount++;
cursor++;
current = new Link<>( obj, tail, null );
if ( tail == null )
{
head = current;
}
else
{
tail.next = current;
}
tail = current;
} // end addElement
/**
* Debugging tool. Checks the list for consistent structure.
*/
public final void consistency()
{
if ( elementCount == 0 )
{
out.println( "LinkedList empty" );
return;
}
Link stepper = head;
Link was = null;
for ( int i = 0; i < elementCount; i++ )
{
if ( stepper.prev != was )
{
out.println( "bad prev Reference" );
}
was = stepper;
if ( stepper.next == null && tail != stepper )
{
out.println( "bad tail" );
}
stepper = stepper.next;
}
out.println( "LinkedList ok with " + elementCount + " entries" );
} // consistency
/**
* Get element from list.
*
* @param index 0 = retrieve first element, 1 = second element etc.
*
* @return the element at that index, null if the list is empty.
* @throws ArrayIndexOutOfBoundsException if index does not reference an existing element.
*/
public final synchronized E elementAt( int index ) throws ArrayIndexOutOfBoundsException
{
setCursor( index );
return current.element;
} // end elementAt
/**
* Generate an enumeration of the list.
*
* @return Enumeration that will cycle through all the list elements in order.
*/
public final synchronized Enumeration elements()
{
return new LinkedListEnumerator<>( this );
} // end elements
/**
* Get the first element.
*
* @return the first element in the list, null if there is none.
*/
public final synchronized E firstElement()
{
return ( head == null ) ? null : head.element;
} // end firstElement
/**
* Get cursor position.
*
* @return -1 = empty, 0 = first element
*/
public final synchronized int getCursor()
{
return cursor;
} // end getCursor
/**
* Set list cursor to an existing element.
*
* @param index = 0 to point to the first element, = 1 to the second etc. You can't set the cursor when the list is
* empty.
*
* @throws ArrayIndexOutOfBoundsException if index does not reference an existing element.
*/
@SuppressWarnings( { "UnnecessaryReturnStatement", "SillyAssignment" } )
public final synchronized void setCursor( int index ) throws ArrayIndexOutOfBoundsException
{
if ( index < 0 || index >= elementCount )
{
throw new ArrayIndexOutOfBoundsException();
}
if ( index == cursor )
{
return;
}
// Chase the chain to find that element.
// We can chase head forward, current back, current forward or tail back.
// Figure out which would be fastest.
// The most common case is moving one ahead or one back. Handle those
// cases specially.
else if ( index < cursor )
{
if ( index == cursor - 1 )
{
current = current.prev;
cursor = index;
}
else if ( index < cursor / 2 )
{
// chase head forward
current = head;
// chase forward from cursor, possibly zero steps.
for ( cursor = 0; cursor < index; cursor++ )
{
current = current.next;
}
// cursor will equal index
}
else
{
// chase current back
for ( ; cursor > index; cursor-- )
{
current = current.prev;
}
// cursor will equal index
}
}
else if ( index == cursor + 1 )
{
current = current.next;
cursor = index;
}
else if ( index < cursor + ( elementCount - cursor ) / 2 )
{
// chase forward from cursor, possibly zero steps.
for ( ; cursor < index; cursor++ )
{
current = current.next;
}
// cursor will equal index
}
else
{
// chase tail back
current = tail;
// chase backward from cursor, possibly zero steps.
for ( cursor = elementCount - 1; cursor > index; cursor-- )
{
current = current.prev;
}
// cursor will equal index
}
} // end setCursor
/**
* Get current element.
*
* @return element pointed to by the list cursor, null if the list is empty.
*/
public final synchronized E getElement()
{
return ( current == null ) ? null : current.element;
} // end getElement
/**
* Replace the current element. Change the link for the current element to point to a different object.
*
* @param obj the new object to replace the current element.
*
* @throws ArrayIndexOutOfBoundsException if the list is empty
*/
public final synchronized void setElement( E obj ) throws ArrayIndexOutOfBoundsException
{
if ( current == null )
{
throw new ArrayIndexOutOfBoundsException();
}
current.element = obj;
} // end setElement
/**
* Search the list for the given object. A match is based on address equality not comparing fields.
*
* @param obj the element to search for.
*
* @return 0 = first element, 1 = second element etc., -1 = not found.
*/
public final synchronized int indexOf( E obj )
{
// chase chain from one end to the other looking for it.
// Perhaps this should be done with .equals instead of ==
Link chase = head;
for ( int i = 0; i < elementCount; i++ )
{
if ( chase.element == obj )
{
return i;
}
chase = chase.next;
}
// did not find it
return -1;
} // end indexOf
/**
* Insert an element just ahead of the current element. When done, the cursor points to the newly added element.
*
* @param obj element to add to the list.
*/
public final synchronized void insertElement( E obj )
{
if ( current == null )
{
addElement( obj );
}
else
{
elementCount++;
// cursor stays the same
Link succ = current;
Link pred = current.prev;
current = new Link<>( obj, pred, succ );
// succ can't be null.
succ.prev = current;
// pred would be null if there was only one element before this
// insert.
if ( pred == null )
{
head = current;
}
else
{
pred.next = current;
}
} // end else
} // end insertElement
/**
* Insert an element AT the given index. Another way to look at this, is we are inserting just ahead of the element
* at that the given index. When done, the cursor points to the newly added element, and the elements past it are
* logically pushed down an index slot.
*
* @param obj the element to add.
* @param index where to insert the new element. 0 = at the head of the list.
*
* @throws ArrayIndexOutOfBoundsException if index does not reference an existing element.
*/
public final synchronized void insertElementAt( E obj, int index ) throws ArrayIndexOutOfBoundsException
{
// we also allow an element to be inserted at the end of the list
// even though that index would be out of bounds.
if ( index == elementCount )
{
addElement( obj );
}
else
{
setCursor( index );
insertElement( obj );
}
} // end insertElementAt
/**
* Is the List empty of elements?
*
* @return true if the list is empty.
*/
public final synchronized boolean isEmpty()
{
return elementCount == 0;
}
/**
* Get last element
*
* @return the last element in the list, null if the list is empty.
*/
public final synchronized E lastElement()
{
return ( tail == null ) ? null : tail.element;
} // end lastElement
/**
* Search the list for the given object. A match is based on address equality, not comparing fields. Search from
* the tail back.
*
* @param obj the element to search for.
*
* @return 0 = first element, 1 = second element etc., -1 = not found.
*/
public final synchronized int lastIndexOf( E obj )
{
// chase chain from one end to the other looking for it.
Link chase = tail;
for ( int i = elementCount - 1; i > 0; i-- )
{
if ( chase.element == obj )
{
return i;
}
chase = chase.prev;
}
// did not find it
return -1;
} // end lastIndexOf
/**
* Delete the current element. The succeeding element becomes the new current element. If there is no successor, the
* predecessor becomes the current element. If the list either is or becomes empty, there is no current element.
*/
public final synchronized void remove()
{
if ( current == null )
{
return;
}
elementCount--;
Link pred = current.prev;
if ( pred == null )
{
head = current.next;
}
else
{
pred.next = current.next;
}
Link succ = current.next;
if ( succ == null )
{
tail = current.prev;
current = pred;
}
else
{
succ.prev = current.prev;
current = succ;
}
} // end remove
/**
* Remove all elements from the list. If there are no other references to the elements, this effectively destroys
* them.
*/
public final synchronized void removeAllElements()
{
head = null;
tail = null;
current = null;
cursor = -1;
elementCount = 0;
}
/**
* Remove an element.
*
* @param index = 0 to remove the first element, = 1 to remove the second etc.
*
* @throws ArrayIndexOutOfBoundsException if index does not reference an existing element.
*/
public final synchronized void removeElementAt( int index ) throws ArrayIndexOutOfBoundsException
{
setCursor( index );
remove();
}
/**
* Replace an element. Change the link for the indexed element to point to a different object.
*
* @param obj the new object to replace the current element.
* @param index the element to be replaced, 0 = the first element, 1 = the second etc.
*
* @throws ArrayIndexOutOfBoundsException if the index does not reference an existing object.
*/
public final synchronized void setElementAt( E obj, int index ) throws ArrayIndexOutOfBoundsException
{
setCursor( index );
setElement( obj );
} // end setElement
/**
* How many elements are currently in the list?
*
* @return the number of elements in the list.
*/
public final int size()
{
return elementCount;
}
} // end LinkedList class
/**
* The LinkedList Class creates a Link object for each element to contain the forward and back References.
*/
class Link
{
/**
* Instance reference to the list item.
*/
@SuppressWarnings( { "WeakerAccess" } )
protected E element;
/**
* Instance reference to the next link node. Null if this is the last item on the list.
*/
@SuppressWarnings( { "WeakerAccess" } )
protected Link next;
/**
* Instance reference to the previous link node. Null if this is the first item on the list.
*/
@SuppressWarnings( { "WeakerAccess" } )
protected Link prev;
/**
* Link constructor.
*
* @param element the list item.
* @param prev reference to the previous link node on the list.
* @param next reference to the next link node on the list.
*/
Link( E element, Link prev, Link next )
{
this.element = element;
this.prev = prev;
this.next = next;
}
} // end class Link
/**
* Enumerator for counting through all the items on the list.
*/
final class LinkedListEnumerator implements Enumeration
{
/**
* The list we are enumerating through.
*/
private final LinkedList linkedList;
/**
* Which item we are on in the enumeration.
*/
private int count;
/**
* Constructor.
*
* @param l the list we wish to enumerate.
*/
LinkedListEnumerator( LinkedList l )
{
this.linkedList = l;
this.count = 0;
} // end constructor
/**
* Are there more elements in the this enumeration to process?
*
* @return true if there are more elements.
*/
public final boolean hasMoreElements()
{
return count < linkedList.elementCount;
} // hasMoreElements
/**
* Get the next element of the enumeration.
*
* @return the next list element.
*/
public final E nextElement()
{
synchronized ( linkedList )
{
if ( count < linkedList.elementCount )
{
return linkedList.elementAt( count++ );
}
}
throw new NoSuchElementException( "LinkedListEnumerator" );
} // end nextElement
} // end class LinkedListEnumerator
// -30-