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

net.sf.saxon.ma.trie.ImmutableHashTrieMap Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.ma.trie;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

// Original author: Michael Froh (published on Github). Released under MPL 2.0
// by Saxonica Limited with permission from the author

public abstract class ImmutableHashTrieMap
        implements ImmutableMap, Iterable>  {

    private static final ImmutableHashTrieMap EMPTY_NODE = new EmptyHashNode();

    public static  ImmutableHashTrieMap empty() {
        return EMPTY_NODE;
    }

    private static  int getBucket(int shift, K key) {
        return key.hashCode() >> shift & MASK;
    }


    public ImmutableHashTrieMap put(K key, V value) {
        return put(0, key, value);
    }


    public ImmutableHashTrieMap remove(K key) {
        return remove(0, key);
    }

    public Option get(K key) {
        return get(0, key);
    }

    /*
    * In the methods below, "shift" denotes how far (in bits) through the
    * hash code we are currently looking. At each level, we add "bits",
    * defined below, to shift.
    */

    private static final int BITS = 5;
    private static final int FANOUT = 1 << BITS;
    private static final int MASK = FANOUT - 1;

    abstract ImmutableHashTrieMap put(int shift, K key,
                                            V value);

    abstract ImmutableHashTrieMap remove(int shift, K key);

    abstract Option get(int shift, K key);

    abstract boolean isArrayNode();

    /**
     * Implementation for an empty map
     * @param  the key type
     * @param  the value type
     */

    private static class EmptyHashNode
            extends ImmutableHashTrieMap {
        @Override
        ImmutableHashTrieMap put(final int shift, final K key,
                                       final V value) {
            return new EntryHashNode(key, value);
        }

        @Override
        ImmutableHashTrieMap remove(final int shift,
                                          final K key) {
            return this;
        }

        @Override
        boolean isArrayNode() {
            return false;
        }

        @Override
        Option get(final int shift, final K key) {
            return Option.none();
        }

        public Iterator> iterator() {
            return Collections.>emptySet().iterator();
        }
    }

    /**
     * Implementation for a single-entry map
     * @param  the key type
     * @param  the value type
     */

    private static class EntryHashNode
            extends ImmutableHashTrieMap {
        private final K key;
        private final V value;

        private EntryHashNode(final K key,
                              final V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        ImmutableHashTrieMap put(final int shift, final K key,
                                       final V value) {
            if (this.key.equals(key)) {
                // Overwriting this entry
                return new EntryHashNode(key, value);
            } else if (this.key.hashCode() == key.hashCode()) {
                // This is a collision. Return a new ListHashNode.
                return new ListHashNode(new Tuple2(this.key, this.value),
                        new Tuple2(key, value));
            }
            // Split this node into an ArrayHashNode with this and the new value
            // as entries.
            return newArrayHashNode(shift, this.key.hashCode(), this,
                    key.hashCode(), new EntryHashNode(key, value));
        }

        @Override
        ImmutableHashTrieMap remove(final int shift,
                                          final K key) {
            if (this.key.equals(key)) {
                return empty();
            }
            return this;
        }

        @Override
        boolean isArrayNode() {
            return false;
        }

        @Override
        Option get(final int shift, final K key) {
            if (this.key.equals(key)) {
                return Option.option(value);
            }
            return Option.none();
        }

        public Iterator> iterator() {
            return Collections.singleton(new Tuple2(key, value)).iterator();
        }
    }

    /**
     * Implementation for a set of entries where the keys of all entries share the same hash code
     * @param  the key type
     * @param  the value type
     */

    private static class ListHashNode extends ImmutableHashTrieMap {
        private final ImmutableList> entries;

        public ListHashNode(Tuple2 entry1,
                            Tuple2 entry2) {
            // These entries must collide
            assert entry1._1.hashCode() == entry2._1.hashCode();
            entries = ImmutableList.>nil().prepend(entry1)
                    .prepend(entry2);
        }

        private ListHashNode(final ImmutableList> entries) {
            // Size should be at least 2
            assert !entries.isEmpty();
            assert !entries.tail().isEmpty();
            this.entries = entries;
        }

        @Override
        ImmutableHashTrieMap put(final int shift, final K key,
                                       final V value) {
            if (entries.head()._1.hashCode() != key.hashCode()) {
                return newArrayHashNode(shift,
                        entries.head()._1.hashCode(),
                        this,
                        key.hashCode(),
                        new EntryHashNode(
                                key, value));
            }
            ImmutableList> newList = ImmutableList.nil();
            boolean found = false;
            for (Tuple2 entry : entries) {
                if (entry._1.equals(key)) {
                    // Node replacement
                    newList =
                            newList.prepend(new Tuple2(key, value));
                    found = true;
                } else {
                    newList = newList.prepend(entry);
                }
            }
            if (!found) {
                // Adding a new entry
                newList = newList.prepend(new Tuple2(key, value));
            }
            return new ListHashNode(newList);
        }

        @Override
        ImmutableHashTrieMap remove(final int shift,
                                          final K key) {
            ImmutableList> newList = ImmutableList.nil();
            int size = 0;
            for (Tuple2 entry : entries) {
                if (!entry._1.equals(key)) {
                    newList = newList.prepend(entry);
                    size++;
                }
            }
            if (size == 1) {
                Tuple2 entry = newList.head();
                return new EntryHashNode(entry._1, entry._2);
            }
            return new ListHashNode(newList);
        }

        @Override
        boolean isArrayNode() {
            return false;
        }

        @Override
        Option get(final int shift, final K key) {
            for (Tuple2 entry : entries) {
                if (entry._1.equals(key)) {
                    return Option.option(entry._2);
                }
            }
            return Option.none();
        }

        public Iterator> iterator() {
            return new Iterator>() {
                private ImmutableList> curList =
                        ListHashNode.this.entries;

                public boolean hasNext() {
                    return !curList.isEmpty();
                }

                public Tuple2 next() {
                    Tuple2 retVal = curList.head();
                    curList = curList.tail();
                    return retVal;
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }

    /**
     * Create a new node combining two existing nodes
     * @param shift the shift to be applied to the hash code at this level in the tree
     * @param hash1 the hash code of the first node
     * @param subNode1 the first node
     * @param hash2 the hash code of the second node
     * @param subNode2 the second node
     * @param  the key type
     * @param  the value type
     * @return the new node
     */

    private static  ImmutableHashTrieMap
        newArrayHashNode(int shift,
                         int hash1,
                         ImmutableHashTrieMap subNode1,
                         int hash2,
                         ImmutableHashTrieMap subNode2) {
        int curShift = shift;
        int h1 = hash1 >> shift & MASK;
        int h2 = hash2 >> shift & MASK;
        List buckets = new LinkedList();
        while (h1 == h2) {
            buckets.add(0, h1);
            curShift += BITS;
            h1 = hash1 >> curShift & MASK;
            h2 = hash2 >> curShift & MASK;
        }
        ImmutableHashTrieMap newNode =
                new BranchedArrayHashNode(h1, subNode1, h2, subNode2);
        for (Integer bucket : buckets) {
            newNode = new SingletonArrayHashNode(bucket, newNode);
        }
        return newNode;

    }

    private static abstract class ArrayHashNode
            extends ImmutableHashTrieMap {
        @Override
        boolean isArrayNode() {
            return true;
        }
    }

    private static class BranchedArrayHashNode
            extends ArrayHashNode {
        private final ImmutableHashTrieMap[] subnodes;
        private final int size;

        public BranchedArrayHashNode(int h1,
                             ImmutableHashTrieMap subNode1,
                             int h2,
                             ImmutableHashTrieMap subNode2) {
            assert h1 != h2;
            size = 2;
            subnodes = new ImmutableHashTrieMap[FANOUT];
            for (int i = 0; i < FANOUT; i++) {
                if (i == h1) {
                    subnodes[i] = subNode1;
                } else if (i == h2) {
                    subnodes[i] = subNode2;
                } else {
                    subnodes[i] = EMPTY_NODE;
                }
            }
        }

        public BranchedArrayHashNode(int size,
                             final ImmutableHashTrieMap[] subnodes) {
            assert subnodes.length == FANOUT;
            this.size = size;
            this.subnodes = subnodes;
        }

        @Override
        ImmutableHashTrieMap put(final int shift, final K key,
                                       final V value) {
            final int bucket = getBucket(shift, key);
            ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
            System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);

            final int newSize =
                    newNodes[bucket] == EMPTY_NODE ? size + 1 : size;
            newNodes[bucket] = newNodes[bucket].put(shift + BITS,
                    key, value);
            return new BranchedArrayHashNode(newSize, newNodes);
        }

        @Override
        ImmutableHashTrieMap remove(final int shift,
                                          final K key) {
            final int bucket = getBucket(shift, key);
            if (subnodes[bucket] == EMPTY_NODE) {
                return this;
            }
            ImmutableHashTrieMap[] newNodes = new ImmutableHashTrieMap[FANOUT];
            System.arraycopy(subnodes, 0, newNodes, 0, FANOUT);
            newNodes[bucket] = newNodes[bucket].remove(shift + BITS,
                    key);
            final int newSize =
                    newNodes[bucket] == EMPTY_NODE ? size - 1 : size;
            if (newSize == 1) {
                int orphanedBucket = -1;
                for (int i = 0; i < FANOUT; i++) {
                    if (newNodes[i] != EMPTY_NODE) {
                        orphanedBucket = i;
                        break;
                    }
                }
                ImmutableHashTrieMap orphanedEntry =
                        subnodes[orphanedBucket];
                if (orphanedEntry.isArrayNode()) {
                    return new SingletonArrayHashNode(orphanedBucket,
                            orphanedEntry);
                }
                return orphanedEntry;
            }
            return new BranchedArrayHashNode(newSize, newNodes);
        }

        @Override
        Option get(final int shift, final K key) {
            final int bucket = getBucket(shift, key);
            return subnodes[bucket].get(shift + BITS, key);
        }

        public Iterator> iterator() {
            return new Iterator>() {
                private int bucket = 0;
                private Iterator> childIterator =
                        subnodes[0].iterator();

                public boolean hasNext() {
                    if (childIterator.hasNext()) {
                        return true;
                    }
                    bucket++;
                    while (bucket < FANOUT) {
                        childIterator = subnodes[bucket].iterator();
                        if (childIterator.hasNext()) {
                            return true;
                        }
                        bucket++;
                    }
                    return false;
                }

                public Tuple2 next() {
                    return childIterator.next();
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }

    private static class SingletonArrayHashNode extends
            ArrayHashNode {
        private final int bucket;
        private final ImmutableHashTrieMap subnode;

        private SingletonArrayHashNode(final int bucket,
                                       final ImmutableHashTrieMap subnode) {
            assert subnode instanceof ArrayHashNode;
            this.bucket = bucket;
            this.subnode = subnode;
        }

        @Override
        ImmutableHashTrieMap put(final int shift, final K key,
                                       final V value) {
            final int bucket = getBucket(shift, key);
            if (bucket == this.bucket) {
                return new SingletonArrayHashNode(bucket,
                        subnode.put(shift + BITS, key, value));
            }
            return new BranchedArrayHashNode(this.bucket, subnode,
                    bucket, new EntryHashNode(key, value));
        }

        @Override
        ImmutableHashTrieMap remove(final int shift, final K key) {
            final int bucket = getBucket(shift, key);
            if (bucket == this.bucket) {
                ImmutableHashTrieMap newNode =
                        subnode.remove(shift + BITS, key);
                if (!(newNode.isArrayNode())) {
                    return newNode;
                }
                return new SingletonArrayHashNode(bucket, newNode);
            }
            return this;
        }

        @Override
        Option get(final int shift, final K key) {
            final int bucket = getBucket(shift, key);
            if (bucket == this.bucket) {
                return subnode.get(shift + BITS, key);
            }
            return Option.none();
        }

        public Iterator> iterator() {
            return subnode.iterator();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy