All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.javimmutable.collections.list.BranchNode Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.2.1
Show newest version
///###////////////////////////////////////////////////////////////////////////
//
// 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 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 source)
        {
            for (Cursor cursor = source.start(); cursor.hasValue(); cursor = cursor.next()) {
                leaves.add(cursor.getValue());
            }
            return this;
        }

        @Nonnull
        @Override
        public Builder add(Iterator source)
        {
            while (source.hasNext()) {
                leaves.add(source.next());
            }
            return this;
        }

        @Nonnull
        @Override
        public Builder add(Iterable 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 source)
        {
            return add(source, 0, source.size());
        }

        @Nonnull
        @Override
        public Builder add(Indexed source,
                              int offset,
                              int limit)
        {
            for (int i = offset; i < limit; ++i) {
                leaves.add(source.get(i));
            }
            return this;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy