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

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

///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
//
// Copyright (c) 2014, 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.Indexed;
import org.javimmutable.collections.MutableBuilder;
import org.javimmutable.collections.cursors.LazyCursor;
import org.javimmutable.collections.cursors.MultiCursor;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Collection;
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.
 *
 * @param 
 */
@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  Builder builder()
    {
        return new Builder();
    }

    static  BranchNode forTesting(int depth,
                                        Node prefix,
                                        Node[] nodes,
                                        Node suffix)
    {
        return new BranchNode(depth,
                                 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()
    {
        MultiCursor.Builder builder = MultiCursor.builder();
        builder = builder.add(LazyCursor.of(prefix));
        for (Node node : nodes) {
            builder = builder.add(LazyCursor.of(node));
        }
        builder = builder.add(LazyCursor.of(suffix));
        return builder.build();
    }

    @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()
        {
            int nodeCount = leaves.size();
            if (nodeCount == 0) {
                return EmptyNode.of();
            }

            if (nodeCount <= 32) {
                return LeafNode.fromList(leaves, 0, nodeCount);
            }

            List> nodes = new ArrayList>();
            int offset = 0;
            while (offset < nodeCount) {
                nodes.add(LeafNode.fromList(leaves, offset, Math.min(offset + 32, nodeCount)));
                offset += 32;
            }
            nodeCount = nodes.size();

            // 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) {
                    Node[] newNodes = ListHelper.allocateNodes(32);
                    for (int i = 0; i < 32; ++i) {
                        newNodes[i] = nodes.get(srcOffset++);
                    }
                    nodes.set(dstOffset++, new BranchNode(depth, ListHelper.sizeForDepth(depth), EmptyNode.of(), newNodes, EmptyNode.of()));
                    nodeCount -= 32;
                }
                // collect remaining nodes
                Node lastNode = nodes.get(srcOffset + (nodeCount - 1));
                if ((lastNode.getDepth() == (depth - 1)) && lastNode.isFull()) {
                    // all remaining nodes are full
                    Node[] newNodes = ListHelper.allocateNodes(nodeCount);
                    for (int i = 0; i < newNodes.length; ++i) {
                        newNodes[i] = nodes.get(srcOffset++);
                    }
                    nodes.set(dstOffset++, new BranchNode(depth, ListHelper.sizeForDepth(depth - 1) * newNodes.length, EmptyNode.of(), newNodes, EmptyNode.of()));
                } else {
                    // all but last remaining nodes are full
                    Node[] newNodes = ListHelper.allocateNodes(nodeCount - 1);
                    for (int i = 0; i < newNodes.length; ++i) {
                        newNodes[i] = nodes.get(srcOffset++);
                    }
                    nodes.set(dstOffset++, new BranchNode(depth, (ListHelper.sizeForDepth(depth - 1) * newNodes.length) + lastNode.size(), EmptyNode.of(), newNodes, lastNode));
                }
                nodeCount = dstOffset;
                depth += 1;
            }
            assert nodeCount == 1;
            return nodes.get(0);
        }

        @Nonnull
        @Override
        public Builder add(Cursor source)
        {
            for (Cursor cursor = source.start(); cursor.hasValue(); cursor = cursor.next()) {
                add(cursor.getValue());
            }
            return this;
        }

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

        @Nonnull
        @Override
        public Builder add(Collection source)
        {
            add(source.iterator());
            return this;
        }

        @Nonnull
        @Override
        public  Builder add(K... source)
        {
            for (T value : source) {
                add(value);
            }
            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) {
                add(source.get(i));
            }
            return this;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy