/*
* Copyright (C) 2014 Alec Dhuse
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.map;
import co.foldingmap.map.vector.VectorObject;
import java.io.Serializable;
import java.util.*;
/**
*
* @author Alec
*/
public class MapObjectList<MapObject> extends AbstractList<MapObject>
implements List<MapObject>,
Cloneable,
Serializable,
RandomAccess {
private transient int firstIndex;
private transient int lastIndex;
private transient MapObject[] array;
public MapObjectList() {
this(10);
}
/**
* Constructs a new instance of MapObjectList with the specified capacity.
*
* @param capacity
* the initial capacity of this MapObjectList.
*/
public MapObjectList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException();
}
firstIndex = lastIndex = 0;
array = newElementArray(capacity);
}
/**
* Creates a new MapObjectList from another collection of MapObjects.
*
* @param collection
*/
public MapObjectList(Collection<? extends MapObject> collection) {
this(collection.size());
Object[] dumpArray = collection.toArray();
if (dumpArray.length != 0) {
if (dumpArray.length > array.length - lastIndex) {
growAtEnd(dumpArray.length);
}
System.arraycopy(dumpArray, 0, this.array, lastIndex, dumpArray.length);
lastIndex += dumpArray.length;
modCount++;
}
}
/**
* Returns if all the MapObjects are instances of VectorObjects
*
* @return
*/
public boolean areAllVectorObjects() {
boolean result = false;
for (int i = firstIndex; i < lastIndex; i++) {
if (array[i] instanceof VectorObject) {
result = true;
} else {
result = false;
break;
}
}
return result;
}
/**
* Inserts the specified object into this {@code MapObjectList} at the
* specified location. The object is inserted before any previous element
* at the specified location. If the location is equal to the size of this
* {@code ArrayList}, the object is added at the end.
*
* @param location
* the index at which to insert the object.
* @param object
* the object to add.
* @throws IndexOutOfBoundsException
* when {@code location < 0 || > size()}
*/
@Override
public void add(int location, MapObject object) {
int size = lastIndex - firstIndex;
if (0 < location && location < size) {
if (firstIndex == 0 && lastIndex == array.length) {
growForInsert(location, 1);
} else if ((location < size / 2 && firstIndex > 0) || lastIndex == array.length) {
System.arraycopy(array, firstIndex, array, --firstIndex, location);
} else {
int index = location + firstIndex;
System.arraycopy(array, index, array, index + 1, size - location);
lastIndex++;
}
array[location + firstIndex] = object;
} else if (location == 0) {
if (firstIndex == 0) {
growAtFront(1);
}
array[--firstIndex] = object;
} else if (location == size) {
if (lastIndex == array.length) {
growAtEnd(1);
}
array[lastIndex++] = object;
} else {
throw new IndexOutOfBoundsException("Index: " + Integer.valueOf(location) + " Size: " + Integer.valueOf(lastIndex - firstIndex));
}
modCount++;
}
/**
* Adds the specified object at the end of this MapObjectList unless an
* instance already exists in the list.
*
* @param object
* The MapObject to add.
* @return
* If the object was added or not.
*/
@Override
public boolean add(MapObject newObject) {
try {
boolean instanceFound = false;
if (firstIndex != lastIndex) {
//check to see if the coordinate exists already
for (int i = firstIndex; i < lastIndex; i++) {
if (array[i].equals(newObject)) {
instanceFound = true;
break;
}
} //end for loop
}
if (instanceFound == false) {
forceAdd(newObject);
return true;
} else {
return false;
}
} catch (Exception e) {
System.err.println("Error in MapObjectList.add() - " + e);
return false;
}
}
/**
* Adds the objects in the specified collection to this
* {@code MapObjectList}.
*
* @param collection
* the collection of objects.
* @return {@code true} if this {@code MapObjectList} is modified,
* {@code false} otherwise.
*/
@Override
public boolean addAll(Collection<? extends MapObject> collection) {
Object[] dumpArray = collection.toArray();
if (dumpArray.length == 0) {
return false;
}
if (dumpArray.length > array.length - lastIndex) {
growAtEnd(dumpArray.length);
}
System.arraycopy(dumpArray, 0, this.array, lastIndex, dumpArray.length);
lastIndex += dumpArray.length;
modCount++;
return true;
}
/**
* Returns a new {@code MapObjectList} with the same elements, the same
* size and the same capacity as this {@code MapObjectList}.
*
* @return a shallow copy of this {@code MapObjectList}
* @see java.lang.Cloneable
*/
@Override
@SuppressWarnings("unchecked")
public MapObjectList clone() {
try {
MapObjectList<MapObject> newList = (MapObjectList<MapObject>) super.clone();
newList.array = array.clone();
return newList;
} catch (CloneNotSupportedException e) {
return null;
}
}
/**
* Returns if the supplied MapObject already exists in the list.
*
* @param object
* The MapObject to check.
*
* @return If the MapObject exist in the list or not.
*/
public boolean contains(co.foldingmap.map.MapObject object) {
boolean result = false;
co.foldingmap.map.MapObject currentObject;
for (int i = firstIndex; i < lastIndex; i++) {
currentObject = (co.foldingmap.map.MapObject) array[i];
if (currentObject == object) {
result = true;
break;
}
}
return result;
}
/**
* Adds the specified object at the end of this MapObjectList even if an
* instance already exists in the list.
*
* @param object
* the MapObject to add.
* @return always true
*/
public boolean forceAdd(MapObject object) {
try {
if (lastIndex == array.length) {
growAtEnd(1);
}
array[lastIndex++] = object;
modCount++;
return true;
} catch (Exception e) {
System.err.println("Error in MapObjectList.forceAdd(Coordinate) - " + e);
return false;
}
}
/**
* Returns the MapObject at the given list location.
*
* @param location
* @return
*/
@Override
public MapObject get(int location) {
if (0 <= location && location < (lastIndex - firstIndex)) {
return array[firstIndex + location];
}
throw new IndexOutOfBoundsException("Index: " + Integer.valueOf(location)
+ " List Size: " + Integer.valueOf(lastIndex - firstIndex));
}
/**
* Returns the array for this List.
*
* @return
*/
public MapObject[] getArray() {
return array;
}
/**
* Returns a new MspObjectList with copies of each object in the original
* list.
*
* @return
*/
public MapObjectList<co.foldingmap.map.MapObject> getFullCopy() {
co.foldingmap.map.MapObject object;
MapObjectList<co.foldingmap.map.MapObject> copy;
copy = new MapObjectList<co.foldingmap.map.MapObject>();
for (int i = firstIndex; i < lastIndex; i++) {
object = (co.foldingmap.map.MapObject) array[i];
copy.add((co.foldingmap.map.MapObject) object.copy());
}
return copy;
}
private void growAtEnd(int required) {
int size = lastIndex - firstIndex;
if (firstIndex >= required - (array.length - lastIndex)) {
int newLast = lastIndex - firstIndex;
if (size > 0) {
System.arraycopy(array, firstIndex, array, 0, size);
int start = newLast < firstIndex ? firstIndex : newLast;
Arrays.fill(array, start, array.length, null);
}
firstIndex = 0;
lastIndex = newLast;
} else {
int increment = size / 2;
if (required > increment)
increment = required;
if (increment < 12)
increment = 12;
MapObject[] newArray = newElementArray(size + increment);
if (size > 0) {
System.arraycopy(array, firstIndex, newArray, 0, size);
firstIndex = 0;
lastIndex = size;
}
array = newArray;
}
}
private void growAtFront(int required) {
int size = lastIndex - firstIndex;
if (array.length - lastIndex + firstIndex >= required) {
int newFirst = array.length - size;
if (size > 0) {
System.arraycopy(array, firstIndex, array, newFirst, size);
int length = firstIndex + size > newFirst ? newFirst : firstIndex + size;
Arrays.fill(array, firstIndex, length, null);
}
firstIndex = newFirst;
lastIndex = array.length;
} else {
int increment = size / 2;
if (required > increment) {
increment = required;
}
if (increment < 12) {
increment = 12;
}
MapObject[] newArray = newElementArray(size + increment);
if (size > 0) {
System.arraycopy(array, firstIndex, newArray, newArray.length - size, size);
}
firstIndex = newArray.length - size;
lastIndex = newArray.length;
array = newArray;
}
}
private void growForInsert(int location, int required) {
int size = lastIndex - firstIndex;
int increment = size / 2;
if (required > increment) {
increment = required;
}
if (increment < 12) {
increment = 12;
}
MapObject[] newArray = newElementArray(size + increment);
int newFirst = increment - required;
// Copy elements after location to the new array skipping inserted elements
System.arraycopy(array, location + firstIndex, newArray, newFirst
+ location + required, size - location);
// Copy elements before location to the new array from firstIndex
System.arraycopy(array, firstIndex, newArray, newFirst, location);
firstIndex = newFirst;
lastIndex = size + increment;
array = newArray;
}
@SuppressWarnings("unchecked")
private MapObject[] newElementArray(int size) {
return (MapObject[]) new Object[size];
}
/**
* Returns the index in the list of a given MapObject.
*
* @param object
* @return
*/
public int indexOf(co.foldingmap.map.MapObject object) {
co.foldingmap.map.MapObject currentObject;
int index = -1;
for (int i = firstIndex; i < lastIndex; i++) {
currentObject = (co.foldingmap.map.MapObject) array[i];
if (currentObject == object) {
index = i;
break;
}
}
return index;
}
/**
* Returns the last element in this list;
* @return
*/
public MapObject lastElement() {
return array[lastIndex - 1];
}
/**
* Removes the object at the specified location from this list.
*
* @param location
* the index of the object to remove.
* @return the removed object.
* @throws IndexOutOfBoundsException
* when {@code location < 0 || >= size()}
*/
@Override
public MapObject remove(int location) {
MapObject result;
int size = lastIndex - firstIndex;
if (0 <= location && location < size) {
if (location == size - 1) {
result = array[--lastIndex];
array[lastIndex] = null;
} else if (location == 0) {
result = array[firstIndex];
array[firstIndex++] = null;
} else {
int elementIndex = firstIndex + location;
result = array[elementIndex];
if (location < size / 2) {
System.arraycopy(array, firstIndex, array, firstIndex + 1, location);
array[firstIndex++] = null;
} else {
System.arraycopy(array, elementIndex + 1, array, elementIndex, size - location - 1);
array[--lastIndex] = null;
}
}
if (firstIndex == lastIndex) {
firstIndex = lastIndex = 0;
}
} else {
throw new IndexOutOfBoundsException(Integer.valueOf(location) + " List size: " + Integer.valueOf(lastIndex - firstIndex));
}
modCount++;
return result;
}
/**
* Removes a MapObject from this List.
*
* @param object
* @return If the object was removed or not.
*/
@Override
public boolean remove(Object object) {
boolean removed;
removed = super.remove(object);
return removed;
}
/**
* Returns the number of elements in this MapObjectList.
*
* @return the number of elements in this MapObjectList.
*/
@Override
public int size() {
return lastIndex - firstIndex;
}
}