/* SnapTree - (c) 2009 Stanford University - PPL */
// SnapReferenceArray
package trees.lockbased.stanfordutils;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReferenceArray;
/** Implements a concurrent fixed-size collection with fast clone. Reads and
* writes have volatile semantics. No provision is provided to grow or shrink
* the collection. Modelled after {@link AtomicReferenceArray}.
*/
public class SnapReferenceArray<E> implements Iterable<E>, Cloneable {
private static final int LOG_BF = 5;
private static final int BF = 1 << LOG_BF;
private static final int BF_MASK = BF - 1;
// Internally this is implemented as an external tree with branching factor
// BF. The leaves of the tree are the elements E. Nodes are considered to
// be unshared if they have the same generation as the root node.
private static class Generation {
}
private static class Node extends AtomicReferenceArray<Object> {
// never modified after initialization of the SnapReferenceArray, but
// convenient to be non-final
Generation gen;
Node(final Generation gen, final int length, final Object initialValue) {
super(length);
this.gen = gen;
if (initialValue != null) {
for (int i = 0; i < length; ++i) {
lazySet(i, initialValue);
}
}
}
Node(final Generation gen, final Node src) {
super(src.length());
this.gen = gen;
for (int i = 0; i < src.length(); ++i) {
lazySet(i, src.get(i));
}
}
Node(final Node src) {
this(new Generation(), src);
}
}
private static class COWMgr extends CopyOnWriteManager<Node> {
private COWMgr(final Node initialValue) {
super(initialValue, 0);
}
protected Node freezeAndClone(final Node value) {
return new Node(value);
}
protected Node cloneFrozen(final Node frozenValue) {
return new Node(frozenValue);
}
}
/** 0 if _length == 0, otherwise the smallest positive int such that
* (1L << (LOG_BF * _height)) >= _length.
*/
private final int _height;
private final int _length;
private CopyOnWriteManager<Node> _rootRef;
public SnapReferenceArray(final int length) {
this(length, null);
}
public SnapReferenceArray(final int length, final E element) {
int height = 0;
Node partial = null;
if (length > 0) {
// We will insert the gen into all of the partials (since they
// are used exactly once). We reuse the fulls, so we will give
// them a null gen that will cause them to be copied before any
// actual writes.
final Generation gen = new Generation();
Object full = element;
do {
++height;
// This is the number of nodes required at this level. They
// are either all full, or all but one full and one partial.
int levelSize = ((length - 1) >> (LOG_BF * (height - 1))) + 1;
// Partial is only present if this level doesn't evenly divide into
// pieces of length BF, or if a lower level didn't divide evenly.
Node newP = null;
if (partial != null || (levelSize & BF_MASK) != 0) {
final int partialBF = ((levelSize - 1) & BF_MASK) + 1;
newP = new Node(gen, partialBF, full);
if (partial != null) {
newP.set(partialBF - 1, partial);
}
assert(partial != null || partialBF < BF);
}
Node newF = null;
if (levelSize > BF || newP == null) {
newF = new Node(null, BF, full);
}
if (levelSize <= BF) {
// we're done
if (newP == null) {
// top level is a full, which isn't duplicated
newF.gen = gen;
partial = newF;
}
else {
// Top level is a partial. If it uses exactly one
// full child, then we can mark that as unshared.
if (newP.length() == 2 && newP.get(0) != newP.get(1)) {
((Node) newP.get(0)).gen = gen;
}
partial = newP;
}
full = null;
}
else {
partial = newP;
full = newF;
assert(full != null);
}
} while (full != null);
}
_height = height;
_length = length;
_rootRef = new COWMgr(partial);
}
@SuppressWarnings("unchecked")
public SnapReferenceArray<E> clone() {
final SnapReferenceArray<E> copy;
try {
copy = (SnapReferenceArray<E>) super.clone();
}
catch (final CloneNotSupportedException xx) {
throw new Error("unexpected", xx);
}
// height and length are done properly by the magic Cloneable.clone()
copy._rootRef = new COWMgr(new Node(_rootRef.frozen()));
return copy;
}
public int length() {
return _length;
}
@SuppressWarnings("unchecked")
public E get(final int index) {
checkBounds(index);
return (E) readableLeaf(_rootRef.read(), index).get(index & BF_MASK);
}
private void checkBounds(final int index) {
if (index < 0 || index >= _length) {
throw new IndexOutOfBoundsException();
}
}
private Node readableLeaf(final Node root, final int index) {
Node cur = root;
for (int h = _height - 1; h >= 1; --h) {
cur = (Node) cur.get((index >> (LOG_BF * h)) & BF_MASK);
}
return cur;
}
public void set(final int index, final E newValue) {
checkBounds(index);
final Epoch.Ticket ticket = _rootRef.beginMutation();
try {
mutableLeaf(_rootRef.mutable(), index).set(index & BF_MASK, newValue);
}
finally {
ticket.leave(0);
}
}
@SuppressWarnings("unchecked")
public E getAndSet(final int index, final E newValue) {
checkBounds(index);
final Epoch.Ticket ticket = _rootRef.beginMutation();
try {
return (E) mutableLeaf(_rootRef.mutable(), index).getAndSet(index & BF_MASK, newValue);
}
finally {
ticket.leave(0);
}
}
public boolean compareAndSet(final int index, final E expected, final E newValue) {
checkBounds(index);
final Epoch.Ticket ticket = _rootRef.beginMutation();
try {
return mutableLeaf(_rootRef.mutable(), index).compareAndSet(index & BF_MASK, expected, newValue);
}
finally {
ticket.leave(0);
}
}
private Node mutableLeaf(final Node root, final int index) {
final Generation gen = root.gen;
Node cur = root;
for (int h = _height - 1; h >= 1; --h) {
final int i = (index >> (LOG_BF * h)) & BF_MASK;
final Node child = (Node) cur.get(i);
if (child.gen == gen) {
// easy case
cur = child;
continue;
}
final Node repl = new Node(gen, child);
// reread before CAS
Node newChild = (Node) cur.get(i);
if (newChild == child) {
cur.compareAndSet(i, child, repl);
newChild = (Node) cur.get(i);
}
assert(newChild.gen == gen);
cur = newChild;
}
return cur;
}
public Iterator<E> iterator() {
final Node root = _rootRef.frozen();
return new Iterator<E>() {
private int _index;
private Node _leaf;
public boolean hasNext() {
return _index < _length;
}
@SuppressWarnings("unchecked")
public E next() {
if ((_index & BF_MASK) == 0) {
_leaf = readableLeaf(root, _index);
}
return (E) _leaf.get(_index++ & BF_MASK);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/** Returns a view of this instance as a fixed-size {@link List}. Reads
* and writes to the list will be propagated to this instance.
*/
public List<E> asList() {
return new AbstractList<E>() {
public E get(final int index) {
return SnapReferenceArray.this.get(index);
}
@Override
public E set(final int index, final E element) {
return SnapReferenceArray.this.getAndSet(index, element);
}
public int size() {
return SnapReferenceArray.this.length();
}
@Override
public Iterator<E> iterator() {
return SnapReferenceArray.this.iterator();
}
};
}
@Override
public String toString() {
return asList().toString();
}
}