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

org.javersion.util.AbstractHashTrie Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Samppa Saarela
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.javersion.util;

import static java.lang.System.arraycopy;
import java.util.Objects;

import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.function.Consumer;

import org.javersion.util.AbstractHashTrie.EntryNode;

import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;

public abstract class AbstractHashTrie, This extends AbstractHashTrie> {

    public abstract int size();

    public boolean isEmpty() {
        return size() == 0;
    }

    @SuppressWarnings("unchecked")
    protected This self() {
        return (This) this;
    }

    protected abstract This doReturn(Node newRoot, int newSize);

    protected abstract Node root();

    public boolean containsKey(Object key) {
        return root().find(key) != null;
    }

    protected final Iterator doIterator() {
        return root().iterator();
    }

    private This commitAndReturn(UpdateContext updateContext, Node newRoot, int newSize) {
        commit(updateContext);
        return doReturn(newRoot, newSize);
    }

    protected final This doAdd(UpdateContext updateContext, E newEntry) {
        Node newRoot = root().assoc(updateContext, newEntry);
        return commitAndReturn(updateContext, newRoot, size() + updateContext.getChangeAndReset());
    }

    @SuppressWarnings("rawtypes")
    protected final This doAddAll(UpdateContext updateContext, Iterator entries) {
        Node newRoot = root();
        int size = size();
        while (entries.hasNext()) {
            @SuppressWarnings("unchecked")
            E entry = (E) entries.next();
            newRoot = newRoot.assoc(updateContext, entry);
            size += updateContext.getChangeAndReset();
        }

        return commitAndReturn(updateContext, newRoot, size);
    }

    protected final This doRemove(UpdateContext updateContext, Object key) {
        Node newRoot = root().dissoc(updateContext, key);
        return commitAndReturn(updateContext, newRoot, size() + updateContext.getChangeAndReset());
    }

    @SuppressWarnings("rawtypes")
    protected final This doRemoveAll(UpdateContext updateContext, Iterator keys) {
        Node newRoot = root();
        int size = size();
        while (keys.hasNext()) {
            newRoot = newRoot.dissoc(updateContext, keys.next());
            size += updateContext.getChangeAndReset();
        }
        return commitAndReturn(updateContext, newRoot, size);
    }

    protected void commit(UpdateContext updateContext) {
        updateContext.commit();
    }

    static abstract class Node> implements Iterable {

        static final int SHIFT_INCREMENT = 5;

        E find(Object key) {
            return findInternal(0, hash(key), key);
        }

        Node assoc(UpdateContext  currentContext, E newEntry) {
            return assocInternal(currentContext, 0, newEntry.getHash(), newEntry);
        }

        Node dissoc(UpdateContext  currentContext, Object key) {
            return dissocInternal(currentContext, 0, hash(key), key);
        }

        static int hash(Object key) {
            return key == null ? 0 : key.hashCode();
        }

        static int index(int bitmap, int bit){
            return Integer.bitCount(bitmap & (bit - 1));
        }

        static int bit(int hash, int shift) {
            return 1 << bitIndex(hash, shift);
        }

        static int bitIndex(int hash, int shift) {
            // xx xxxxx xxxxx xxxxx xxxxx NNNNN xxxxx   >>> 5
            // 00 00000 00000 00000 00000 00000 NNNNN   & 0x01f
            // return number (NNNNN) between 0..31
            return (hash >>> shift) & 0x01f;
        }


        abstract E findInternal(int shift, int hash, Object key);

        abstract Node assocInternal(UpdateContext  currentContext, int shift, int hash, E newEntry);

        abstract Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key);

        protected abstract Node[] getChildren();

    }

    @SuppressWarnings("rawtypes")
    private static final Iterator EMTPY_ITER = Collections.emptyIterator();

    @SuppressWarnings("rawtypes")
    static final Node EMPTY_NODE = new Node() {

        @Override
        public Iterator iterator() {
            return EMTPY_ITER;
        }

        @Override
        EntryNode findInternal(int shift, int hash, Object key) {
            return null;
        }

        @Override
        Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key) {
            return this;
        }

        @SuppressWarnings("unchecked")
        @Override
        Node assocInternal(UpdateContext  currentContext, int shift, int hash, EntryNode newEntryNode) {
            if (!currentContext.insert(newEntryNode)) {
                return this;
            }
            if (currentContext.expectedUpdates() == 1) {
                return newEntryNode;
            } else {
                Node node = new HashNode(currentContext);
                return node.assocInternal(currentContext, shift, hash, newEntryNode);
            }
        }

        @Override
        protected Node[] getChildren() {
            return null;
        }
    };

    protected static abstract class EntryNode> extends Node {

        final K key;

        public EntryNode(K key) {
            this.key = key;
        }

        public int getHash() {
            return hash(key);
        }

        @SuppressWarnings("unchecked")
        protected E self() {
            return (E) this;
        }

        @SuppressWarnings("unchecked")
        protected Node split(final UpdateContext  currentContext, final int shift, final int hash, final E newEntry) {
            int thisHash = getHash();
            if (hash == thisHash) {
                if (!currentContext.insert(newEntry)) {
                    return this;
                }
                return new CollisionNode((E) this, newEntry);
            }
            else {
                @SuppressWarnings("rawtypes")
                Node[] newChildren = new Node[HashNode.newSizeForInsert(currentContext, 1)];
                newChildren[0] = this;
                Node result = new HashNode(currentContext, bit(thisHash, shift), newChildren)
                        .assocInternal(currentContext, shift, hash, newEntry);
                if (currentContext.hasChanged()) {
                    return result;
                }
                return this;
            }
        }

        @Override
        Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key) {
            if (Objects.equals(key, this.key) && currentContext.delete(self())) {
                return null;
            }
            return this;
        }

        @Override
        E findInternal(int shift, int hash, Object key) {
            if (Objects.equals(this.key, key)) {
                return self();
            }
            return null;
        }

        @Override
        public Iterator iterator() {
            return Iterators.singletonIterator(self());
        }

        @Override
        protected Node[] getChildren() {
            return null;
        }
    }


    static final class HashNode> extends Node {

        final UpdateContext  updateContext;

        private int bitmap;

        private Node[] children;

        HashNode(UpdateContext  contextReference) {
            this(contextReference, contextReference.expectedUpdates());
        }

        @SuppressWarnings("unchecked")
        HashNode(UpdateContext  contextReference, int expectedSize) {
            this(contextReference, 0, new Node[expectedSize < 32 ? expectedSize : 32]);
        }

        HashNode(UpdateContext  contextReference, int bitmap, Node[] children) {
            this.updateContext = contextReference;
            this.bitmap = bitmap;
            this.children = children;
        }

        @Override
        Node assocInternal(final UpdateContext  currentContext, final int shift, int hash, final E newEntry) {
            int bit = bit(hash, shift);
            int index = index(bitmap, bit);
            if ((bitmap & bit) != 0) {
                Node oldNode = children[index];
                Node newNode = oldNode.assocInternal(currentContext, shift + SHIFT_INCREMENT, hash, newEntry);
                if (newNode == oldNode) {
                    return this;
                } else {
                    HashNode editable = cloneForReplace(currentContext);
                    editable.children[index] = newNode;

                    return editable;
                }
            } else if (currentContext.insert(newEntry)) {
                return insert(currentContext, index, newEntry, bit);
            } else {
                return this;
            }
        }

        @Override
        Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key) {
            int bit = bit(hash, shift);
            if ((bitmap & bit) == 0) {
                return this;
            }
            int index = index(bitmap, bit);
            Node oldNode = children[index];
            Node newNode = oldNode.dissocInternal(currentContext, shift + SHIFT_INCREMENT, hash, key);

            if (newNode == oldNode) {
                return this;
            } else if (newNode == null) {
                if (bitmap == bit) {
                    return null;
                } else {
                    return cloneForDelete(currentContext, index, bit);
                }
            } else {
                HashNode editable = cloneForReplace(currentContext);
                editable.children[index] = newNode;

                return editable;
            }
        }

        @Override
        public E findInternal(int shift, int hash, Object key) {
            int bit = bit(hash, shift);
            if ((bitmap & bit) == 0) {
                return null;
            }
            int index = index(bitmap, bit);
            Node nodeOrEntry = children[index];
            return nodeOrEntry.findInternal(shift + SHIFT_INCREMENT, hash, key);
        }


        @SuppressWarnings("unchecked")
        private Node insert(UpdateContext  currentContext, int index, E newEntry, int bit) {
            int childCount = childCount();
            boolean editInPlace = updateContext.isSameAs(currentContext);

            Node[] newChildren;
            if (editInPlace && childCount < children.length) {
                newChildren = this.children;
            } else {
                newChildren = new Node[newSizeForInsert(currentContext, childCount)];
                if (index > 0) {
                    arraycopy(children, 0, newChildren, 0, index);
                }
            }

            // make room for insertion
            if (index < childCount) {
                arraycopy(children, index, newChildren, index + 1, childCount - index);
            }

            newChildren[index] = newEntry;

            if (childCount == 31) {
                // Convert to ArrayNode as it can be done here practically with no extra cost
                return new ArrayNode<>(currentContext, newChildren, childCount + 1);
            }
            else if (editInPlace) {
                this.bitmap |= bit;
                this.children = newChildren;
                return this;
            }
            else {
                return new HashNode<>(currentContext, bitmap | bit, newChildren);
            }
        }

        private int childCount() {
            return Integer.bitCount(bitmap);
        }

        @SuppressWarnings("unchecked")
        private Node cloneForDelete(UpdateContext  currentContext, int index, int bit) {
            int childCount = childCount();
            boolean editInPlace = updateContext.isSameAs(currentContext);

            Node[] newChildren;
            if (editInPlace) {
                newChildren = this.children;
            } else {
                newChildren = new Node[childCount - 1];
                if (index > 0) {
                    arraycopy(children, 0, newChildren, 0, index);
                }
            }

            // Delete given node
            if (index + 1 < childCount) {
                arraycopy(children, index + 1, newChildren, index, childCount - index - 1);
                if (newChildren.length >= childCount) {
                    newChildren[childCount - 1] = null;
                }
            }

            if (editInPlace) {
                this.bitmap = bitmap ^ bit;
                return this;
            }
            else {
                return new HashNode<>(currentContext, bitmap ^ bit, newChildren);
            }
        }

        static int newSizeForInsert(UpdateContext  currentContext, int currentChildCount) {
            if (currentContext.expectedUpdates() == 1) {
                return currentChildCount + 1;
            } else {
                return currentChildCount < 16 ? 2*(currentChildCount + 1) : 32;
            }
        }

        private HashNode cloneForReplace(UpdateContext  currentContext) {
            if (this.updateContext.isSameAs(currentContext)) {
                return this;
            } else {
                return new HashNode<>(currentContext, bitmap, children.clone());
            }
        }

        @Override
        public Iterator iterator() {
            return new ArrayIterator<>(children);
        }

        @Override
        public Node[] getChildren() {
            return children;
        }

    }

    static final class ArrayNode> extends Node {

        final UpdateContext  updateContext;

        private Node[] children;

        private int childCount;

        ArrayNode(UpdateContext  contextReference, Node[] children, int childCount) {
            this.updateContext = contextReference;
            this.children = children;
            this.childCount = childCount;
        }

        @Override
        Node assocInternal(final UpdateContext  currentContext, final int shift, int hash, final E newEntry) {
            int index = bitIndex(hash, shift);
            Node node = children[index];
            int newChildCount = childCount;
            Node newChild;
            if (node != null) {
                newChild = node.assocInternal(currentContext, shift + SHIFT_INCREMENT, hash, newEntry);
                if (newChild == node) {
                    return this;
                }
            } else if(currentContext.insert(newEntry))  {
                newChildCount++;
                newChild = newEntry;
            } else {
                return this;
            }
            if (isEditInPlace(currentContext)) {
                this.children[index] = newChild;
                this.childCount = newChildCount;
                return this;
            } else {
                Node[] newChildren = this.children.clone();
                newChildren[index] = newChild;
                return new ArrayNode<>(currentContext, newChildren, newChildCount);
            }
        }

        @Override
        Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key) {
            int index = bitIndex(hash, shift);
            Node node = children[index];
            if (node == null) {
                return this;
            }
            int newChildCount = childCount;
            Node newChild = node.dissocInternal(currentContext, shift + SHIFT_INCREMENT, hash, key);
            if (newChild == node) {
                return this;
            } else if (newChild == null) {
                newChildCount--;
                if (newChildCount < 16) {
                    return toBitmapNode(currentContext, newChildCount, index);
                }
            }
            if (isEditInPlace(currentContext)) {
                this.children[index] = newChild;
                this.childCount = newChildCount;
                return this;
            } else {
                Node[] newChildren = this.children.clone();
                newChildren[index] = newChild;
                return new ArrayNode<>(currentContext, newChildren, newChildCount);
            }
        }

        private Node toBitmapNode(UpdateContext  currentContext, int newChildCount, int removedIndex) {
            @SuppressWarnings("unchecked")
            Node[] newChildren = new Node[newChildCount];
            int bitmap = 0;
            for (int i=0, j=0; i < children.length; i++) {
                if (children[i] != null && i != removedIndex) {
                    newChildren[j] = children[i];
                    bitmap |= 1 << i;
                    j++;
                }
            }
            return new HashNode<>(currentContext, bitmap, newChildren);
        }

        @Override
        E findInternal(int shift, int hash, Object key) {
            int index = bitIndex(hash, shift);
            Node node = children[index];
            if (node != null) {
                return node.findInternal(shift + SHIFT_INCREMENT, hash, key);
            } else {
                return null;
            }
        }

        private boolean isEditInPlace(UpdateContext  currentContext) {
            return this.updateContext.isSameAs(currentContext);
        }

        @Override
        public Iterator iterator() {
            return new ArrayIterator<>(children);
        }

        @Override
        public Node[] getChildren() {
            return children;
        }

    }

    static final class CollisionNode> extends Node {

        final int hash;

        private E[] entries;

        @SuppressWarnings("unchecked")
        public CollisionNode(E first, E second) {
            this.hash = first.getHash();
            this.entries = (E[]) new EntryNode[] { first, second };
        }
        @SuppressWarnings("unchecked")
        private CollisionNode(EntryNode[] entries) {
            this.hash = entries[0].getHash();
            this.entries = (E[]) entries;
        }

        @Override
        public E findInternal(int shift, int hash, Object key) {
            for (E entry : entries) {
                if (Objects.equals(entry.key, key)) {
                    return entry;
                }
            }
            return null;
        }

        @Override
        @SuppressWarnings("unchecked")
        public Node assocInternal(final UpdateContext  currentContext, final int shift, int hash, final E newEntry) {
            if (hash == this.hash) {
                for (int i=0; i < entries.length; i++) {
                    if (Objects.equals(entries[i].key, newEntry.key)) {
                        if (currentContext.merge(entries[i], newEntry)) {
                            E[] newEntries = entries.clone();
                            newEntries[i] = newEntry;
                            return new CollisionNode<>(newEntries);
                        } else {
                            return this;
                        }
                    }
                }

                if (!currentContext.insert(newEntry)) {
                    return this;
                }

                E[] newEntries = (E[]) new EntryNode[entries.length + 1];
                arraycopy(entries, 0, newEntries, 0, entries.length);
                newEntries[entries.length] = newEntry;
                return new CollisionNode<>(newEntries);
            }


            Node[] newChildren = (currentContext.expectedUpdates() == 1
                    ? new Node[] { this, null } : new Node[] { this, null, null, null });

            Node newNode = new HashNode<>(currentContext, bit(this.hash, shift), newChildren);
            return newNode.assocInternal(currentContext, shift, hash, newEntry);
        }

        @Override
        Node dissocInternal(UpdateContext  currentContext, int shift, int hash, Object key) {
            if (hash == this.hash) {
                for (int i=0; i < entries.length; i++) {
                    if (Objects.equals(entries[i].key, key)) {
                        if (!currentContext.delete(entries[i])) {
                            return this;
                        }
                        if (entries.length == 2) {
                            if (i == 1) {
                                return entries[0];
                            } else {
                                return entries[1];
                            }
                        }
                        @SuppressWarnings("unchecked")
                        E[] newEntries = (E[]) new EntryNode[entries.length - 1];
                        arraycopy(entries, 0, newEntries, 0, i);
                        if (i + 1 < entries.length) {
                            arraycopy(entries, i + 1, newEntries, i, entries.length - i - 1);
                        }
                        return new CollisionNode<>(newEntries);
                    }
                }
            }
            return this;
        }

        @Override
        public Iterator iterator() {
            return new ArrayIterator<>(entries);
        }

        @Override
        protected Node[] getChildren() {
            return entries;
        }

    }

    static class ArrayIterator> extends UnmodifiableIterator {

        @SuppressWarnings("unchecked")
        private final Node[][] nodeStack = new Node[7][];
        private final int[] nodeIndices = new int[7];
        private int stackIndex = 0;

        public ArrayIterator(Node[] array) {
            nodeStack[0] = array;
        }

        @Override
        public boolean hasNext() {
            while (stackIndex >= 0) {
                while (nodeIndices[stackIndex] < nodeStack[stackIndex].length) {
                    if (nodeStack[stackIndex][nodeIndices[stackIndex]] != null) {
                        return true;
                    }
                    nodeIndices[stackIndex]++;
                }
                stackIndex--;
            }
            return false;
        }

        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            Node node = nodeStack[stackIndex][nodeIndices[stackIndex]];
            nodeIndices[stackIndex]++;
            if (node instanceof EntryNode) {
                return (E) node;
            } else {
                stackIndex++;
                nodeStack[stackIndex] = node.getChildren();
                nodeIndices[stackIndex] = 0;
                return next();
            }
        }
    }

    static abstract class NodeSpliterator> implements Spliterator {

        private Node[] array;

        private int pos;

        private int limit;

        private int sizeEstimate;

        private final int characteristics;

        private NodeSpliterator subSpliterator;

        @SuppressWarnings("unchecked")
        protected NodeSpliterator(Node node, int sizeEstimate, int additionalCharacteristics) {
            if (node instanceof EntryNode) {
                this.array = (Node[]) new Node[] { node };
                pos = 0;
                limit = 1;
                this.sizeEstimate = 1;
            }
            else {
                array = node.getChildren();
                if (array == null) {
                    pos = limit = this.sizeEstimate = 0;
                } else {
                    pos = 0;
                    limit = array.length;
                    this.sizeEstimate = sizeEstimate;
                }
            }
            this.characteristics = ORDERED | SIZED | additionalCharacteristics;
        }

        protected NodeSpliterator(Node[] array, int pos, int limit, int sizeEstimate, int additionalCharacteristics) {
            this.array = array;
            this.pos = pos;
            this.limit = limit;
            this.sizeEstimate = sizeEstimate;
            this.characteristics = ORDERED | additionalCharacteristics;
        }

        @Override
        public final boolean tryAdvance(Consumer action) {
            if (subSpliterator != null) {
                if (subSpliterator.tryAdvance(action)) {
                    return true;
                }
                subSpliterator = null;
            }
            if (pos >= limit) {
                return false;
            }
            Node node = array[pos++];
            if (node instanceof EntryNode) {
                @SuppressWarnings("unchecked")
                T value = apply((E) node);
                action.accept(value);
                return true;
            } else {
                Node[] children = node.getChildren();
                subSpliterator = newSubSpliterator(children, 0, children.length, sizeEstimate / (limit - pos));
                return subSpliterator.tryAdvance(action);
            }
        }

        @Override
        public final void forEachRemaining(Consumer action) {
            if (subSpliterator != null) {
                subSpliterator.forEachRemaining(action);
                subSpliterator = null;
            }
            forEach(array, pos, limit, action);
        }

        private void forEach(Node[] nodes, Consumer action) {
            forEach(nodes, 0, nodes.length, action);
        }

        private void forEach(Node[] nodes, int pos, int limit, Consumer action) {
            for (; pos < limit; pos++) {
                if (nodes[pos] != null) {
                    forEach(nodes[pos], action);
                }
            }
        }

        private void forEach(Node node, Consumer action) {
            if (node instanceof EntryNode) {
                @SuppressWarnings("unchecked")
                T value = apply((E) node);
                action.accept(value);
            } else {
                Node[] children = node.getChildren();
                if (children != null) {
                    forEach(children, action);
                }
            }
        }

        private void trim() {
            while (pos < limit && array[pos] == null) {
                pos++;
            }
            while (limit > pos && array[limit-1] == null) {
                limit--;
            }
        }

        @Override
        public NodeSpliterator trySplit() {
            trim();
            NodeSpliterator prefix;
            if (subSpliterator != null) {
                return trySplitSubSpliterator();
            }
            else if (pos >= limit) {
                return null;
            }
            else if (pos + 1 == limit) {
                return trySplitLastNode();
            }

            int mid = (pos + limit) >>> 1;
            prefix = newSubSpliterator(array, pos, mid, sizeEstimate >>> 1);
            this.pos = mid;
            return prefix;
        }

        private NodeSpliterator trySplitLastNode() {
            Node[] children = array[pos].getChildren();
            if (children == null) {
                return null;
            }
            array = children;
            pos = 0;
            limit = array.length;
            return trySplit();
        }

        private NodeSpliterator trySplitSubSpliterator() {
            NodeSpliterator prefix;
            if (pos >= limit) {
                // Array is already consumed, recurse split into subIterator
                prefix = subSpliterator.trySplit();
                this.sizeEstimate = subSpliterator.sizeEstimate;
            } else {
                // Split subIterator off
                prefix = subSpliterator;
                this.sizeEstimate -= subSpliterator.sizeEstimate;
                subSpliterator = null;
            }
            return prefix;
        }

        @Override
        public final long estimateSize() {
            return sizeEstimate;
        }

        @Override
        public int characteristics() {
            return characteristics;
        }

        protected abstract NodeSpliterator newSubSpliterator(Node[] array, int pos, int limit, int sizeEstimate);

        protected abstract T apply(E entry);

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy