
org.javimmutable.collections.list.BranchNode Maven / Gradle / Ivy
///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
//
// Copyright (c) 2018, 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.SplitableIterable;
import org.javimmutable.collections.SplitableIterator;
import org.javimmutable.collections.cursors.LazyMultiCursor;
import org.javimmutable.collections.iterators.LazyMultiIterator;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
/**
* 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 forNodeBuilder(int depth,
int size,
Node prefix,
Indexed> sourceNodes,
int offset,
int limit,
Node suffix)
{
assert limit > offset;
assert ListHelper.allNodesFull(depth, sourceNodes, offset, limit);
final Node[] nodes = ListHelper.allocateNodes(sourceNodes, offset, limit);
return new BranchNode<>(depth, size, prefix, nodes, suffix);
}
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 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();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy