package com.nettgryppa.util;
// Copyright 2006 Gregory Rubin grrubin@gmail.com
// Permission is given to use, modify, and or distribute this code so long as this message remains attached
import java.util.*;
/**
* This class is meant to simulate a list of infinite (and optionally bi-infinite) length. All positive indices
* are valid but will default to null. This list can also be bi-infinite, in which case all
* negative indices are also valid. This class is also useful for sparse lists as it is backed by a {@link java.util.SortedMap}
* Copyright 2006 Gregory Rubin grrubin@gmail.com
* Permission is given to use, modify, and or distribute this code so long as this message remains attached
* @author grrubin@gmail.com
* @version 1.0
*/
public class InfiniteList implements List {
private SortedMap myMap;
private boolean allowNegative;
/**
* Constructs an empty list with allowNegative set to false.
*/
public InfiniteList() {
myMap = new TreeMap();
allowNegative = false;
}
/**
* Constructs an empty list.
*/
public InfiniteList(boolean allowNegative) {
myMap = new TreeMap();
this.allowNegative = allowNegative;
}
/**
* Copy constructor
*/
public InfiniteList(InfiniteList obj) {
myMap = new TreeMap(obj.myMap);
this.allowNegative = obj.allowNegative;
}
/**
* Does this specific instance allow negative indices
*/
public boolean allowsNegative() {
return allowNegative;
}
public int size() {
if(myMap.isEmpty())
return 0;
return (myMap.lastKey().intValue() - myMap.firstKey().intValue() + 1);
}
/**
* Maximum assigned index
*/
public int max() {
if(myMap.isEmpty() || null == myMap.lastKey()) {
return 0;
} else {
return (myMap.lastKey().intValue());
}
}
/**
* Minimum assigned index
*/
public int min() {
if(myMap.isEmpty() || null == myMap.firstKey()) {
return 0;
} else {
return (myMap.firstKey().intValue());
}
}
/*
* How many indices have non-null values
*/
public int load() {
return myMap.size();
}
public boolean add(E element) {
if(null == element)
throw new NullPointerException("Adding a null element to the end of the list is meaningless.");
if(isEmpty())
set(0, element);
else
add(max() + 1, element);
return true;
}
public void add(int index, E element) {
if(null == element && index > max())
throw new NullPointerException("Adding a null element to the end of the list is meaningless.");
if(index <= max()) {
Integer[] indices = myMap.tailMap(Integer.valueOf(index)).keySet().toArray(new Integer[0]);
Arrays.sort(indices);
for(int x = indices.length - 1; x >= 0; x--) {
set(indices[x] + 1, get(indices[x]));
set(indices[x], null);
}
}
set(index, element);
}
/*
* Add all elements in the collection to this list.
* This will handle nulls as expected in that if they come before the last element,
* they will be preserved.
*/
public boolean addAll(Collection extends E> c) {
if(!isEmpty())
return addAll(max() + 1, c);
else
return addAll(0, c);
}
/*
* Add all elements in the collection to this list starting at index.
* This will handle nulls as expected in that if they come before the last element,
* they will be preserved.
*/
public boolean addAll(int index, Collection extends E> c) {
if(c.isEmpty())
return false;
if(index <= max() && !isEmpty()) {
Integer[] indices = myMap.tailMap(Integer.valueOf(index)).keySet().toArray(new Integer[0]);
Arrays.sort(indices);
for(int x = indices.length - 1; x >= 0; x--) {
set(indices[x] + c.size(), get(indices[x]));
set(indices[x], null);
}
}
for(E obj: c) {
set(index, obj);
index++;
}
return true;
}
public void clear() {
myMap.clear();
}
public boolean contains(Object o) {
if(null == o)
return true;
return myMap.containsValue(o);
}
public boolean containsAll(Collection> o) {
for(Object obj: o) {
if(!contains(obj))
return false;
}
return true;
}
/**
* All indices with non-null values in ascending order
*/
public Set indexSet() {
return myMap.keySet();
}
/**
* Acts as expected if other object is an InfiniteList. Else returns true if
* this list has no negative elements and its positive elements are equal to the list
* passed in.
* NOTE: If the other object is not an InfiniteList, we cannot guarantee that
* infiniteList.equals(o) == o.equals(infiniteList) An example of where this will
* break is any list that contains nulls on either end.
*/
public boolean equals(Object o) {
if(null == o) {
return false;
} else if(o instanceof InfiniteList) {
return ((InfiniteList)o).myMap.equals(myMap);
} else if(o instanceof List) {
List tempList = (List)o;
if(isEmpty() && tempList.isEmpty()) {
return true;
} else if(min() >= 0 && max() < tempList.size()) {
for(int x = 0; x < tempList.size(); x++) {
if ( (get(x) != null && !get(x).equals(tempList.get(x)))
|| (tempList.get(x) != null && !tempList.get(x).equals(get(x))) )
return false;
}
return true;
} else {
return false;
}
} else {
return false;
}
}
public E get(int index) {
if(!allowNegative && index < 0)
throw new IndexOutOfBoundsException();
return myMap.get(Integer.valueOf(index));
}
public int hashCode() { // TODO: Improve
int hashCode = 1;
ListIterator i = listIterator(0);
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
i = listIterator(0);
while (i.hasPrevious()) {
Object obj = i.previous();
hashCode = 37*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
/**
* WARNING! DO NOT USE THIS TO DETERMINE IF THE LIST CONTAINS AN ELEMENT AS
* -1 IS A VALID RESPONSE! USE {@link #contains(Object o)}
* If o is null then returns {@link #min()} - 1
*/
public int indexOf(Object o) {
if(null == o) {
return min() - 1;
} else if(!myMap.containsValue(o)) {
return -1;
} else {
for(Map.Entry entry: myMap.entrySet()) {
if(entry.getValue().equals(o))
return entry.getKey().intValue();
}
return -1; // Unreachable line
}
}
public boolean isEmpty() {
return myMap.isEmpty();
}
/**
* WARNING! DO NOT USE THIS TO DETERMINE IF THE LIST CONTAINS AN ELEMENT AS
* -1 IS A VALID RESPONSE! USE {@link #contains(Object o)}
* WARNING! THIS IS A SLOW IMPLEMENTATION
* If o is null then returns {@link #max()} + 1
*/
public int lastIndexOf(Object o) {
if(null == o) {
return max() + 1;
} else if(!myMap.containsValue(o)) {
return -1;
} else {
int result = -1;
for(Map.Entry entry: myMap.entrySet()) {
if(entry.getValue().equals(o))
result = entry.getKey().intValue();
}
return result;
}
}
public Iterator iterator() {
return listIterator();
}
public ListIterator listIterator() {
return new InfiniteListIterator(this);
}
public ListIterator listIterator(int index) {
return new InfiniteListIterator(this, index);
}
public E remove(int index) {
E old = get(index);
set(index, null);
Set indices = new TreeSet(myMap.tailMap(Integer.valueOf(index)).keySet());
for(Integer x: indices) {
set(x.intValue() - 1, get(x.intValue()));
set(x.intValue(), null);
}
return old;
}
public boolean remove(Object o) {
if(null == o)
return true; // We always contain the null element
if(contains(o)) {
int index = indexOf(o);
remove(index);
return true;
} else {
return false;
}
}
public E set(int index, E element) {
if(!allowNegative && index < 0)
throw new IndexOutOfBoundsException();
if(null != element)
return myMap.put(Integer.valueOf(index), element);
else
return myMap.remove(Integer.valueOf(index));
}
public InfiniteList subList(int fromIndex, int toIndex) {
InfiniteList result = new InfiniteList();
result.myMap = myMap.subMap(Integer.valueOf(fromIndex), Integer.valueOf(toIndex));
result.allowNegative = allowNegative;
return result;
}
public boolean retainAll(Collection> c) {
return myMap.values().retainAll(c);
}
public boolean removeAll(Collection> c) {
return myMap.values().removeAll(c);
}
/**
* NOTE: While order is guaranteed, indices are not guarenteed to be identical unless all are non-negative
*/
public Object[] toArray() {
return toArray(new Object[0]);
}
/**
* NOTE: While order is guaranteed, indices are not guarenteed to be identical unless all are non-negative
*/
@SuppressWarnings("unchecked")
public T[] toArray(T[] a) {
Arrays.fill(a, null);
int offset = (min() < 0 ?
-1 * min() :
0);
// We mock this up by building a normal list and sending that back
List tempList = new ArrayList(max() + offset);
if(!isEmpty()) {
for(int x = 0; x <= max() + offset; x++) {
tempList.add((T)get(x - offset));
}
}
return (T[])tempList.toArray(a);
}
private static class InfiniteListIterator implements ListIterator {
private boolean tainted = true;
private InfiniteList myList;
private int nextIndex;
private int previousIndex;
private int lastIndex;
public InfiniteListIterator(InfiniteList list) {
myList = list;
nextIndex = list.min();
previousIndex = nextIndex - 1;
}
public InfiniteListIterator(InfiniteList list, int index) {
myList = list;
nextIndex = index;
previousIndex = nextIndex - 1;
}
public boolean hasNext() {
if(myList.isEmpty())
return false;
return myList.max() >= nextIndex;
}
public boolean hasPrevious() {
if(myList.isEmpty())
return false;
return myList.min() <= previousIndex;
}
public E next() {
tainted = false;
previousIndex = nextIndex;
lastIndex = nextIndex;
return myList.get(nextIndex++);
}
public E previous() {
tainted = false;
nextIndex = previousIndex;
lastIndex = previousIndex;
return myList.get(previousIndex--);
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return previousIndex;
}
public void remove() {
if(tainted)
throw new IllegalStateException();
tainted = true;
myList.remove(lastIndex);
nextIndex--;
}
public void add(E o) {
myList.add(nextIndex, o);
nextIndex++;
previousIndex = nextIndex - 1;
tainted = true;
}
public void set(E o) {
if(tainted)
throw new IllegalStateException();
myList.set(lastIndex, o);
}
};
}