com.github.grignaak.collections.CowArrayList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cow-collections Show documentation
Show all versions of cow-collections Show documentation
Copy-on-write collections for easy, thread-safe, immutability
The newest version!
package com.github.grignaak.collections;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.RandomAccess;
import javax.annotation.CheckForNull;
/**
* An array-based copy-on-write list, where pushing and popping from the end are amortized constant time. Access and
* updates anywhere in the list are sub-linear (nearly constant). Insertion and removal from anywhere not near the end
* of the list are linear in time.
*
* Implementation notes
*
* The current implementation (subject to change) is a 32-way trie, with a length-32 tail. In practice this means that
* structural sharing doesn't start until after the list has 32 entries; and then happens at 32-entry chunks. We found
* this to utilize cache lines and also be a good balance in structural sharing.
*/
public final class CowArrayList extends AbstractList implements CowList, RandomAccess {
/*
* For clarification, we use index to refer to an element's location in
* the entire data structure, and position to refer to an element's location
* in a specific array. Convert from an index to a position via {@link #valuePosition(int)}
* and {@link #childPosition(int, int)}
*/
private long generation;
public CowArrayList() {
this(EMPTY_NODE.generation + 1, 0, 5, EMPTY_NODE, new Object[32]);
}
private CowArrayList(long generation, int size, int shift, Node root, Object[] tail) {
this.generation = generation;
this.size = size;
this.shift = shift;
this.root = root;
this.tail = tail;
}
//region pulled down
protected static final int LOW_5_BITS_MASK = 0x1f;
protected static final int HIGH_27_BITS_MASK = ~LOW_5_BITS_MASK;
/**
* A 32-way trie node. The leaves at {@code level == 0} hold the data; all other layers house only nodes.
*
* All nodes are densely packed; all leaves have arrays of size 32.
*/
protected static final class Node {
private final long generation;
protected Object[] nodes;
Node(long generation, Object[] nodes) {
this.generation = generation;
this.nodes = nodes;
}
/**
* Copy the node if it isn't owned by the editor; should only be used inside Node
*
* @return this or a copy of this
*/
private Node _editable(long generation) {
return this.generation == generation ? this : new Node(generation, nodes.clone());
}
/**
* Return a potential copy of the array. Use when stealing the array
* for use in the same builder.
*
* That is, this is equivalent to {@code this._editable(editor).nodes}
* but a node is not created.
*/
Object[] editableArray(long generation) {
if (this.generation == generation) {
return nodes;
} else {
return nodes.clone();
}
}
/**
* Copy the array that houses the index within the tree. Use when
* stealing the array for use outside this builder.
*
* @return this or a copy of this
*/
Object[] cloneArrayForIndex(int index, int level) {
return leafNode(index, level).nodes.clone();
}
/**
* Fetch the leaf node holding the given index in the tree. The level
* tells where in the tree this node is.
*/
Node leafNode(int index, int level) {
Node node = this;
for (int shift = level; shift > 0; shift -= 5) {
node = (Node) node.nodes[childPosition(index, shift)];
}
return node;
}
/**
* Put the child at the position.
*
* @return this or a copy of this
*/
Node putNode(long generation, int nodePosition, Node child) {
Node me = _editable(generation);
me.nodes[nodePosition] = child;
return me;
}
/**
* Append the child.
*
* @return this or a copy of this
*/
Node appendNode(long generation, Node child) {
Object[] appended = MoreArrays.arrayCopyAndAppend(nodes, child);
if (this.generation == generation) {
nodes = appended;
return this;
} else {
return new Node(generation, appended);
}
}
/**
* Remove the last child.
*
* @return this or a copy of this
*/
Node shrink(long generation) {
Object[] shrunk = nodes.length == 1 ? EMPTY_ARRAY : MoreArrays.copyToLength(this.nodes, nodes.length - 1);
if (this.generation == generation) {
this.nodes = shrunk;
return this;
} else {
return new Node(generation, shrunk);
}
}
/**
* Put the child at the index into the tree; level giving the depth into the tree.
*
* @return this or a copy of this
*/
Node swapOut(long generation, int index, int level, E value, Box returned) {
Node me = _editable(generation);
if (level == 0) {
returned.box( MoreArrays.swapOut(me.nodes, valuePosition(index), value) );
} else {
int pos = childPosition(index, level);
Node oldChild = (Node) me.nodes[pos];
Node newChild = oldChild.swapOut(generation, index, level-5, value, returned);
me.nodes[pos] = newChild;
}
return me;
}
/**
* Get the value at the index within the tree; the level tells where this
* node is within the tree.
*/
@SuppressWarnings("unchecked")
E get(int index, int level) {
return (E) leafNode(index, level).nodes[valuePosition(index)];
}
/**
* Put the leaf at the end of the tree, as given by lastIndex (which should
* include the values in the leaf)
*
* @return this or a copy of this
*/
Node pushLeaf(long generation, int lastIndex, int level, Node leaf) {
int pos = childPosition(lastIndex, level);
if (level == 5) {
// the penultimate layer!
return appendNode(generation, leaf);
} else {
if (nodes.length == pos) {
return appendNode(generation,
newPath(generation, level - 5, leaf));
} else {
Node child = (Node) nodes[pos];
return putNode(generation, pos,
child.pushLeaf(generation, lastIndex, level - 5, leaf));
}
}
}
/**
* Remove the last leaf from the tree; putting its values into the box.
*
* The values put into the box are editable by the given Owner.
*
* @param lastIndex the last index in the tree
* @return this, a copy of this, or null if this is now empty
*/
@CheckForNull
Node popLeaf(long generation, int lastIndex, int level, Box