/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the LICENSE file that accompanied
* this code.
*
* --
*/
package com.sun.sgs.app.util;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedObjectRemoval;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.Task;
/**
* This class represents a {@code java.util.List} which supports a concurrent
* and scalable behavior. In particular, this List allows for arbitrary
* insertions and removals, arbitrary searches for values, and a number of
* other methods which are also defined in the {@code java.util.List}
* interface. This data structure builds upon the {@code AbstractList} class
* by implementing methods specific to concurrent and scalable operations. As
* an implementation decision, this data structure does not support
* {@code null} insertions but does implement all optional operations defined
* in the {@code java.util.List} interface.
* <p>
* Those who are interested in using a simple data structure that is not
* intended to grow very large, contains simple elements, but offers decent
* concurrency should use the
* {@code java.util.concurrent.CopyOnWriteArrayList<E>} type. However, if
* lists are intended to be very large and cloning the list is infeasible due
* to its size or the complexity of its contents, then the
* {@code ScalableList} is a better tool as it does not require cloning and it
* scales reasonably well.
* <p>
* This class actually stores a collection of {@code ManagedReference}s,
* which in turn point to {@code ManagedObject}s. Therefore, each element
* stored is converted into a {@code ManagedObject} in the form of an
* {@code Element} wrapper if it is not one already, and a
* {@code ManagedReference} is created to reference it. The wrapper is
* detected during an element removal: if the element is an instance of the
* wrapper {@code Element} class, then it is removed from the data manager.
* Conversely, if the element had no wrapper (but by definition is still a
* {@code ManagedObject}), then it remains within the data manager until the
* user explicitly removes it.
* <p>
* The class achieves scalability and concurrency by partitioning an ordinary
* list into a number of smaller lists contained in {@code ListNode} objects,
* and joining the nodes in a tree format. This implementation bears
* similarity to a skip-list in that access to arbitrary elements occurs
* through initially large jumps, and then through a finer iteration of the
* contained list. To allow for this behaviour, each {@code ListNode} holds a
* subset of the contents so that changes in the entries need not propagate to
* all elements at once. In fact, each {@code ListNode} only holds onto the
* size of its {@code SubList} (its children) and not a cumulative total of
* all its previous siblings. This enables intermediate changes to have no
* effect on neighbouring nodes, such as re-indexing.
* <p>
* The {@code branchingFactor} is a user-defined parameter which describes how
* the underlying tree is organized. A large {@code branchingFactor} means
* that each node in the tree contains a large number of children, providing
* for a shallower tree, but many more sibling traversals. Concurrency is
* somewhat compromised since parent nodes containing a large number of
* children are locked during modification. A smaller branching factor reduces
* the sibling traversals, but makes the tree deeper, somewhat affecting
* performance during split operations. Depending on the use of the list, it
* may be desirable to have a large {@code branchingFactor}, such as for
* improved scalability, or a smaller {@code branchingFactor}, such as for
* better concurrency.
* <p>
* Performing splits and removing unused nodes can be somewhat expensive,
* depending on the values set for the {@code branchingFactor} and
* {@code bucketSize}. This is because changes that occur at the leaf level
* need to propagate to the parents; for a deep tree, this can affect a number
* of different nodes. However, it is seen that the benefits provided by the
* partitioning of the list that enable concurrency outweigh the performance
* hit for these operations.
* <p>
* As mentioned, {@code ListNode}s contain a subset of the total number of
* elements in the {@code ScalableList}. When an iterator is referencing an
* element, the iterator is not affected by changes that occur in prior
* {@code ListNode}s. However, if modifications happen after the current
* {@code ListNode} being examined, they will be incorporated in the
* iteration, suggesting that the target element may not be the same one as
* when the call was initially made. However, if a modification in the form of
* an addition or removal happens on the same {@code ListNode}, then the
* iterator will throw a {@code ConcurrentModificationException} as a result
* of a compromise to the integrity of the node. This exception is not thrown
* if an element is replaced using the {@code set()} method because there
* would be no change in index to prompt the exception.
* <p>
* Since the {@code ScalableList} type is a {@code ManagedObject}, it is
* important to note that applications which instantiate it should be
* responsible for removing it from the data manager. This can be done by
* statically obtaining the {@code DataManager} through the {@code AppContext}
* via the call
* {@code AppContext.getDataManager().removeObject(ManagedObject)}.
* <p>
* Contrary to the {@code ScalableList}, iterators both within and provided
* by the {@code ScalableList} type are not {@code ManagedObject}s.
* Therefore, they are not stored within the data manager and do not need to
* be removed using the {@code DataManager}.
* <p>
* Since the list is capable of containing many elements, applications which
* use iterators to traverse elements should be aware that prolonged iterative
* tasks have the potential to lock out other concurrent tasks on the list.
* Therefore, it is highly recommended (and considered good practice) that
* potentially long iterations be broken up into smaller tasks. This strategy
* improves the concurrency of the {@code ScalableList} as it reduces the
* locked elements owned by any one task.
* <p>
*
* @param <E> the type of the elements stored in the {@code ScalableList}
*/
public class ScalableList<E> extends AbstractList<E> implements Serializable,
ManagedObjectRemoval {
private static final long serialVersionUID = 1L;
/**
* Default value for the {@code branchingFactor} if one is not explicitly
* set.
*/
public static final int DEFAULT_BRANCHING_FACTOR = 5;
/**
* Default value for the {@code bucketSize} if one is not explicitly set.
*/
public static final int DEFAULT_BUCKET_SIZE = 10;
/**
* The top node in the tree
*/
private ManagedReference<TreeNode<E>> root;
/**
* A reference to the head node of the list.
*/
private ManagedReference<DummyConnector<E>> headRef;
/**
* A reference to the tail of the list. This makes appending to the list a
* constant-time operation.
*/
private ManagedReference<DummyConnector<E>> tailRef;
/**
* If non-null, a runnable to call when a task that asynchronously removes
* nodes is done -- used for testing. Note that this method is called
* during the transaction that completes the removal.
*/
private static volatile Runnable noteDoneRemoving = null;
/**
* The maximum number of elements a {@code ListNode} can contain. This
* number should be small enough to enable concurrency but large enough to
* contain a reasonable number of nodes. If it is not explicitly set, it
* defaults to a value of 10.
*/
private int bucketSize = DEFAULT_BUCKET_SIZE;
/**
* The maximum number of children contained in a TreeNode<E>; this
* parameter is passed to the TreeNode<E> during instantiation. If it is
* not explicitly set, it defaults to a value of 5.
*/
private int branchingFactor = DEFAULT_BRANCHING_FACTOR;
/*
* IMPLEMENTATION
*/
/**
* Constructor which creates a {@code ScalableList} object with default
* values for the {@code bucketSize} and {@code branchingFactor}.
*/
public ScalableList() {
TreeNode<E> t = new TreeNode<E>(this, null, false);
headRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
tailRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
setRoot(t);
}
/**
* Constructor which creates a {@code ScalableList} object with the
* {@code branchingFactor} and {@code bucketSize} supplied as a parameter.
* The {@code bucketSize} can be any integer larger than 0, however the
* {@code branchingFactor} must be larger than 1 so that the tree can be
* meaningful. Otherwise, it would only be able to grow to a maximum size
* of {@code bucketSize} since branching could not introduce any
* additional children.
*
* @param branchingFactor the number of children each node should have. A
* {@code branchingFactor} of 2 means that the tree structure is binary.
* @param bucketSize the size of each partitioned list. This value must be
* a positive integer (larger than 0).
* @throws IllegalArgumentException when the arguments are too small
*/
public ScalableList(int branchingFactor, int bucketSize) {
isLegal(branchingFactor, bucketSize);
this.bucketSize = bucketSize;
this.branchingFactor = branchingFactor;
TreeNode<E> t = new TreeNode<E>(this, null, false);
headRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
tailRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
setRoot(t);
}
/**
* Constructor which creates a {@code ScalableList} object with the
* {@code branchingFactor}, {@code bucketSize}, and a {@code collection}
* supplied as parameters. The {@code bucketSize} can be any integer
* larger than 0, however the {@code branchingFactor} must be larger than
* 1 so that the tree can be meaningful. Otherwise, it would only be able
* to grow to a maximum size of {@code bucketSize} since branching could
* not introduce any additional children. The {@code collection}
* represents a {@code Collection} of elements which will be added to the
* newly formed {@code ScalableList}.
*
* @param branchingFactor the number of children each node should have. A
* {@code branchingFactor} of 2 means that the tree structure is binary.
* @param bucketSize the size of each partitioned list. This value must be
* a positive integer (larger than 0).
* @param collection a collection of objects to initially populate the
* {@code ScalableList}
* @throws IllegalArgumentException if the collection is invalid (contains
* {@code null} elements), or if the {@code branchingFactor} or
* {@code bucketSize} are not within their respective ranges
*/
public ScalableList(int branchingFactor, int bucketSize,
Collection<E> collection) {
this(branchingFactor, bucketSize);
this.addAll(collection);
}
/**
* Creates a reference to the supplied argument if it is not {@code null}.
*
* @param <T> the type of the object
* @param t the object to reference
* @return a {@code ManagedReference} of the object, or {@code null} if
* the argument is null.
*/
static <T> ManagedReference<T> createReferenceIfNecessary(T t) {
if (t == null) {
return null;
}
return AppContext.getDataManager().createReference(t);
}
/**
* Returns the value stored within the {@code ManagedReference},
* depending on whether it is an {@code Element} or {@code ManagedObject}
*
* @param <E> the type of element
* @param ref the {@code ManagedReference} storing the value
* @param delete {@code true} if the {@code Element} object should be
* deleted, and {@code false} otherwise
* @return the value stored in the reference
*/
@SuppressWarnings("unchecked")
static <E> E getValueFromReference(ManagedReference<ManagedObject> ref,
boolean delete) {
E obj = (E) ref.get();
// In case we wrapped the item with an
// Element object, fetch the value. In here,
// check if the Element is to be deleted
if (obj instanceof Element) {
Element<E> tmp = uncheckedCast(obj);
if (delete) {
AppContext.getDataManager().removeObject(obj);
}
return tmp.getValue();
}
return obj;
}
/**
* Casts the object to the desired type in order to avoid unchecked cast
* warnings
*
* @param <T> the type to cast to
* @param object the object to cast
* @return the casted version of the object
*/
@SuppressWarnings("unchecked")
private static <T> T uncheckedCast(Object object) {
return (T) object;
}
/**
* Retrieves the {@code branchingFactor} for the list.
*
* @return the {@code branchingFactor}
*/
int getBranchingFactor() {
return branchingFactor;
}
/**
* Retrieves the {@code bucketSize} for the {@code ListNode} elements.
*
* @return the {@code bucketSize}
*/
int getBucketSize() {
return bucketSize;
}
/**
* Ensure that the parameters are valid.
*
* @param branchingFactor the branching factor
* @param bucketSize the maximum number of elements in a {@code ListNode}
* @throws IllegalArgumentException if one of the values is invalid
*/
private void isLegal(int branchingFactor, int bucketSize) {
if (bucketSize < 1) {
throw new IllegalArgumentException("Cluster size must "
+ "be an integer larger than 0");
}
if (branchingFactor < 2) {
throw new IllegalArgumentException("Max child size must "
+ "be an integer larger than 1");
}
}
/**
* Appends the specified element to the end of this list. This
* implementation accepts all elements except for {@code null}.
*
* @param e element to add
* @return this method will always return {@code true}
*/
public boolean add(E e) {
if (e == null) {
throw new NullPointerException("Element cannot be null");
}
// add it at the end and propagate change to parents
getTail().append(e);
// update the tail in case it has changed.
ListNode<E> next = getTail().next();
if (next != null) {
setTail(next);
}
return true;
}
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index the index
* @param e the element to add
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
public void add(int index, E e) {
isNotNegative(index);
// Check for the different boundary cases
if (e == null) {
throw new NullPointerException("Element cannot be null");
} else if (getHead().equals(getTail()) && getHead().size() == 0) {
getTail().append(e);
return;
} else if (getHead() == null) {
throw new IndexOutOfBoundsException("Cannot add to index " +
index + " on an empty list");
}
// otherwise, add it to the specified index.
// This requires a search of the list nodes.
SearchResult<E> sr;
if (index != 0) {
sr = getNode(--index);
sr.node.insert(sr.offset + 1, e);
} else {
getHead().insert(0, e);
}
// update the tail in case it has changed.
ListNode<E> next = getTail().next();
if (next != null) {
setTail(next);
}
}
/**
* Removes all of the elements referred to in the list, but retains a
* basic structure that consists of a {@code SubList}, {@code ListNode},
* and a {@code TreeNode}, along with {@code DummyConnector}s
* representing the head and tail of the list. The call to
* {@code removingObject()} is asynchronous, meaning the objects are
* deleted from the data manager in a scheduled task.
*/
public void clear() {
// If the head is null, then the ScalableList is
// not initialized; therefore, no work to do.
if (getHead() == null) {
return;
}
// Otherwise, remove the entire object..
removingObject();
// ..and then supply a new empty structure
TreeNode<E> t = new TreeNode<E>(this, null, false);
headRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
tailRef =
AppContext.getDataManager().createReference(
new DummyConnector<E>(t.getChild()));
setRoot(t);
}
/**
* Returns {@code true} if this list contains the specified element. More
* formally, returns {@code true} if and only if this list contains at
* least one element e such that
* <p>
* {@code (o==null ? e==null : o.equals(e))}.
*
* @param o the object which may be in the list
* @return whether the object is contained in the list; {@code true} if
* so, {@code false} otherwise
*/
public boolean contains(Object o) {
if (o == null) {
return false;
}
return (indexOf(o) != -1);
}
/**
* Returns the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element. More
* formally, returns the lowest index i such that
* <p>
* {@code (o==null ? get(i)==null : o.equals(get(i)))},
* <p>
* or -1 if there is no such index.
*
* @param o the element to search for
* @return the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element.
*/
public int indexOf(Object o) {
int listIndex = 0;
ScalableListNodeIterator<E> iter =
new ScalableListNodeIterator<E>(getHead());
while (iter.hasNext()) {
ListNode<E> n = (ListNode<E>) iter.next();
int index = n.getSubList().indexOf(o);
if (index != -1) {
return listIndex + index;
}
listIndex += n.size();
}
return -1;
}
/**
* Returns the index in this list of the last occurrence of the specified
* element, or -1 if this list does not contain this element. More
* formally, returns the highest index i such that (o==null ? get(i)==null :
* o.equals(get(i))), or -1 if there is no such index.
*
* @param obj element to search for
* @return the index in this list of the last occurrence of the specified
* element, or -1 if this list does not contain this element.
*/
public int lastIndexOf(Object obj) {
int listIndex = 0;
int absIndex = -1;
ScalableListNodeIterator<E> iter =
new ScalableListNodeIterator<E>(getHead());
// For every ListNode encountered, check for an
// instance of the supplied object
while (iter.hasNext()) {
ListNode<E> n = iter.next();
int index = n.getSubList().lastIndexOf(obj);
// Save the most recent occurrence of a matching index
// but keep searching in case we find another in another
// node.
if (index != -1) {
absIndex = listIndex + index;
}
listIndex += n.size();
}
return absIndex;
}
/**
* Determines if the provided index is positive (not negative). If the
* index is negative, then an {@code IndexOutOfBoundsException} is thrown.
*
* @param index the index to check for validity
* @throws IndexOutOfBoundsException if the index is negative
*/
private void isNotNegative(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException("Index " + index +
" cannot be negative");
}
}
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
public E remove(int index) {
isNotNegative(index);
SearchResult<E> sr = getNode(index);
ListNode<E> n = sr.node;
return n.remove(this, sr.offset);
}
/**
* This operation preserves the order of the elements in the list and
* keeps multiple copies of the same elements if they exist.
*
* @param c the collection of elements to keep
* @return {@code true} if this list changed as a result of the call
*/
public boolean retainAll(Collection<?> c) {
Iterator<E> iter = new ScalableIterator<E>(this);
ArrayList<E> newList = new ArrayList<E>();
boolean changed = false;
// For each element in the overall list,
// if it is contained in the supplied
// collection, then we add it to a
// new list instance. Once finished, we
// will add everything from this into
// an emptied list (this).
while (iter.hasNext()) {
E e = iter.next();
// Add the node to the new collection
if (c.contains(e)) {
newList.add(e);
} else {
changed = true;
}
}
// Clear the current list and add all
// elements from the temporary collection.
clear();
addAll(newList);
return changed;
}
/**
* Retrieves the head {@code ListNode}.
*
* @return the head {@code ListNode} if it exists
* @throws ObjectNotFoundException if no reference exists
*/
ListNode<E> getHead() {
return headRef.get().getRefAsListNode();
}
/**
* Retrieves the tail {@code ListNode}.
*
* @return the tail {@code ListNode} if it exists
* @throws ObjectNotFoundException if there is no reference
*/
ListNode<E> getTail() {
return tailRef.get().getRefAsListNode();
}
/**
* Sets the tail of the {@code ListNode} linked-list
*
* @param newTail the tail
*/
void setTail(ListNode<E> newTail) {
tailRef.getForUpdate().setRef(newTail);
}
/**
* Sets the head of the {@code ListNode} linked-list
*
* @param newHead the head
*/
void setHead(ListNode<E> newHead) {
headRef.getForUpdate().setRef(newHead);
}
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index the index to set
* @param obj the object to replace the existing value
* @return the old value which was replaced
*/
@SuppressWarnings("unchecked")
public E set(int index, Object obj) {
if (obj == null) {
throw new NullPointerException(
"Value for set operation cannot be null");
}
SearchResult<E> sr = getNode(index);
return sr.node.set(sr.offset, obj);
}
/**
* Returns the element at the specified position in this list.
*
* @param index the index to retrieve
* @return the element at the supplied index
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
public E get(int index) {
// Iterate through using the count. Use a get()
// iterator because we are not modifying anything; hence, false.
SearchResult<E> sr = getNode(index);
SubList<E> sublist = sr.node.getSubList();
if (sublist == null) {
return null;
}
return sublist.get(sr.offset);
}
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* @return an {@code Iterator} over the elements in the list
*/
public Iterator<E> iterator() {
return new ScalableIterator<E>(this);
}
/**
* Returns a {@code ListIterator} over the elements in this list in proper
* sequence.
*
* @return a {@code ListIterator} over the elements in the list
*/
public ListIterator<E> listIterator() {
return new ScalableListIterator<E>(this);
}
/**
* Returns a {@code ListIterator} over the elements in this list, starting
* from the element at the given index.
*
* @return a {@code ListIterator} over the elements in the list
* @param index the index
* @throws IndexOutOfBoundsException if the index is out of bounds:
* {@code index < 0 || index > size()}
*/
public ListIterator<E> listIterator(int index) {
// Special case: if the index provided equals the size,
// then we must manually supply the starting node because
// a search would throw an IndexOutOfBoundsException
if (index == this.size()) {
ListNode<E> tail = getTail();
return new ScalableListIterator<E>(this, tail, tail.size());
}
return new ScalableListIterator<E>(this, getNode(index));
}
/**
* Removes the first occurrence in this list of the specified element. If
* this list does not contain the element, it is unchanged. More formally,
* removes the element with the lowest index i such that
* <p>
* {@code (o==null ? get(i)==null : o.equals(get(i)))} (if such an element
* exists).
*
* @param obj element to be removed from the list, if present
* @return the element previously at the specified position
*/
public boolean remove(Object obj) {
ScalableListNodeIterator<E> iter =
new ScalableListNodeIterator<E>(getHead());
boolean removed = false;
// Find and remove the object in the ListNode<E> that contains it
while (iter.hasNext()) {
ListNode<E> n = iter.next();
removed = n.remove(this, obj);
if (removed) {
break;
}
}
return removed;
}
/**
* Retrieves the root {@code TreeNode} if it exists or null otherwise.
*
* @return the root node, or null if it does not exist
*/
TreeNode<E> getRoot() {
return this.root.get();
}
/**
* Obtains the child of the root node, since the root node does not get
* updated unless a split/removal occurs. For most operations, returning
* the child is sufficient.
*
* @return
*/
private Node<E> getRootChild() {
return getRoot().getChild();
}
/**
* Sets the root element of the underlying tree structure. This is
* necessary during a split or remove.
*
* @param newRoot the {@code TreeNode} which is to be the new root
*/
void setRoot(TreeNode<E> newRoot) {
AppContext.getDataManager().markForUpdate(this);
root = createReferenceIfNecessary(newRoot);
}
/**
* Returns {@code true} if this list contains no elements.
*
* @return {@code true} if the list is empty, and {@code false} otherwise
*/
public boolean isEmpty() {
return (getHead().size() == 0);
}
/**
* Performs the search for the desired {@code ListNode}. This method will
* locate the {@code ListNode} using a recursive search process and return
* a {@code SearchResult} which contains both the {@code ListNode} and the
* numeric offset corresponding to the provided index.
*
* @param index the absolute index in the entire list to search for the
* entry
* @return the {@code SearchResult} which contains the element at the
* specified {@code index}
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
private SearchResult<E> getNode(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException("Index cannot be negative");
}
// Recursive method to eventually return ListNode<E>
// containing the desired index.
return getRootChild().search(0, index + 1);
}
/**
* Retrieves the size of the list. The size of the list is obtained by
* performing a traversal of the root's children and adding each of their
* sizes. For very large lists, this process can be somewhat expensive as
* it does not occur in constant-time, but rather in a logarithmic time
* proportional to the {@code branchingFactor}.
*
* @return the number of elements in the list
*/
public int size() {
// This gives us a reference to the one and only
// root TreeNode<E>, which parents the entire tree.
Node<E> current = getRootChild();
int size = 0;
// Iterate through the first level of the tree to get
// the sizes of each node.
while (current != null) {
size += current.size();
current = current.next();
}
return size;
}
/**
* {@inheritDoc}
*/
public void removingObject() {
AsynchronousClearTask<E> clearTask =
new AsynchronousClearTask<E>(this);
// Schedule asynchronous task here
// which will delete the list
AppContext.getTaskManager().scheduleTask(clearTask);
// Remove the dummy connectors
DataManager dm = AppContext.getDataManager();
dm.removeObject(headRef.get());
dm.removeObject(tailRef.get());
}
/*
* INLINE CLASSES
*/
/**
* The {@code DummyConnector} class is used as a junction point for two
* {@code ManagedReference}s so that changes to the second
* {@code ManagedReference} need not affect the top structure. The class
* is fairly simple, only containing methods which enable the value to be
* easily set and retrieved.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class DummyConnector<E> implements ManagedObject, Serializable {
private static final long serialVersionUID = 3L;
/**
* The pointer to the object.
*/
private ManagedReference<Node<E>> ref = null;
/**
* Default no-argument constructor.
*/
DummyConnector() {
ref = null;
}
/**
* Constructor which accepts the object
*
* @param reference the element which this object is to point to
*/
DummyConnector(Node<E> reference) {
if (reference != null) {
ref = AppContext.getDataManager().createReference(reference);
}
}
/**
* Sets the reference for this object.
*
* @param newRef the intended new reference
*/
void setRef(Node<E> newRef) {
AppContext.getDataManager().markForUpdate(this);
ref = createReferenceIfNecessary(newRef);
}
/**
* Retrieves the reference as a {@code ListNode}.
*
* @return the reference as a {@code ListNode}, or null if it does
* not exist
*/
ListNode<E> getRefAsListNode() {
if (ref == null) {
return null;
}
return (ListNode<E>) ref.get();
}
}
/**
* An object which forms a tree above the {@code ListNode<E>} linked list.
* Each {@code TreeNode<E>} has a {@code child} reference to either one
* {@code TreeNode<E>} or a {@code ListNode<E>}. The {@code TreeNode<E>}
* also has a reference to its next and previous sibling and its parent.
* <p>
* The {@code TreeNode<E>} is intended to only track the size of its
* descendant children and the number of children that it owns.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class TreeNode<E> implements ManagedObject, Serializable, Node<E> {
// Public fields referenced during propagation
public static final byte DECREMENT_SIZE = 0;
public static final byte INCREMENT_SIZE = 1;
public static final byte INCREMENT_CHILDREN_AND_SIZE = 2;
public static final byte DECREMENT_CHILDREN_AND_SIZE = 3;
private static final long serialVersionUID = 1L;
// References to neighboring elements
private ManagedReference<TreeNode<E>> nextRef;
private ManagedReference<TreeNode<E>> prevRef;
private ManagedReference<Node<E>> childRef;
private ManagedReference<TreeNode<E>> parentRef;
private final ManagedReference<ScalableList<E>> owner;
/**
* The maximum number of children this node can contain
*/
private final int branchingFactor;
/**
* The maximum number of elements which the underlying
* {@code ListNode} can store
*/
private final int bucketSize;
/**
* The number of elements that exist as descendants of this node
*/
private int size = 0;
/**
* The number of immediate children
*/
private int childrenCount = 0;
/**
* Constructor which is called on a split. This is used to create a
* new sibling and accepts a TreeNode argument which represents the
* new child it is to possess.
*
* @param list a reference to the owning {@code ScalableList}
* @param parent the intended parent
* @param child the child to be added underneath the new
* {@code TreeNode}
* @param numberChildren the total number of children that will exist
* once the {@code TreeNode} is instantiated. This is based on the
* nature of the linked list of {@code ListNodes} attached to the
* {@code child}.
* @param size the total number of elements that will exist under the
* {@code TreeNode}, based on the elements that already exist among
* the {@code child} and its siblings.
*/
private TreeNode(ScalableList<E> list, TreeNode<E> parent,
Node<E> child, int numberChildren, int size) {
this(list);
// Set up links
assert (child != null);
childRef = AppContext.getDataManager().createReference(child);
// this might be the root so parent could be null
parentRef = createReferenceIfNecessary(parent);
childrenCount = numberChildren;
this.size = size;
}
/**
* Constructor which is called by public constructors.
*
* @param list the {@code ScalableList} which is the owner of this
* structure
*/
private TreeNode(ScalableList<E> list) {
owner = AppContext.getDataManager().createReference(list);
this.branchingFactor = list.getBranchingFactor();
nextRef = null;
this.bucketSize = list.getBucketSize();
}
/**
* Create a new TreeNode on account of a new leaf ({@code ListNode})
* being created.
*
* @param list the {@code ScalableList} which is the owner of this
* structure
* @param parent the intended parent
* @param e an element to add into the empty {@code ListNode}
*/
TreeNode(ScalableList<E> list, TreeNode<E> parent, E e) {
this(list);
assert (e != null);
ListNode<E> n = new ListNode<E>(this, bucketSize, e);
size = n.size();
childrenCount = 1;
DataManager dm = AppContext.getDataManager();
childRef = dm.createReference((Node<E>) n);
parentRef = createReferenceIfNecessary(parent);
}
/**
* Constructor which creates a {@code TreeNode} while specifying
* parameters for the node characteristics
*
* @param list the {@code ScalableList} owner of this structure
* @param parent the intended parent {@code ListNode}
* @param isSplit {@code true} if the {@code TreeNode} is to be
* created due to a {@code split} operation, and {@code false}
* otherwise.
*/
TreeNode(ScalableList<E> list, TreeNode<E> parent, boolean isSplit) {
this(list);
if (!isSplit) {
ListNode<E> n = new ListNode<E>(this, bucketSize);
DataManager dm = AppContext.getDataManager();
size = n.size();
childRef = dm.createReference((Node<E>) n);
} else {
size = 0;
childRef = null;
}
parentRef = createReferenceIfNecessary(parent);
}
/**
* Returns a {@code String} representation of this object.
*
* @return a {@code String} representation of this object.
*/
public String toString() {
StringBuilder s = new StringBuilder();
int size = 0;
int children = getChildCount();
Node<E> node = getChild();
// Add information for each child
for (int i = 0; i < children; i++) {
if (i > 0) {
s.append(";");
}
s.append(node.toString());
node = node.next();
size += node.size();
}
// Add list metrics
s.append("size: ");
s.append(size);
s.append(", children: ");
s.append(children);
return s.toString();
}
/**
* {@inheritDoc}
*/
public TreeNode<E> prev() {
if (prevRef == null) {
return null;
}
return prevRef.get();
}
/**
* {@inheritDoc}
*/
public void setPrev(Node<E> ref) {
AppContext.getDataManager().markForUpdate(this);
TreeNode<E> t = uncheckedCast(ref);
prevRef = createReferenceIfNecessary(t);
}
/**
* {@inheritDoc}
*/
public TreeNode<E> next() {
if (nextRef == null) {
return null;
}
return nextRef.get();
}
/**
* Obtains the child of the current node. The child represents the
* head of the list of children. The {@code TreeNode} does not have
* references to all the children, so it uses knowledge of the head to
* iterate through them.
*
* @return the child, which can be either a {@code TreeNode} or
* {@code ListNode}, depending on the position of the current node in
* the tree.
*/
Node<E> getChild() {
if (childRef == null) {
return null;
}
return childRef.get();
}
/**
* Sets the child to be the supplied parameter as long as it is not
* null. This method also updates the size of the parent because
* changing the child suggests that a new size exists.
*
* @param child the new child
*/
void setChild(Node<E> child, int size, int numberOfChildren) {
AppContext.getDataManager().markForUpdate(this);
this.size = size;
this.childrenCount = numberOfChildren;
childRef = createReferenceIfNecessary(child);
}
/**
* Sets the child to null for the relinking process.
*/
void setChildToNull() {
AppContext.getDataManager().markForUpdate(this);
childRef = null;
}
/**
* {@inheritDoc}
*/
public int size() {
return size;
}
/**
* Recursively increments the node's size until reaching the root. The
* root is not updated to enable some degree of concurrency.
*/
void increment() {
if (getParent() == null) {
return;
}
AppContext.getDataManager().markForUpdate(this);
size++;
getParent().increment();
}
/**
* Recursively decrements the node's size until reaching the root. The
* root is not updated to enable some degree of concurrency.
*/
void decrement() {
if (getParent() == null) {
return;
}
AppContext.getDataManager().markForUpdate(this);
if (--size == 0) {
getParent().decrementChildrenAndSize();
} else {
getParent().decrement();
}
}
/**
* {@inheritDoc}
*/
public void clear() {
TreeNode<E> parent = getParent();
AppContext.getDataManager().removeObject(this);
if (parent != null) {
parent.clear();
}
}
/**
* Retrieves the number of immediate children beneath this node.
*
* @return the number of immediate children
*/
int getChildCount() {
// If this is the root, then we have to iterate and
// obtain the actual value.
if (getParent() == null) {
return getNumberOfChildrenUsingIteration();
}
return childrenCount;
}
/**
* {@inheritDoc}
*/
public TreeNode<E> getParent() {
if (parentRef == null) {
return null;
}
return parentRef.get();
}
/**
* {@inheritDoc}
*/
public void setNext(Node<E> ref) {
AppContext.getDataManager().markForUpdate(this);
TreeNode<E> t = uncheckedCast(ref);
nextRef = createReferenceIfNecessary(t);
}
/**
* Unlinks itself from the tree without performing a recursive
* deletion. This method is guaranteed to delete itself. This method
* re-links references that are dangling as a result of this node's
* removal. There are four conditions:
* <p>
* The first condition is if the {@code TreeNode} is an intermediate
* node. If so, it is necessary to link the left and right siblings
* together before the node is pruned.
* <p>
* The second condition is if the {@code TreeNode} is the tail node.
* If so, then the previous element's next reference is set to null
* before the node is pruned.
* <p>
* The third condition is if the {@code TreeNode} is the head node. If
* so, then the next element's previous reference is set to null
* before the node is pruned.
* <p>
* The last condition is if the {@code TreeNode} is an only- child. If
* so, then the parent's child reference is set to null before the
* node is pruned.
*/
void prune() {
// Decide how to perform re-linking of the nodes,
if (prevRef != null) {
// interior TreeNode
if (nextRef != null) {
prevRef.getForUpdate().setNext(this.next());
nextRef.getForUpdate().setPrev(this.prev());
} else {
// relink tail
prevRef.getForUpdate().setNext(null);
}
} else if (nextRef != null) {
// relink head and temporarily point to the next sibling.
// If the next sibling didn't belong to the parent, then
// the parent will be removed anyway since its child
// count will have reached 0.
nextRef.getForUpdate().setPrev(null);
parentRef.getForUpdate().setChild(nextRef.get(),
parentRef.get().size(),
parentRef.get().getChildCount());
} else {
TreeNode<E> parent = parentRef.getForUpdate();
parent.setChildToNull();
}
AppContext.getDataManager().removeObject(this);
}
/**
* Determines where to split a list of children based on the list
* size. By default, this is set to {@code numberOfChildren}/2.
*
* @param numberOfChildren the number of children to split
* @return the index corresponding to the location where to split the
* linked list
*/
private static int calculateSplitSize(int numberOfChildren) {
return numberOfChildren / 2;
}
/**
* This method walks up the tree and performs any {@code TreeNode<E>}
* splits if the children sizes have been exceeded.
*/
private boolean performSplitIfNecessary() {
// Check if we need to split
if (childrenCount > branchingFactor) {
split();
return true;
}
return false;
}
/**
* Splits the children into two roughly equal groups, and generates a
* sibling to parent one of the groups.
*/
private void split() {
TreeNode<E> newNode = null;
Node<E> tmp = (Node<E>) this.getChild();
AppContext.getDataManager().markForUpdate(this);
// Updates the reference of the new root, if created
generateParentIfNecessary();
// Perform split by creating and linking new sibling
newNode = createAndLinkSibling(tmp, childrenCount);
// Link the new sibling to the tree
newNode.setPrev(this);
newNode.setNext(this.next());
this.setNext(newNode);
if (newNode.next() != null) {
newNode.next().setPrev(newNode);
}
}
/**
* Determines the nature of the sibling to be created. The Node
* created will always be a TreeNode<E>, but the type of child
* depends on whether it parents ListNodes, or other TreeNodes.
*
* @param child the {@code ManagedObject} which needs to be split and
* placed under a new parent
* @param numberOfChildren the number of children which the new
* sibling will parent
* @return the {@TreeNode<E>} that not represents a sibling which is
* connected to the tree
*/
private TreeNode<E> createAndLinkSibling(Node<E> child,
int numberOfChildren) {
int halfway = calculateSplitSize(numberOfChildren);
int size = 0;
// Create the new sibling
TreeNode<E> newNode =
new TreeNode<E>(owner.get(), getParent(), true);
// Iterate through the children and update references
Node<E> newChild = child;
int i = 0;
int children = 0;
while (child != null && i < numberOfChildren) {
// When we reach halfway, save the child as it
// will be the child of the new TreeNode. When
// we pass halfway, update the parents and
// decrease the size.
if (i == halfway) {
newChild = child;
if (child instanceof TreeNode) {
child.prev().setNext(null);
child.setPrev(null);
}
}
if (i++ >= halfway) {
size += child.size();
children++;
child.setParent(newNode);
}
child = child.next();
}
// Update the new parent's child, its size,
// and our own size
newNode.setChild(newChild, size, children);
AppContext.getDataManager().markForUpdate(this);
this.childrenCount -= children;
this.size -= size;
return newNode;
}
/**
* This method creates a parent above the provided node so that any
* new siblings resulting from a split can be joined to the new
* parent. The decision to create a new parent depends on whether the
* supplied node is the root; only these nodes are orphaned while
* siblings have parents assigned to them.
*/
private void generateParentIfNecessary() {
// If this is the currently existing root, make a new one
// and set the this node as the child
if (getParent() == null) {
TreeNode<E> grandparent =
new TreeNode<E>(owner.get(), null, this, 1, size());
// Link the node to its new parent
setParent(grandparent);
owner.getForUpdate().setRoot(grandparent);
}
}
/**
* Sets the parent for the node.
*
* @param parent the intended parent
*/
public void setParent(TreeNode<E> parent) {
AppContext.getDataManager().markForUpdate(this);
parentRef = createReferenceIfNecessary(parent);
}
/**
* Increments the number of children and size, and determines whether
* the parent should do both, or just perform an increment of the
* size.
*/
void incrementChildrenAndSize() {
AppContext.getDataManager().markForUpdate(this);
TreeNode<E> parent = getParent();
// If we are at the root, then check if we need to perform a
// split. This will generate a new root.
if (parent == null) {
int numberOfRootChildren =
getNumberOfChildrenUsingIteration();
// Split if too many root children. Here we get
// the real size since we don't update the root's
// size to allow for concurrency. Since it will
// no longer be the root, we need to find and set
// its value.
if (numberOfRootChildren > branchingFactor) {
size = owner.get().size();
childrenCount = numberOfRootChildren;
split();
}
return;
}
size++;
childrenCount++;
// If it is not the root node, then
// see if we need to split it normally.
if (!performSplitIfNecessary()) {
parent.increment();
return;
}
parent.incrementChildrenAndSize();
}
/**
* Decrements the number of children and size, and determines whether
* the parent should do both again, or just decrement the size.
*/
void decrementChildrenAndSize() {
TreeNode<E> parent = getParent();
// We are done when we reach the root
if (parent == null) {
return;
}
AppContext.getDataManager().markForUpdate(this);
size--;
childrenCount--;
/*
* If there are no children, then prune this node accordingly. If
* the parent has only one child (this), then link the parent to
* the children and prune this node. For both these cases, we
* propagate the decrement of size and children to the parent.
* Otherwise, we only propagate the decrement the size.
*/
if (childrenCount == 0 && size() == 0) {
prune();
parent.decrementChildrenAndSize();
return;
} else if (parent.getChildCount() == 1) {
pruneIntermediate();
}
parent.decrement();
}
/**
* Removes a {@code TreeNode} that is the only child of a parent. This
* requires that all children are updated of its new parent.
*/
private void pruneIntermediate() {
TreeNode<E> parent = getParent();
Node<E> child = childRef.getForUpdate();
parent.setChild(child, parent.size(), childrenCount);
for (int i = 0; i < childrenCount; i++) {
child.setParent(parent);
child = child.next();
}
AppContext.getDataManager().removeObject(this);
}
/**
* This method retrieves the number of children by iterating through
* them one by one. This method is used when the current node is the
* root, since the field {@code childrenCount} does not get updated.
*
* @return the number of children
*/
private int getNumberOfChildrenUsingIteration() {
int total = 0;
Node<E> child = getChild();
while (child != null) {
total++;
child = child.next();
}
return total;
}
/**
* {@inheritDoc}
*/
public SearchResult<E> search(int currentValue, int destIndex) {
TreeNode<E> t = this;
// Iterate through siblings
while ((currentValue + t.size) < destIndex) {
currentValue += t.size;
t = t.next();
// Check if the specified index was too large;
if (t == null) {
throw new IndexOutOfBoundsException("The index " +
destIndex + " is out of range.");
}
}
return t.getChild().search(currentValue, destIndex);
}
}
/**
* An asynchronous task which iterates through the tree and removes all
* children. This task is instantiated when a {@code clear()} command is
* issued from the {@code ScalableList}. The success of this operation is
* dependent on implemented {@code clear()} methods on each of the
* underlying components.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
private static class AsynchronousClearTask<E> implements Serializable,
Task, ManagedObject {
private static final long serialVersionUID = 4L;
/**
* A reference to the current {@code Node} whose children are being
* deleted
*/
private ManagedReference<ListNode<E>> current;
/**
* Constructor for the asynchronous task
*
* @param root the root node of the entire tree structure
*/
AsynchronousClearTask(ScalableList<E> list) {
assert (list != null);
DataManager dm = AppContext.getDataManager();
current = dm.createReference(list.getHead());
}
/**
* The entry point of the task to perform the clear.
*/
public void run() {
// Perform some work and check if we need to reschedule
DataManager dm = AppContext.getDataManager();
dm.markForUpdate(this);
if (doWork()) {
AppContext.getTaskManager().scheduleTask(this);
} else {
dm.removeObject(this);
Runnable r = noteDoneRemoving;
if (r != null) {
r.run();
}
}
}
/**
* Removes some number of elements from the ScalableList and
* returns {@code true} if there is more work to be done. If there are
* no more elements to remove, then it will return {@code false},
* signifying to the {@code AsynchronousClearTask} to start taking
* values from the queue
*
* @return {@code true} if more work needs to be done, and
* {@code false} if there are no more elements to remove.
*/
private boolean doWork() {
ListNode<E> currentListNode = current.get();
AppContext.getDataManager().markForUpdate(currentListNode);
ListNode<E> next;
int size = currentListNode.size();
E entry = null;
// Perform some removals
while (size > 0 && currentListNode != null &&
AppContext.getTaskManager().shouldContinue()) {
// Repeatedly remove the head element in the
// ListNode as long as one exists.
next = currentListNode.next();
entry = currentListNode.remove(null, 0);
if (--size == 0 && next != null) {
currentListNode = next;
size = currentListNode.size();
}
if (entry instanceof Element) {
AppContext.getDataManager().removeObject(entry);
}
}
// If we exit and the size is reported to be 0, then we
// call the clear() method to delete the empty tree.
// Otherwise, we will set up to re-execute the task
if (size == 0) {
currentListNode.clear();
return false;
} else {
current =
AppContext.getDataManager().createReference(
currentListNode);
}
return true;
}
}
/**
* This class represents a stored entity of the list. It is a wrapper for
* any object that is stored in the list so that the list can refer to it
* by using a ManagedReference, rather than the actual object itself. This
* makes managing sublists less intensive as each reference to an
* underlying entity is a fixed size.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class Element<E> implements Serializable, ManagedObject {
private static final long serialVersionUID = 5L;
/**
* The stored value which is not a {@code ManagedObject}. If it were
* a {@code ManagedObject}, then it would not need to be enveloped by
* an {@code Element} wrapper.
*/
private E value;
/**
* Constructor for creating an {@code Element}.
*
* @param e the element to store within the {@code Element}
* {@code ManagedObject}
*/
Element(E e) {
value = e;
}
/**
* Retrieves the element.
*
* @return the element stored by this object
*/
E getValue() {
return value;
}
/**
* Sets the value of the element
*
* @param e the new value to set
*/
void setValue(E e) {
assert (e != null);
AppContext.getDataManager().markForUpdate(this);
value = e;
}
/**
* Returns a {@code String} representation
*
* @return the value in {@code String} form
*/
public String toString() {
return value.toString();
}
}
/**
* This iterator walks through the {@code ListNode<E>}s which parent the
* {@code SubList}s. An iterator for this type of element is necessary to
* support the other element iterator as well as {@code indexOf()}
* operations.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class ScalableListNodeIterator<E> implements Serializable,
Iterator<ListNode<E>> {
/**
* The current position of the iterator. This is initialized as
* {@code null} and is set to null when there are no more elements.
*/
private ListNode<E> current;
/**
* The next position of the iterator. This is initialized as the head
* element provided in the constructor
*/
private ListNode<E> next;
/**
* The previous position of the iterator. This is initialized as the
* head element provided in the constructor
*/
private ListNode<E> prev;
private static final long serialVersionUID = 7L;
/**
* Constructor to create a new iterator.
*
* @param head the head {@code ListNode} from which to begin iterating
*/
ScalableListNodeIterator(ListNode<E> head) {
prev = null;
current = null;
next = head;
}
/**
* Determines if there is a next element to iterate over; that is, if
* the next {@code ListNode} is not null.
*
* @return {@code true} if the next element is not null, and
* {@code false} otherwise
*/
public boolean hasNext() {
return (next != null);
}
/**
* Returns the next {@code ListNode<E>} in order from the list
*
* @return the next {@code ListNode}
* @throws NoSuchElementException if there exists no next sibling
*/
public ListNode<E> next() {
if (current == null && next == null) {
throw new NoSuchElementException("There is no next element");
}
prev = current;
current = next;
if (next != null) {
next = next.next();
}
return current;
}
/**
* Determines if there is a previous element to iterate over; that is,
* if the previous {@code ListNode} is not null.
*
* @return {@code true} if the previous element is not null, and
* {@code false} otherwise
*/
public boolean hasPrev() {
return (prev != null);
}
/**
* Returns the previous {@code ListNode<E>} in order from the list
*
* @return the previous {@code ListNode}
* @throws NoSuchElementException if there exists no previous sibling
*/
public ListNode<E> prev() {
if (current == null && prev == null) {
throw new NoSuchElementException("There is no prev element");
}
next = current;
current = prev;
if (prev != null) {
prev = prev.prev();
}
return current;
}
/**
* @deprecated this operation is not supported because the
* {@code ScalableList} only removes elements explicitly, not
* {@code ListNodes}.
* @throws UnsupportedOperationException the operation is not
* supported
*/
public void remove() {
throw new UnsupportedOperationException(
"This method is not supported");
}
}
/**
* This class represents an iterator of the elements of the
* {@code ScalableList}.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class ScalableIterator<E> implements Serializable, Iterator<E> {
/**
* A reference to the {@code ScalableList} which this iterator is
* referring to
*/
final ManagedReference<ScalableList<E>> owner;
/**
* The current {@code ListNode} of the iterative process
*/
protected ManagedReference<ListNode<E>> currentNode;
/**
* The iteration location of the list
*/
protected int cursor;
/**
* Flag which only lets one removal happen per call to {@code next()}
*/
protected boolean wasNextCalled;
/**
* The value for the current {@code ListNode} to determine if any
* changes have taken place since the last time it was accessed
*/
protected long listNodeReferenceValue = -1;
private static final long serialVersionUID = 8L;
/**
* Performs a check to see that the index for {@code next()} is still
* within range of the sub list.
*
* @param <E> the type of element stored
* @param offset the offset
* @param currentListNode the current {@code ListNode} being examined
* @return {@code true} if the offset exists in the sub list and
* {@code false} otherwise
*/
static <E> boolean isNextWithinRange(int offset,
ListNode<E> currentListNode) {
return (offset < currentListNode.size());
}
/**
* Constructor used to create a {@code ScalableListIterator} for the
* underlying elements in the {@code ScalableList}.
*
* @param list the {@code ScalableList} over which to iterate
*/
ScalableIterator(ScalableList<E> list) {
this(list, list.getHead());
}
ScalableIterator(ScalableList<E> list, ListNode<E> startingNode) {
owner = AppContext.getDataManager().createReference(list);
currentNode =
AppContext.getDataManager().createReference(startingNode);
cursor = 0;
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
wasNextCalled = false;
}
/**
* Retrieves the index of the current element by walking up the tree
* to the root and aggregating the counts of each {@code ListNode} and
* {@code TreeNode}. This operation is slightly expensive because of
* the required percolation up the tree.
*
* @return the absolute index of the current element being examined
*/
int getCurrentIndex() {
return getAbsoluteIndex(currentNode.get(), 1) + cursor;
}
/**
* Walks up the tree and collects the sizes to produce an absolute
* index.
*
* @return the absolute index of the given node
*/
int getAbsoluteIndex(Node<E> node, int size) {
TreeNode<E> parent = node.getParent();
// We reached the root; return the size we have.
if (parent == null) {
return size - 1;
}
Node<E> firstChild = parent.getChild();
// Iterate through siblings. We don't
// aggregate the first node's size because
// we already found the offset from the
// caller.
while (!node.equals(firstChild)) {
node = node.prev();
size += node.size();
}
return getAbsoluteIndex(parent, size);
}
/**
* Retrieves the next element.
*
* @throws ConcurrentModificationException if the {@code ListNode}
* that the iterator is pointing to has been modified to (addition or
* removal) by someone else
* @throws NoSuchElementException if there is no next element
* @return the next element
*/
@SuppressWarnings("unchecked")
public E next() {
// Check the integrity of the ListNode to see if
// any changes were made since we last operated.
// Throw a ConcurrentModificationException if so.
checkDataIntegrity();
List<ManagedReference<ManagedObject>> elements =
currentNode.get().getSubList().getElements();
// Retrieve the next value from the list; we may have to load
// up a new ListNode
int index = getCursorBasedOnPreviousAction(true);
if (!isNextWithinRange(index, currentNode.get())) {
if (loadNextListNode()) {
elements = currentNode.get().getSubList().getElements();
index = 0;
} else {
throw new NoSuchElementException(
"There is no next element");
}
}
cursor = index;
wasNextCalled = true;
// Once we have located the next node,
// update the reference values
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
// In case we wrapped the item with an
// Element object, fetch the value.
return (E) getValueFromReference(elements.get(cursor), false);
}
/**
* Retrieve the index of interest, based on our previous direction and
* intended direction
*
* @param next whether we intend on travelling in the {@code next}
* direction; {@code true} if so, and {@code false} otherwise
* @return the index corresponding to the cursor
*/
protected int getCursorBasedOnPreviousAction(boolean next) {
if (next) {
return (wasNextCalled ? cursor + 1 : cursor);
} else {
return (wasNextCalled ? cursor : cursor - 1);
}
}
/**
* Get the next {@code ListNode} for the
* {@code ScalableListNodeIterator}.
*
* @return {@code true} if there was a next {@code ListNode}, and
* {@code false} otherwise
*/
private boolean loadNextListNode() {
ListNode<E> next = currentNode.get().next();
if (next != null) {
currentNode =
AppContext.getDataManager().createReference(next);
cursor = 0;
return true;
}
return false;
}
/**
* Checks whether the data integrity value has changed, and throws a
* {@code ConcurrentModificationException} if so.
*
* @throws ConcurrentModificationException if the data integrity value
* has changed or if it cannot be verified
*/
void checkDataIntegrity() {
try {
if (currentNode.get().getDataIntegrityValue() ==
listNodeReferenceValue) {
return;
}
} catch (ObjectNotFoundException onfe) {
}
throw new ConcurrentModificationException(
"The ListNode has been modified or removed");
}
/**
* Returns whether there is a next element to iterate over.
*
* @return {@code true} if there is a next element, or {@code false}
* otherwise
* @throws ConcurrentModificationException if the {@code ListNode} has
* been modified or removed
*/
public boolean hasNext() {
checkDataIntegrity();
// If there is an element in the iterator still,
// then simply return true since it will be the
// next element to be returned.
if (isNextWithinRange(getCursorBasedOnPreviousAction(true),
currentNode.get())) {
return true;
}
// Try seeing if there is a next ListNode
return (currentNode.get().next() != null);
}
/**
* Removes from the underlying collection the last element returned by
* the iterator. This can only be called once per call to {@code next}.
*/
public void remove() {
if (!wasNextCalled) {
throw new IllegalStateException(
"remove() must follow next() or previous()");
}
doRemove();
wasNextCalled = false;
}
/**
* Performs the remove and updates the references if necessary
*/
void doRemove() {
ListNode<E> prev = currentNode.get().prev();
ListNode<E> next = currentNode.get().next();
int size = currentNode.get().size();
currentNode.get().remove(owner.get(), cursor);
// if the removal caused a ListNode to be removed,
// then we need to replace the currentNode with a
// valid one.
if (size == 1 || cursor == 0) {
if (prev != null) {
currentNode =
AppContext.getDataManager().createReference(prev);
cursor = currentNode.get().size();
} else if (next != null) {
currentNode =
AppContext.getDataManager().createReference(next);
cursor = 0;
} else {
cursor = 0;
}
} else {
cursor--;
}
// update the data integrity value
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
}
}
/**
* A class which implements a {@code ListIterator} for the
* {@code ScalableList} data structure. This iterator allows
* bi-directional traversal and other operations native to the
* {@code ListIterator} interface.
*
* @param <E> the type of element
*/
static class ScalableListIterator<E> extends ScalableIterator<E>
implements ListIterator<E> {
private static final long serialVersionUID = 9L;
/**
* A flag which records whether a removal or set is an invalid
* operation. {@code True} if invalid, and {@code false} otherwise.
*/
private boolean cannotRemoveOrSet = true;
/**
* Constructor which starts the iterations at the specified
* {@code ListNode}.
*
* @param list the {@code ScalableList} over which to iterate
*/
ScalableListIterator(ScalableList<E> list) {
super(list);
}
/**
* Constructor which creates a {@code ScalableListIterator} given the
* list, a {@code startingNode} and a {@code startingIndex} denoting
* the starting point. This constructor is used primarily for when the
* user specifies an index that is one larger than the highest index
* value.
*
* @param list a reference to the {@code ScalableList}
* @param startingIndex the starting index (relative)
* @param startingNode the starting node
*/
ScalableListIterator(ScalableList<E> list, ListNode<E> startingNode,
int startingIndex) {
super(list, startingNode);
wasNextCalled = false;
cursor = startingIndex;
}
/**
* Constructor which creates a {@code ScalableListIterator} given the
* list and a {@code searchResult} denoting the starting point.
*
* @param list
* @param searchResult
*/
ScalableListIterator(ScalableList<E> list,
SearchResult<E> searchResult) {
super(list, searchResult.node);
cursor = searchResult.offset;
}
/**
* Performs a check to see that the index for {@code prev()} is still
* within range of the sub list.
*
* @param <E> the type of element stored
* @param offset the offset
* @param currentListNode the current {@code ListNode} being examined
* @return {@code true} if the offset exists in the sub list and
* {@code false} otherwise
*/
static <E> boolean isPrevWithinRange(int offset,
ListNode<E> currentListNode) {
return (offset >= 0);
}
/**
* Get the previous {@code ListNode} for the
* {@code ScalableListNodeIterator}.
*
* @return {@code true} if there was a previous {@code ListNode}, and
* {@code false} otherwise
*/
private boolean loadPrevListNode() {
ListNode<E> prev = currentNode.get().prev();
if (prev != null) {
currentNode =
AppContext.getDataManager().createReference(prev);
return true;
}
return false;
}
/**
* Returns the index of the element that would be returned by a
* subsequent call to <tt>next</tt>. (Returns list size if the list
* iterator is at the end of the list.)
*
* @return the index of the element that would be returned by a
* subsequent call to <tt>next</tt>, or list size if list iterator
* is at end of list.
*/
public int nextIndex() {
int nextIndex = getCurrentIndex();
if (wasNextCalled) {
nextIndex++;
}
return nextIndex;
}
/**
* Returns the index of the element that would be returned by a
* subsequent call to <tt>previous</tt>. (Returns -1 if the list
* iterator is at the beginning of the list.)
*
* @return the index of the element that would be returned by a
* subsequent call to <tt>previous</tt>, or -1 if list iterator is
* at beginning of list.
*/
public int previousIndex() {
int previousIndex = getCurrentIndex();
if (!wasNextCalled) {
previousIndex--;
}
return previousIndex;
}
/**
* Returns <tt>true</tt> if this list iterator has more elements
* when traversing the list in the reverse direction. (In other words,
* returns <tt>true</tt> if <tt>previous</tt> would return an
* element rather than throwing an exception.)
*
* @return <tt>true</tt> if the list iterator has more elements when
* traversing the list in the reverse direction.
* @throws ConcurrentModificationException if the {@code ListNode} has
* been modified or removed
*/
public boolean hasPrevious() {
checkDataIntegrity();
// If the future index will be equal to or larger than 0,
// then another element exists
if (isPrevWithinRange(getCursorBasedOnPreviousAction(false),
currentNode.get())) {
return true;
}
// Otherwise, try loading the next ListNode
return (currentNode.get().prev() != null);
}
/**
* Returns the previous element in the list. This method may be called
* repeatedly to iterate through the list backwards, or intermixed
* with calls to <tt>next</tt> to go back and forth. (Note that
* alternating calls to <tt>next</tt> and <tt>previous</tt> will
* return the same element repeatedly.)
*
* @return the previous element in the list
* @throws NoSuchElementException if the iteration has no previous
* element
* @throws ConcurrentModificationException if the {@code ListNode} has
* been modified or removed
*/
@SuppressWarnings("unchecked")
public E previous() {
// Check the integrity of the ListNode to see if
// any changes were made since we last operated.
// Throw a ConcurrentModificationException if so.
checkDataIntegrity();
List<ManagedReference<ManagedObject>> elements =
currentNode.get().getSubList().getElements();
// Retrieve the value from the list; we may have to load
// up a new ListNode
int index = getCursorBasedOnPreviousAction(false);
if (!isPrevWithinRange(index, currentNode.get())) {
if (loadPrevListNode()) {
elements = currentNode.get().getSubList().getElements();
index = currentNode.get().size() - 1;
} else {
throw new NoSuchElementException(
"The previous element does not exist");
}
}
cannotRemoveOrSet = false;
cursor = index;
wasNextCalled = false;
// Once we have found the previous node,
// update the reference values
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
return (E) getValueFromReference(elements.get(cursor), false);
}
/**
* Replaces the last element returned by <tt>next</tt> or
* <tt>previous</tt> with the specified element (optional
* operation). This process will automatically remove the old element
* from the data manager if it was not a {@code ManagedObject}.
*
* @param o the element with which to replace the last element
* returned by <tt>next</tt> or <tt>previous</tt>.
* @throws IllegalStateException if the operation is called without
* {@code next} or {@code previous} being called
*/
public void set(E o) {
if (cannotRemoveOrSet) {
throw new IllegalStateException(
"set() must follow next() or previous()");
}
currentNode.get().set(cursor, o);
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
}
/**
* Removes from the underlying collection the last element returned by
* the iterator. This can only be called once per call to {@code next}.
*
* @throws IllegalStateException if the method is not preceded by
* {@code next()} or {@code prev()}
*/
public void remove() {
if (cannotRemoveOrSet) {
throw new IllegalStateException(
"remove() must follow next() or previous()");
}
if (!wasNextCalled) {
this.doRemove();
} else {
super.remove();
}
cannotRemoveOrSet = true;
}
/**
* {@inheritDoc}
*/
void doRemove() {
ListNode<E> prev = currentNode.get().prev();
ListNode<E> next = currentNode.get().next();
int size = currentNode.get().size();
currentNode.get().remove(owner.get(), cursor);
// if the removal caused a ListNode to be removed,
// then we need to replace the currentNode with a
// valid one.
if (size == 1 || cursor == 0) {
if (next != null) {
currentNode =
AppContext.getDataManager().createReference(next);
cursor = 0;
} else if (prev != null) {
currentNode =
AppContext.getDataManager().createReference(prev);
cursor = currentNode.get().size();
} else {
cursor = 0;
}
} else {
cursor++;
}
// update the data integrity value
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
}
/**
* {@inheritDoc}
*/
public E next() {
E result = super.next();
cannotRemoveOrSet = false;
return result;
}
/**
* Inserts the specified element into the list. The element is
* inserted immediately before the next element that would be returned
* by <tt>next</tt>, if any, and after the next element that would
* be returned by <tt>previous</tt>, if any. (If the list contains
* no elements, the new element becomes the sole element on the list.)
* The new element is inserted before the implicit cursor: a
* subsequent call to <tt>next</tt> would be unaffected, and a
* subsequent call to <tt>previous</tt> would return the new
* element. (This call increases by one the value that would be
* returned by a call to <tt>nextIndex</tt> or
* <tt>previousIndex</tt>.)
*
* @param o the element to insert.
* @throws IndexOutOfBoundsException if the index which the element is
* to be added is out of bounds
*/
public void add(E o) {
// Add element to what will be the next index
if (wasNextCalled) {
currentNode.get().insert(++cursor, o);
} else {
currentNode.get().insert(cursor++, o);
}
// If the addition added a new list node,
// then we need to point to it
if (cursor > currentNode.get().size() - 1) {
currentNode =
AppContext.getDataManager().createReference(
currentNode.get().next());
cursor = 0;
}
cannotRemoveOrSet = true;
listNodeReferenceValue =
currentNode.get().getDataIntegrityValue();
}
}
/**
* Node which parents a {@code SubList<E>}. These nodes can be considered
* as the leaf nodes of the tree and contain references to a portion of
* the list. A {@code ListNode}'s parent is always a {@code TreeNode}
* since they are the deepest organizational element of the
* {@code ScalableList}. {@code ListNode}s are arranged in a
* doubly-linked list, each having a reference to its parent.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class ListNode<E> implements ManagedObject, Serializable, Node<E> {
private static final int DATA_INTEGRITY_STARTING_VALUE =
Integer.MIN_VALUE;
// References to neighbors
private ManagedReference<SubList<E>> subListRef;
private ManagedReference<ListNode<E>> nextRef;
private ManagedReference<ListNode<E>> prevRef;
private ManagedReference<TreeNode<E>> parentRef;
private static final long serialVersionUID = 9L;
/**
* A value which increments when changes are made to the node. This is
* used by iterators to deal with concurrent modifications
*/
private int dataIntegrityVal;
/**
* The number of elements contained in the SubList.
*/
private int count;
/**
* Private constructor which the public constructors will call.
*
* @param parent the intended parent
*/
private ListNode(TreeNode<E> parent) {
nextRef = null;
prevRef = null;
dataIntegrityVal = DATA_INTEGRITY_STARTING_VALUE;
parentRef = AppContext.getDataManager().createReference(parent);
}
/**
* Constructor which uses knowledge of a parent and maximum list size.
* A {@code ListNode} that exceeds {@code maxSize} will be subject to
* splitting.
*
* @param parent the intended parent
* @param maxSize the maximum number of elements that can be stored
*/
ListNode(TreeNode<E> parent, int maxSize) {
this(parent);
SubList<E> sublist = new SubList<E>(maxSize);
count = sublist.size();
subListRef = AppContext.getDataManager().createReference(sublist);
}
/**
* Constructor which uses knowledge of a parent and maximum list size.
* A {@code ListNode} that exceeds {@code maxSize} will be subject to
* splitting.
*
* @param parent the intended parent
* @param maxSize the maximum number of elements that can be stored
* @param e an element which is to be stored as the first item in the
* list
*/
ListNode(TreeNode<E> parent, int maxSize, E e) {
this(parent);
SubList<E> sublist = new SubList<E>(maxSize, e);
count = sublist.size();
subListRef = AppContext.getDataManager().createReference(sublist);
}
/**
* Constructor which uses knowledge of a parent and maximum list size.
* A {@code ListNode} that exceeds {@code maxSize} will be subject to
* splitting.
*
* @param parent the intended parent
* @param maxSize the maximum number of elements that can be stored
* @param list a list of items which are to be added into the empty
* list
*/
ListNode(TreeNode<E> parent, int maxSize,
List<ManagedReference<ManagedObject>> list) {
this(parent);
SubList<E> sublist = new SubList<E>(maxSize, list);
count = sublist.size();
subListRef = AppContext.getDataManager().createReference(sublist);
}
/**
* Returns the data integrity value of the {@code ListNode}
*
* @return the current data integrity value
*/
int getDataIntegrityValue() {
return dataIntegrityVal;
}
/**
* {@inheritDoc}
*/
public void setNext(Node<E> ref) {
AppContext.getDataManager().markForUpdate(this);
ListNode<E> node = uncheckedCast(ref);
nextRef = createReferenceIfNecessary(node);
}
/**
* {@inheritDoc}
*/
public void setParent(TreeNode<E> parent) {
AppContext.getDataManager().markForUpdate(this);
parentRef = createReferenceIfNecessary(parent);
}
/**
* {@inheritDoc}
*/
public ListNode<E> next() {
if (nextRef == null) {
return null;
}
return nextRef.get();
}
/**
* {@inheritDoc}
*/
public void setPrev(Node<E> ref) {
AppContext.getDataManager().markForUpdate(this);
ListNode<E> n = uncheckedCast(ref);
prevRef = createReferenceIfNecessary(n);
}
/**
* {@inheritDoc}
*/
public ListNode<E> prev() {
if (prevRef == null) {
return null;
}
return prevRef.get();
}
/**
* {@inheritDoc}
*/
public int size() {
return count;
}
/**
* Returns the {@code SubList} object which contains a subset of the
* elements in the collection.
*
* @return the {@code SubList} containing list elements, or null if
* one has not yet been instantiated
*/
SubList<E> getSubList() {
if (subListRef == null) {
return null;
}
return subListRef.get();
}
/**
* Appends the supplied object to the list and performs a split if
* necessary.
*
* @param e the element to append
*/
void append(E e) {
getSubList().append(e);
AppContext.getDataManager().markForUpdate(this);
count++;
dataIntegrityVal++;
// check if we need to split; i.e. if we have exceeded
// the specified list size.
if (count > getSubList().maxChildren) {
split();
} else {
this.getParent().increment();
}
}
/**
* Inserts the supplied value at the given index. The index is
* relative to the current list and not the global collection.
*
* @param index the index to insert the value, relative to the current
* {@code SubList}
* @param e the value to insert
*/
void insert(int index, E e) {
getSubList().insert(index, e);
AppContext.getDataManager().markForUpdate(this);
count++;
dataIntegrityVal++;
// check if we need to split; i.e. if we have exceeded
// the specified list size.
if (count > getSubList().maxChildren) {
split();
} else {
this.getParent().increment();
}
}
/**
* Removes the object at the specified index of the sublist. The index
* argument is not an absolute index; it is a relative index which
* points to a valid index in the list.
* <p>
* For example, if there are five ListNodes with a cluster size of
* five, the item with an absolute index of 16 corresponds to an
* element in the fourth ListNode<E>, with a relative offset of 1.
*
* @param list a reference to the {@code ScalableList}; this argument
* should only ever be null during the {@code AsynchronousClearTask}
* operation
* @param index the index corresponding to an element in the list (not
* an absolute index with respect to the {@code ScalableList} object
* @return the element that was removed
*/
E remove(ScalableList<E> list, int index) {
E old = getSubList().remove(index);
if (old != null) {
doRemoveWork(list);
}
return old;
}
/**
* {@inheritDoc}
*/
public void clear() {
TreeNode<E> parent = getParent();
DataManager dm = AppContext.getDataManager();
dm.removeObject(getSubList());
dm.removeObject(this);
parent.clear();
}
/**
* A {@code String} representation of the {@code ListNode}.
*
* @return a {@code String} representing the contents of the
* {@code ListNode}
*/
public String toString() {
return subListRef.get().toString();
}
/**
* Performs the work of calling the recursive update methods
*
* @param list a reference to the {@code ScalableList}
*/
private void doRemoveWork(ScalableList<E> list) {
AppContext.getDataManager().markForUpdate(this);
count--;
dataIntegrityVal++;
TreeNode<E> parent = getParent();
if (count == 0) {
checkRemoveListNode(list);
parent.decrementChildrenAndSize();
} else {
parent.decrement();
}
}
/**
* Removes the {@code Object} from the {@code SubList<E>}, if it
* exists.
*
* @param list a reference to the {@code ScalableList}; since this
* method is not called by the {@code AsynchronousClearTask}, this
* parameter must not be null.
* @param obj the {@code Object} to remove
* @return whether the object was removed or not; {@code true} if so,
* {@code false} otherwise
*/
boolean remove(ScalableList<E> list, Object obj) {
assert (list != null);
boolean result = getSubList().remove(obj);
// If a removal took place, then update
// count information accordingly
if (result) {
doRemoveWork(list);
}
return result;
}
/**
* Sets the element at the supplied index with the provided value.
*
* @param index the index to set the value
* @param obj the value to replace the existing one
* @return the old value
*/
E set(int index, Object obj) {
return subListRef.getForUpdate().set(index, obj);
}
/**
* A method that determines how to remove an empty {@code ListNode<E>}
* from a list of other {@code ListNode<E>}s. Update previous to
* point to next (doubly) and remove this object from Data Store
*/
private void checkRemoveListNode(ScalableList<E> list) {
// If the size is not zero, then we
// will not be removing any nodes,
// so no relinking; just return.
if (size() != 0) {
return;
}
// If this is an only child and its size is 0,
// we will keep it so that we can make future
// appends rather than creating a new ListNode.
if (next() == null && prev() == null) {
return;
}
// Link the parent to the next child before
// we attempt to remove the current node
linkParentToNextIfNecessary();
// Now we need to remove the list
// node and relink accordingly.
// First, we determine the type
// of list node: there are three possibilities:
// 1) interior node; connect prev to next
// 2) head node. Update child pointer from parent
// 3) tail node
if (next() != null && prev() != null) {
prev().setNext(next());
next().setPrev(prev());
} else if (next() != null) {
next().setPrev(null);
// The clear task will cause the list parameter
// to be null because we don't need to be
// updating the head; if we did, then we would
// asynchronously be updating the empty list's
// head.
if (list != null) {
list.setHead(next());
}
} else {
prev().setNext(null);
// The clear task will cause the list parameter
// to be null because we don't need to be
// updating the head; if we did, then we would
// asynchronously be updating the empty list's
// head.
if (list != null) {
list.setTail(prev());
}
}
// This is an empty node, so remove it from Data Store.
AppContext.getDataManager().removeObject(getSubList());
AppContext.getDataManager().removeObject(this);
}
/**
* Determines if the parent should be connected to the next sibling
* after a removal has taken place. This will occur as long as there
* is a next sibling, the next sibling has the same parent as
* {@code this}, the parent has sufficient children, and if the
* parent was not yet removed. Otherwise, if the parent has no
* children, then there is no child to update the child reference to,
* so we set it to null and prune the node during the propagation.
*/
private void linkParentToNextIfNecessary() {
TreeNode<E> parent = getParent();
// We decrement the child count because this method is
// called after a removal has taken place
int children = parent.getChildCount() - 1;
if (next() != null && children > 0 &&
parent.getChild().equals(this) &&
parent.equals(next().getParent())) {
parent
.setChild(next(), parent.size(), parent
.getChildCount());
} else if (children == 0) {
parent.setChildToNull();
}
}
/**
* Retrieves the parent of the {@code ListNode}
*
* @return the parent
*/
public TreeNode<E> getParent() {
TreeNode<E> parent;
if (parentRef == null) {
return null;
}
// By definition, there should always be a parent
// to a list node. Throw an ObjectNotFoundException
// if this call goes bad.
parent = parentRef.get();
return parent;
}
/**
* Splits the children into two roughly equal groups, and generates a
* sibling to parent one of the groups.
*/
private void split() {
List<ManagedReference<ManagedObject>> contents =
getSubList().getElements();
List<ManagedReference<ManagedObject>> spawned =
new ArrayList<ManagedReference<ManagedObject>>();
// move last half of list into a new child
int sublistSize = getSubList().size();
int lower = sublistSize / 2;
for (int index = lower; index < sublistSize; index++) {
ManagedReference<ManagedObject> temp = contents.get(index);
spawned.add(temp);
}
// remove the relocated nodes from the current list
// and mark that the list has changed
contents.removeAll(spawned);
this.count = contents.size();
// Create a new ListNode<E> for the moved contents
ListNode<E> spawnedNode =
new ListNode<E>(getParent(),
this.getSubList().maxChildren, spawned);
// Perform necessarily linking
spawnedNode.setNext(this.next());
spawnedNode.setPrev(this);
if (next() != null) {
this.next().setPrev(spawnedNode);
}
this.setNext(spawnedNode);
// Walks up the tree to increment the new number of children
this.getParent().incrementChildrenAndSize();
}
/**
* {@inheritDoc}
*/
public SearchResult<E> search(int currentValue, int destIndex) {
ListNode<E> n = this;
while ((currentValue + n.size()) < destIndex) {
currentValue += n.size();
n = n.next();
if (n == null) {
throw new IndexOutOfBoundsException(
"The index is out of range.");
}
}
return new SearchResult<E>(n, destIndex - currentValue - 1);
}
}
/**
* This object represents a partition in the list, otherwise denoted as a
* {@code bucket} (as per {@code bucketSize}). Only one of these
* {@code SubList} objects lives inside a ListNode<E> object; therefore,
* there are as many {@code SubList} objects as there are {@code ListNode}s.
* <p>
* The separation of the elements from the {@code ListNode}s is to allow
* iterations and other read operations while modifications to elements
* occur. This is particularly important for replacements because parent
* sizes do not need to change since no elements are being added or
* removed.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static class SubList<E> implements ManagedObject, Serializable {
private static final long serialVersionUID = 10L;
/**
* A reference to the list of elements
*/
private ArrayList<ManagedReference<ManagedObject>> contents;
/**
* The maximum number of children which can be contained, previously
* addressed as the {@code bucketSize}
*/
private final int maxChildren;
/**
* Performs a quick check to see if the argument is a legal parameter;
* that is, larger than 0.
*/
public static boolean isLegal(int maxSize) {
return (maxSize > 0);
}
/**
* Constructor which creates a {@code SubList}.
*
* @param maxSize the maximum number of elements which can be stored
* @param collection the elements to add to the empty list
*/
SubList(int maxSize, List<ManagedReference<ManagedObject>> collection) {
this(maxSize);
ManagedReference<ManagedObject> tmp = null;
for (int i = 0; i < collection.size(); i++) {
tmp = collection.get(i);
contents.add(tmp);
}
}
/**
* Constructor which creates a {@code SubList}
*
* @param maxSize the maximum number of elements which can be stored
*/
SubList(int maxSize) {
assert (isLegal(maxSize));
maxChildren = maxSize;
contents = new ArrayList<ManagedReference<ManagedObject>>();
}
/**
* Constructor to create a {@code SubList}
*
* @param maxSize the maximum number of elements which can be stored
* @param e an element to add to the empty list, at the first index
*/
SubList(int maxSize, E e) {
this(maxSize);
append(e);
}
/**
* Returns a {@code String} representation of this object.
*
* @return a {@code String} representation of this object
*/
public String toString() {
StringBuilder s = new StringBuilder("{");
for (int i = 0; i < contents.size(); i++) {
if (i > 0) {
s.append(",");
}
s.append(contents.get(i).toString());
}
s.append("}");
return s.toString();
}
/**
* Returns the maximum number of children for this structure.
*
* @return the maximum number of children
*/
int getMaxChildren() {
return maxChildren;
}
/**
* Returns the size of the collection.
*
* @return the size
*/
int size() {
return contents.size();
}
/**
* Returns the elements contained in the {@code SubList} as an
* {@code ArrayList}.
*
* @return the elements contained in the {@code SubList}
*/
List<ManagedReference<ManagedObject>> getElements() {
return contents;
}
/**
* Since the list is a collection of ManagedReferences, we are
* interested in retrieving the value it points to.
*
* @param index the index to retrieve
* @return the element, if it exists, or null otherwise
* @throws IndexOutOfBoundsException if the index is out of bounds
* (less than 0 or larger than the {@code SubList} size)
*/
@SuppressWarnings("unchecked")
E get(int index) {
return (E) getValueFromReference(contents.get(index), false);
}
/**
* Sets the value at the index provided. The index is not an absolute
* index; rather, it is relative to the current list. If the index
* does not correspond to a valid index in the underlying list, an
* {@code IndexOutOfBoundsException} will be thrown.
*
* @param index the index to add the element
* @param obj the element to be added
* @return the old element that was replaced
* @throws IndexOutOfBoundsException if the index is outside the range
* of the underlying list
*/
@SuppressWarnings("unchecked")
E set(int index, Object obj) {
assert (obj != null);
AppContext.getDataManager().markForUpdate(this);
// Wrap the element as an Element if is not already
// a ManagedObject
ManagedReference<ManagedObject> ref = createRefForAdd((E) obj);
// Get the stored element value, depending on
// what kind of value it was (ManagedObject or Element)
return (E) getValueFromReference(contents.set(index, ref), true);
}
/**
* Appends the supplied argument to the list.
*
* @param e the element to add to append
* @return whether the operation was successful; {@code true} if so,
* {@code false} otherwise
*/
boolean append(E e) {
assert (e != null);
// If it is not yet a ManagedObject, then
// create a new Element to make it a ManagedObject
ManagedReference<ManagedObject> ref = createRefForAdd(e);
AppContext.getDataManager().markForUpdate(this);
return contents.add(ref);
}
/**
* Returns the index of the element inside the {@code SubList<E>}. If
* the element does not exist, then -1 is returned.
*
* @param o the element whose last index is to be found
* @return the index of the element, or -1 if it does not exist
*/
int lastIndexOf(Object o) {
assert (o != null);
Iterator<ManagedReference<ManagedObject>> iter =
contents.iterator();
int index = 0;
int lastIndex = -1;
// Iterate through all contents,
// checking for equality
while (iter.hasNext()) {
ManagedReference<ManagedObject> ref = iter.next();
Object obj = getValueFromReference(ref, false);
if (o.equals(obj)) {
lastIndex = index;
}
index++;
}
return lastIndex;
}
/**
* Inserts the element into the list at a specified location. If the
* index is not valid, an {@code IndexOutOfBoundsException} is thrown.
*
* @param index the index to add the new element.
* @param e the object which is to be inserted at the specified
* {@code index}
* @throws IndexOutOfBoundsException if the supplied index is outside
* of the range of the underlying list
*/
void insert(int index, E e) {
AppContext.getDataManager().markForUpdate(this);
assert (e != null);
if (index < 0) {
throw new IndexOutOfBoundsException(
"Supplied index cannot be less than 0");
}
ManagedReference<ManagedObject> ref = createRefForAdd(e);
contents.add(index, ref);
}
/**
* Sets up the ref {@code ManagedReference} so that it contains a
* serialized {@code ManagedObject}.
*
* @param e the element to potentially wrap in an {@code Element}
* @return a reference to the wrapped element, ready to be stored into
* a {@code SubList}
* @throws IllegalArgumentException if the element does not implement
* the {@code Serializable} interface
*/
private ManagedReference<ManagedObject> createRefForAdd(E e) {
assert (e != null);
ManagedReference<ManagedObject> ref = null;
if (!(e instanceof Serializable)) {
throw new IllegalArgumentException(
"The element does not implement "
+ "the Serializable interface");
}
// Determine if we need to wrap the parameter or not.
if (e instanceof ManagedObject) {
ref =
AppContext.getDataManager().createReference(
(ManagedObject) e);
} else {
Element<E> element = new Element<E>(e);
ref =
AppContext.getDataManager().createReference(
(ManagedObject) element);
}
return ref;
}
/**
* Determines the index of the first occurrence of the supplied
* argument. If the element does not exist, then -1 is returned.
*
* @param o the element whose index is to be searched
* @return the first index of the supplied element, or -1 if it does
* not exist in the list
*/
int indexOf(Object o) {
assert (o != null);
Iterator<ManagedReference<ManagedObject>> iter =
contents.iterator();
int index = 0;
// Iterate through all the list
// contents until we find a match
while (iter.hasNext()) {
ManagedReference<ManagedObject> ref = iter.next();
Object obj = getValueFromReference(ref, false);
if (o.equals(obj)) {
return index;
}
index++;
}
return -1;
}
/**
* Removes the element at the supplied index. This method throws an
* {@code IndexOutOfBoundsException} if the index does not exist in
* the underlying list.
*
* @param index the index to remove
* @return the object removed from the index
* @throws IndexOutOfBoundsException if the index is outside of the
* range of the underlying list
*/
@SuppressWarnings("unchecked")
E remove(int index) {
if (index > contents.size() - 1) {
throw new IndexOutOfBoundsException(
"The index is out of bounds");
}
AppContext.getDataManager().markForUpdate(this);
return (E) getValueFromReference(contents.remove(index), true);
}
/**
* Removes the supplied object from the underlying list, if it exists.
*
* @param obj the element to remove from the list
* @return whether the operation was successful; {@code true} if so,
* {@code false} otherwise
*/
boolean remove(Object obj) {
Iterator<ManagedReference<ManagedObject>> iter =
contents.iterator();
// go through all the elements in this collection (a sublist)
while (iter.hasNext()) {
ManagedReference<ManagedObject> current = iter.next();
Object object = getValueFromReference(current, false);
if (obj.equals(object)) {
// remove the ManagedReference from the sub list.
// If the object that the ManagedReference was
// pointing to is an Element object, remove it
// from the data manager.
AppContext.getDataManager().markForUpdate(this);
iter.remove();
Object stored = current.get();
if (stored instanceof Element) {
AppContext.getDataManager().removeObject(stored);
}
return true;
}
}
return false;
}
}
/**
* An interface which unifies the concept of a {@code ListNode} and
* {@code TreeNode} as elements within a {@code ScalableList}. This
* interface avoids cast warnings when the identity of a given element is
* not immediately known.
*
* @param <E> the type of element stored in the {@code ScalableList}
*/
static interface Node<E> {
/**
* Retrieves the node's parent.
*
* @return the parent, or null if none exists.
*/
TreeNode<E> getParent();
/**
* Sets the {@code Node}'s parent to the supplied argument.
*/
void setParent(TreeNode<E> t);
/**
* The size of the node; that is, the sum of the sizes of its
* immediate children.
*
* @return the size of this node.
*/
int size();
/**
* Walks up the tree and removes the object and any of its parents.
* This method is intended to be called during the
* {@code AsynchronousClearTask} operation.
*/
void clear();
/**
* Traverses the tree (recursively) in search of the ListNode<E>
* which contains the index provided. If no ListNode<E> can be found,
* then null is returned.
*
* @param currentValue the current index value at the beginning of
* this current search
* @param destIndex the absolute index of the desired element
* @return the {@code ListNode} containing the absolute
* {@code destIndex}
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
SearchResult<E> search(int currentValue, int destIndex);
/**
* Returns the next {@code Node} in sequence, or null if none exists.
*
* @return the next node
*/
Node<E> next();
/**
* Sets the next element to be the supplied argument. The argument
* should be the same type as the variable.
*
* @param next the next {@code Node}
*/
void setNext(Node<E> next);
/**
* Returns the previous {@code Node} in sequence, or null if none
* exists.
*
* @return the previous node
*/
Node<E> prev();
/**
* Sets the previous element to be the supplied argument. The argument
* should be the same type as the variable.
*
* @param prev the previous {@code Node}
*/
void setPrev(Node<E> prev);
}
/**
* This class is responsible for returning the results of a search,
* including the target {@code ListNode} and the offset. Since this object
* is not Serializable, its intention is to be a temporary object whose
* scope is simply within the tree search process. Therefore, it is
* unnecessary to include mutator methods to adjust the entries; only
* accessor methods are needed to retreive the stored values.
*/
private static class SearchResult<E> {
/**
* The target node where the element lives
*/
final ListNode<E> node;
/**
* The offset of the element within the {@code ListNode}'s
* {@code SubList}
*/
final int offset;
/**
* Constructor which creates a new SearchResult based on the target
* node and the calculated offset. The offset is not an absolute
* index, but rather the index of the element with respect to the
* {@code SubList} of the {@code ListNode}.
*
* @param node the target node
* @param offset the index of the element in this {@code ListNode}'s
* {@code SubList}.
*/
SearchResult(ListNode<E> node, int offset) {
this.node = node;
this.offset = offset;
}
}
}