org.javimmutable.collections.list.BranchNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javimmutable-collections Show documentation
Show all versions of javimmutable-collections Show documentation
Library providing immutable/persistent collection classes for
Java. While collections are immutable they provide methods for
adding and removing values by creating new modified copies of
themselves. Each copy shares almost all of its structure with
other copies to minimize memory consumption.
///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
//
// Copyright (c) 2017, Burton Computer Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
//
// Neither the name of the Burton Computer Corporation nor the names
// of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.javimmutable.collections.list;
import org.javimmutable.collections.Cursor;
import org.javimmutable.collections.Cursorable;
import org.javimmutable.collections.Indexed;
import org.javimmutable.collections.MutableBuilder;
import org.javimmutable.collections.SplitableIterable;
import org.javimmutable.collections.SplitableIterator;
import org.javimmutable.collections.cursors.LazyMultiCursor;
import org.javimmutable.collections.indexed.IndexedList;
import org.javimmutable.collections.iterators.LazyMultiIterator;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Node implementation containing other nodes. Prefix and suffix nodes can contain nodes
* of any depth and size (including empty) while body nodes array contains only full nodes
* of exactly depth - 1. This allows fast access to the body nodes by a computed offset
* based on size but also quick adds and deletes from either end using the prefix and suffix
* nodes. Once a BranchNode reaches its theoretical limit based on depth any insert triggers
* the creation of a new higher depth node containing the branch so that the new parent's prefix
* or suffix can contain the new value.
*/
@Immutable
class BranchNode
implements Node
{
private final int depth;
private final int size;
private final Node prefix; // possibly empty and can be any depth
private final Node[] nodes; // all of these are full and have depth - 1
private final Node suffix; // possibly empty and can be any depth
private BranchNode(int depth,
int size,
Node prefix,
Node[] nodes,
Node suffix)
{
assert nodes.length <= 32;
assert size <= ListHelper.sizeForDepth(depth);
this.depth = depth;
this.size = size;
this.prefix = prefix;
this.nodes = nodes;
this.suffix = suffix;
}
BranchNode(T prefixValue,
Node node)
{
this(node.getDepth() + 1,
node.size() + 1,
new LeafNode<>(prefixValue),
ListHelper.allocateSingleNode(node),
EmptyNode.of());
assert node.isFull();
}
BranchNode(Node node,
T suffixValue)
{
this(node.getDepth() + 1,
node.size() + 1,
EmptyNode.of(),
ListHelper.allocateSingleNode(node),
new LeafNode<>(suffixValue));
assert node.isFull();
}
static Node of(Indexed extends T> leaves)
{
int nodeCount = leaves.size();
if (nodeCount == 0) {
return EmptyNode.of();
}
if (nodeCount <= 32) {
return LeafNode.fromList(leaves, 0, nodeCount);
}
final Node[] nodes = ListHelper.allocateNodes(1 + (leaves.size() / 32));
int offset = 0;
int index = 0;
while (offset < nodeCount) {
nodes[index++] = LeafNode.fromList(leaves, offset, Math.min(offset + 32, nodeCount));
offset += 32;
}
nodeCount = index;
// loop invariant - all nodes except last one are always full
// last one is possibly full
int depth = 2;
while (nodeCount > 1) {
int dstOffset = 0;
int srcOffset = 0;
// fill all full nodes
while (nodeCount > 32) {
final Node[] newNodes = ListHelper.allocateNodes(32);
System.arraycopy(nodes, srcOffset, newNodes, 0, 32);
nodes[dstOffset++] = new BranchNode<>(depth, ListHelper.sizeForDepth(depth), EmptyNode.of(), newNodes, EmptyNode.of());
srcOffset += 32;
nodeCount -= 32;
}
// collect remaining nodes
if (nodeCount == 1) {
nodes[dstOffset++] = nodes[srcOffset];
} else if (nodeCount > 1) {
final Node lastNode = nodes[srcOffset + nodeCount - 1];
if ((lastNode.getDepth() == (depth - 1)) && lastNode.isFull()) {
// all remaining nodes are full
final Node[] newNodes = ListHelper.allocateNodes(nodeCount);
System.arraycopy(nodes, srcOffset, newNodes, 0, nodeCount);
nodes[dstOffset++] = new BranchNode<>(depth, ListHelper.sizeForDepth(depth - 1) * nodeCount, EmptyNode.of(), newNodes, EmptyNode.of());
} else {
// all but last remaining nodes are full
final int newNodesLength = nodeCount - 1;
final Node[] newNodes = ListHelper.allocateNodes(newNodesLength);
System.arraycopy(nodes, srcOffset, newNodes, 0, newNodesLength);
nodes[dstOffset++] = new BranchNode<>(depth, (ListHelper.sizeForDepth(depth - 1) * newNodesLength) + lastNode.size(), EmptyNode.of(), newNodes, lastNode);
}
}
nodeCount = dstOffset;
depth += 1;
}
assert nodeCount == 1;
return nodes[0];
}
static Builder builder()
{
return new Builder<>();
}
static BranchNode forTesting(Node prefix,
Node[] nodes,
Node suffix)
{
return new BranchNode<>(2,
prefix.size() + (nodes.length * 32) + suffix.size(),
prefix,
nodes.clone(),
suffix);
}
@Override
public boolean isEmpty()
{
return size == 0;
}
@Override
public boolean isFull()
{
return size == ListHelper.sizeForDepth(depth);
}
@Override
public int size()
{
return size;
}
@Override
public int getDepth()
{
return depth;
}
private static Node forDelete(int size,
Node prefix,
Node[] nodes,
Node suffix)
{
if (nodes.length == 0) {
if (prefix.isEmpty()) {
return suffix;
} else if (suffix.isEmpty()) {
return prefix;
} else {
int depth = 1 + Math.max(prefix.getDepth(), suffix.getDepth());
return new BranchNode<>(depth, size, prefix, nodes, suffix);
}
} else if ((nodes.length == 1) && prefix.isEmpty() && suffix.isEmpty()) {
return nodes[0];
} else {
int depth = 1 + nodes[0].getDepth();
return new BranchNode<>(depth, size, prefix, nodes, suffix);
}
}
@Override
public Node deleteFirst()
{
if (!prefix.isEmpty()) {
return forDelete(size - 1, prefix.deleteFirst(), nodes, suffix);
}
if (nodes.length > 0) {
Node newPrefix = nodes[0];
Node[] newNodes = ListHelper.allocateNodes(nodes.length - 1);
System.arraycopy(nodes, 1, newNodes, 0, newNodes.length);
return forDelete(size - 1, newPrefix.deleteFirst(), newNodes, suffix);
}
if (!suffix.isEmpty()) {
return suffix.deleteFirst();
}
throw new IllegalStateException();
}
@Override
public Node deleteLast()
{
if (!suffix.isEmpty()) {
return forDelete(size - 1, prefix, nodes, suffix.deleteLast());
}
if (nodes.length > 0) {
Node newSuffix = nodes[nodes.length - 1];
Node[] newNodes = ListHelper.allocateNodes(nodes.length - 1);
System.arraycopy(nodes, 0, newNodes, 0, newNodes.length);
return forDelete(size - 1, prefix, newNodes, newSuffix.deleteLast());
}
if (!prefix.isEmpty()) {
return prefix.deleteLast();
}
throw new IllegalStateException();
}
@Override
public Node insertFirst(T value)
{
if (isFull()) {
return new BranchNode<>(value, this);
}
if (prefix.getDepth() < (depth - 1)) {
return new BranchNode<>(depth, size + 1, prefix.insertFirst(value), nodes, suffix);
}
assert prefix.getDepth() == (depth - 1);
assert !prefix.isFull();
Node[] newNodes;
Node newPrefix = prefix.insertFirst(value);
if (newPrefix.isFull()) {
newNodes = ListHelper.allocateNodes(nodes.length + 1);
System.arraycopy(nodes, 0, newNodes, 1, nodes.length);
newNodes[0] = newPrefix;
newPrefix = EmptyNode.of();
} else {
newNodes = nodes;
}
return new BranchNode<>(depth, size + 1, newPrefix, newNodes, suffix);
}
@Override
public Node insertLast(T value)
{
if (isFull()) {
return new BranchNode<>(this, value);
}
if (suffix.getDepth() < (depth - 1)) {
return new BranchNode<>(depth, size + 1, prefix, nodes, suffix.insertLast(value));
}
assert suffix.getDepth() == (depth - 1);
assert !suffix.isFull();
Node[] newNodes;
Node newSuffix = suffix.insertLast(value);
if (newSuffix.isFull()) {
newNodes = ListHelper.allocateNodes(nodes.length + 1);
System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
newNodes[nodes.length] = newSuffix;
newSuffix = EmptyNode.of();
} else {
newNodes = nodes;
}
return new BranchNode<>(depth, size + 1, prefix, newNodes, newSuffix);
}
@Override
public boolean containsIndex(int index)
{
return (index >= 0) && (index < size);
}
@Override
public T get(int index)
{
if (prefix.containsIndex(index)) {
return prefix.get(index);
}
index -= prefix.size();
final int fullNodeSize = ListHelper.sizeForDepth(depth - 1);
int arrayIndex = index / fullNodeSize;
if (arrayIndex < nodes.length) {
return nodes[arrayIndex].get(index - (arrayIndex * fullNodeSize));
}
index -= nodes.length * fullNodeSize;
if (suffix.containsIndex(index)) {
return suffix.get(index);
}
throw new IndexOutOfBoundsException();
}
@Override
public Node assign(int index,
T value)
{
if (prefix.containsIndex(index)) {
return new BranchNode<>(depth, size, prefix.assign(index, value), nodes, suffix);
}
index -= prefix.size();
final int fullNodeSize = ListHelper.sizeForDepth(depth - 1);
int arrayIndex = index / fullNodeSize;
if (arrayIndex < nodes.length) {
Node[] newNodes = nodes.clone();
newNodes[arrayIndex] = nodes[arrayIndex].assign(index - (arrayIndex * fullNodeSize), value);
return new BranchNode<>(depth, size, prefix, newNodes, suffix);
}
index -= nodes.length * fullNodeSize;
if (suffix.containsIndex(index)) {
return new BranchNode<>(depth, size, prefix, nodes, suffix.assign(index, value));
}
throw new IndexOutOfBoundsException();
}
@Nonnull
@Override
public Cursor cursor()
{
return LazyMultiCursor.cursor(indexedForCursor());
}
@Nonnull
@Override
public SplitableIterator iterator()
{
return LazyMultiIterator.iterator(indexedForIterator());
}
private Indexed> indexedForCursor()
{
final int last = nodes.length + 1;
return new Indexed>()
{
@Override
public Cursorable get(int index)
{
return getNode(index, last);
}
@Override
public int size()
{
return last + 1;
}
};
}
private Indexed> indexedForIterator()
{
final int last = nodes.length + 1;
return new Indexed>()
{
@Override
public SplitableIterable get(int index)
{
return getNode(index, last);
}
@Override
public int size()
{
return last + 1;
}
};
}
private Node getNode(int index,
int last)
{
if (index == 0) {
return prefix;
} else if (index == last) {
return suffix;
} else {
return nodes[index - 1];
}
}
@Override
public void checkInvariants()
{
if (nodes.length > 32) {
throw new IllegalStateException();
}
if ((nodes.length == 32) && !(prefix.isEmpty() && suffix.isEmpty())) {
throw new IllegalStateException();
}
for (Node node : nodes) {
if ((node.getDepth() != (depth - 1)) || !node.isFull()) {
throw new IllegalStateException();
}
}
int computedSize = prefix.size() + suffix.size();
for (Node node : nodes) {
computedSize += node.size();
}
if (computedSize != size) {
throw new IllegalStateException();
}
if (prefix.isFull() && (prefix.getDepth() == (depth - 1))) {
throw new IllegalStateException();
}
if (suffix.isFull() && (suffix.getDepth() == (depth - 1))) {
throw new IllegalStateException();
}
prefix.checkInvariants();
for (Node node : nodes) {
node.checkInvariants();
}
suffix.checkInvariants();
}
static class Builder
implements MutableBuilder>
{
private final List leaves = new ArrayList<>();
@Nonnull
@Override
public Builder add(T value)
{
leaves.add(value);
return this;
}
@Nonnull
@Override
public Node build()
{
return of(IndexedList.retained(leaves));
}
@Nonnull
@Override
public Builder add(Cursor extends T> source)
{
for (Cursor extends T> cursor = source.start(); cursor.hasValue(); cursor = cursor.next()) {
leaves.add(cursor.getValue());
}
return this;
}
@Nonnull
@Override
public Builder add(Iterator extends T> source)
{
while (source.hasNext()) {
leaves.add(source.next());
}
return this;
}
@Nonnull
@Override
public Builder add(Iterable extends T> source)
{
return add(source.iterator());
}
@SafeVarargs
@Nonnull
@Override
public final Builder add(K... source)
{
leaves.addAll(Arrays.asList(source));
return this;
}
@Nonnull
@Override
public Builder add(Indexed extends T> source)
{
return add(source, 0, source.size());
}
@Nonnull
@Override
public Builder add(Indexed extends T> source,
int offset,
int limit)
{
for (int i = offset; i < limit; ++i) {
leaves.add(source.get(i));
}
return this;
}
}
}