/*
* Aphelion
* Copyright (c) 2013 Joris van der Wel
*
* This file is part of Aphelion
*
* Aphelion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Aphelion 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 General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Aphelion. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, the following supplemental terms apply, based on section 7 of
* the GNU Affero General Public License (version 3):
* a) Preservation of all legal notices and author attributions
* b) Prohibition of misrepresentation of the origin of this material, and
* modified versions are required to be marked in reasonable ways as
* different from the original version (for example by appending a copyright notice).
*
* Linking this library statically or dynamically with other modules is making a
* combined work based on this library. Thus, the terms and conditions of the
* GNU Affero General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent module,
* the terms and conditions of the license of that module. An independent
* module is a module which is not derived from or based on this library.
*/
package aphelion.shared.swissarmyknife;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A Linked List implementation that lets you reference the links it uses (LinkedListEntry).
* By using circular references between the link and the object it stores
* removal / reinsertion, is O(1) instead of O(n) in java.util.LinkedList.
* Also, in this case, rapid reinsertion will not create new objects.
* This implementation is doubly linked (you can traverse values in either direction).
* Optionally, you can also construct circular linked lists (in this case you would use
* LinkedListEntry's without a LinkedListHead)
*
* @param <T> The type of values contained by the links
* @author Joris
* @see LinkedListEntry
*/
public class LinkedListHead<T> implements Iterable<T>
{
/**
* The first Link of this list.
* If this property is null, last must also be null
*/
public LinkedListEntry<T> first;
/**
* The last Link of this list.
* If this property is null, last must also be null
*/
public LinkedListEntry<T> last;
/**
* Create a new empty linked list
*/
public LinkedListHead()
{
}
/**
* Is this list empty?
* @return
*/
public boolean isEmpty()
{
if (first == null || last == null)
{
assert first == null && last == null : "Inconsistency: first may only be null if last is also null";
return true;
}
return false;
}
/**
* Create a new LinkedListEntry with the given data and prepend to the start of the list
* @param data
* @return The newly created LinkedListEntry
*/
public LinkedListEntry<T> prependData(T data)
{
return prepend(new LinkedListEntry<>(data));
}
/**
* Prepend the given LinkedListEntry to the start of the list
* @param link
* @return `link`
*/
public LinkedListEntry<T> prepend(LinkedListEntry<T> link)
{
if (isEmpty())
{
assert link.head == null : "The given `link` is already part of a list";
assert link.previous == null : "The given `link` is already part of a list";
assert link.next == null : "The given `link` is already part of a list";
link.head = this;
first = link;
last = link;
link.previous = null;
link.next = null;
return link;
}
else
{
return first.prepend(link);
}
}
/**
* Create a new LinkedListEntry with the given data and append to the end of the list
* @param data
* @return The newly created LinkedListEntry
*/
public LinkedListEntry<T> appendData(T data)
{
return append(new LinkedListEntry<>(data));
}
/**
* Append the given LinkedListEntry to the end of the list
* @param link
* @return `link`
*/
public LinkedListEntry<T> append(LinkedListEntry<T> link)
{
if (isEmpty())
{
assert link.head == null : "The given `link` is already part of a list";
assert link.previous == null : "The given `link` is already part of a list";
assert link.next == null : "The given `link` is already part of a list";
link.head = this;
first = link;
last = link;
link.previous = null;
link.next = null;
return link;
}
else
{
return last.append(link);
}
}
/**
* Remove the given range of links between `start` and `end` but do not clear the links.
* The links between start and end will form their own headless list.
* @param start
* @param end
*/
public void extractRange(LinkedListEntry<T> start, LinkedListEntry<T> end)
{
removeRange(start, end, false);
}
/**
* Remove the given range of links between `start` and `end` and clear all the links.
* @param start
* @param end
*/
public void removeRange(LinkedListEntry<T> start, LinkedListEntry<T> end)
{
removeRange(start, end, true);
}
private void removeRange(LinkedListEntry<T> start, LinkedListEntry<T> end, boolean clearLinks)
{
LinkedListEntry<T> link;
assert start.head == this : "The given `start` link is not part of this list";
assert end.head == this : "The given `end` link is not part of this list";
LinkedListEntry<T> left = start.previous;
LinkedListEntry<T> right = end.next;
link = start;
while(true)
{
assert link != null;
assert link.head == this : "One of the links in the given range is not part of this list";
LinkedListEntry<T> next = link.next;
link.head = null;
if (clearLinks)
{
link.previous = null;
link.next = null;
}
if (link == end)
{
break;
}
link = next;
assert link != start;
}
if (left != null)
{
left.next = right;
}
if (this.first == start)
{
this.first = right;
}
if (right != null)
{
right.previous = left;
}
if (this.last == end)
{
this.last = left;
}
start.previous = null;
end.next = null;
}
/**
* Remove all links
*/
public void clear()
{
if (first == null || last == null)
{
assert first == null && last == null : "Inconsistency: first may only be null if last is also null";
return;
}
removeRange(first, last);
}
/**
* Append the given headless list of entries to the end of this list
* @param start
* @param end
*/
public void appendForeignRange(LinkedListEntry<T> start, LinkedListEntry<T> end)
{
LinkedListEntry<T> link;
assert start != null : "`start` should not be null";
assert end != null : "`end` should not be null";
if (first == null)
{
if (start.head != null)
{
assert start.head != this : "One of the links in the given range is not a foreign link";
start.head.extractRange(start, end);
}
link = start;
while(true)
{
assert link != null;
assert link.head == null;
link.head = this;
if (link == end)
{
break;
}
link = link.next;
assert link != start;
}
this.first = start;
this.last = end;
}
else
{
last.appendForeignRange(start, end);
}
}
/**
* Prepend the given headless list of entries to the start of this list
* @param start
* @param end
*/
public void prependForeignRange(LinkedListEntry<T> start, LinkedListEntry<T> end)
{
LinkedListEntry<T> link;
assert start != null : "`start` should not be null";
assert end != null : "`end` should not be null";
if (first == null)
{
if (start.head != null)
{
assert start.head != this : "One of the links in the given range is not a foreign link";
start.head.extractRange(start, end);
}
link = start;
while(true)
{
assert link != null;
assert link.head == null;
link.head = this;
if (link == end)
{
break;
}
link = link.next;
assert link != start;
}
this.first = start;
this.last = end;
}
else
{
first.prependForeignRange(start, end);
}
}
/**
* Find a link by matching the given `data` using == and remove it.
* @param data The reference to compare
* @return The link that was removed, or null if `data` was not found
*/
public LinkedListEntry<T> removeByReference(T data)
{
LinkedListEntry<T> entry;
entry = first;
while (entry != null)
{
if (entry.data == data)
{
entry.remove();
return entry;
}
entry = entry.next;
}
return null;
}
/**
* Find a link by matching the given `data` using data.equals() and remove it.
* @param data The object to test for equality, or null to test for null.
* @return The link that was removed, or null if `data` was not found
*/
public LinkedListEntry<T> removeByEquals(T data)
{
LinkedListEntry<T> entry;
entry = first;
while (entry != null)
{
if (data == null)
{
if (entry.data == null)
{
entry.remove();
return entry;
}
}
else
{
if (data.equals(entry.data))
{
entry.remove();
return entry;
}
}
entry = entry.next;
}
return null;
}
@Override
public String toString()
{
int a;
LinkedListEntry<T> link;
StringBuilder builder;
if (first == null)
{
return "LL[]";
}
builder = new StringBuilder("LL[");
link = first;
for (a = 0; a < 10 && link != null; a++)
{
if (link.data != null)
{
builder.append(link.data.toString());
}
else
{
builder.append("null");
}
link = link.next;
if (link != null)
{
builder.append(",");
}
}
if (link != null)
{
builder.append("...");
}
builder.append("]");
return builder.toString();
}
/**
* Return and remove the last item
* @return The last item or null if the list is empty
*/
public T pop()
{
LinkedListEntry<T> val;
if (last == null)
{
return null;
}
val = last;
val.remove();
return val.data;
}
/**
* Return and remove the first item
* @return The first item or null if the list is empty
*/
public T shift()
{
LinkedListEntry<T> val;
if (last == null)
{
return null;
}
val = first;
val.remove();
return val.data;
}
/**
* Iterate over the values from `start` to `end`. Calling it.remove() is allowed.
* @return
*/
@Override
public Iterator<T> iterator()
{
return new Itr(false, false);
}
/**
* Iterate over the values from `start` to `end`. Calling it.remove() is NOT allowed.
* @return
*/
public Iterator<T> iteratorReadOnly()
{
return new Itr(true, false);
}
/**
* Iterate over the values from `end` to `start`. Calling it.remove() is allowed.
* @return
*/
public Iterator<T> iteratorReverse()
{
return new Itr(false, true);
}
/**
* Iterate over the values from `end` to `start`. Calling it.remove() is NOT allowed.
* @return
*/
public Iterator<T> iteratorReverseReadOnly()
{
return new Itr(true, true);
}
/** Make all links in this linked list headless.
*/
public void headless()
{
for (LinkedListEntry<T> link = this.first; link != null; link = link.next)
{
assert link.head == this;
link.head = null;
}
}
/**
*
* @return The number of links in this list
*/
public int calculateSize()
{
int size = 0;
for (LinkedListEntry<T> link = this.first; link != null; link = link.next)
{
++size;
}
return size;
}
/**
* Verify the internal consistency of this list by using assert.
* This is slow when performed too often, it is most useful in test cases
*/
public void assertConsistency()
{
if (!SwissArmyKnife.assertEnabled)
{
return;
}
LinkedListEntry link = this.first;
while (link != null)
{
assert link.head == this;
if (link.next == null)
{
assert this.last == link;
}
else
{
assert link.next.previous == link;
assert link.next != link;
assert link.previous != link;
}
link = link.next;
}
}
private class Itr implements Iterator<T>
{
private LinkedListEntry<T> current;
private LinkedListEntry<T> next;
private final boolean readonly;
private final boolean reverse;
Itr(boolean readonly, boolean reverse)
{
this.readonly = readonly;
this.reverse = reverse;
current = null;
next = reverse ? last : first;
}
@Override
public boolean hasNext()
{
return next != null;
}
@Override
public T next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
current = next;
next = reverse ? current.previous : current.next;
return current.data;
}
@Override
public void remove()
{
if (readonly)
{
throw new UnsupportedOperationException();
}
if (current == null)
{
throw new IllegalStateException();
}
current.remove();
}
}
}