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

net.sf.saxon.ma.map.HashTrieMap Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.map;

import net.sf.saxon.expr.sort.AtomicMatchKey;
import net.sf.saxon.ma.trie.ImmutableHashTrieMap;
import net.sf.saxon.ma.trie.ImmutableMap;
import net.sf.saxon.ma.trie.TrieKVP;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.transpile.CSharpSuppressWarnings;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.type.*;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;

import java.util.Iterator;

/**
 * An immutable map. This implementation, which uses a hash trie, was introduced in Saxon 9.6
 */
public class HashTrieMap extends MapItem {

    // The underlying trie holds key-value pairs, but these do not correspond directly
    // to the key value pairs in the XDM map. Instead, the key in the trie is an AtomicMatchKey
    // based on the XDM key, which allows equality matching to differ from the Java-level
    // equals method (to take account of collations, etc). The value in the trie is
    // actually a tuple holding both the real key, and the value.

    private ImmutableMap imap;

    // The following values are maintained incrementally when
    // entries are added to the map. They are not changed when entries are removed,
    // so the actual type may be narrower than these values suggest. The purpose of
    // keeping this information is to try and avoid dynamic checking of the map
    // contents against a required type wherever possible. We therefore have
    // a guarantee that all entries in the map conform to the types maintained here;
    // but there is no guarantee that they do not also conform to some more specific
    // type.

    // The set of UTypes of keys in the map
    private UType keyUType = UType.VOID;

    // The set of UTypes of items in the values in the map
    private UType valueUType = UType.VOID;

    // The atomic type of all keys, if they are homogeneous; otherwise null
    private AtomicType keyAtomicType = ErrorType.getInstance();

    // The item type of all items in values, if they are homogeneous; otherwise null
    private ItemType valueItemType = ErrorType.getInstance();

    // The "envelope" cardinality of values in the map (0:1, 0:0, 1:1, 0:many, 1:many)
    private int valueCardinality = 0;

    // The number of entries in the map; -1 if unknown
    private int entries = -1;

    /**
     * Create an empty map
     */

    public HashTrieMap() {
        this.imap = ImmutableHashTrieMap.empty();
        this.entries = 0;
    }


    /**
     * Create a singleton map with a single key and value
     * @param key the key value
     * @param value the associated value
     * @return a singleton map
     */

    public static HashTrieMap singleton(AtomicValue key, GroundedValue value) {
        return new HashTrieMap().addEntry(key, value);
    }

    /**
     * Create a map whose contents are a copy of an existing immutable map
     * @param imap the map to be copied
     */

    public HashTrieMap(ImmutableMap imap) {
        this.imap = imap;
        entries = -1;
    }

    /**
     * Create a map whose entries are copies of the entries in an existing MapItem
     *
     * @param map       the existing map to be copied
     * @return the new map
     */

    public static HashTrieMap copy(MapItem map) {
        if (map instanceof HashTrieMap) {
            return (HashTrieMap)map;
        }
        HashTrieMap m2 = new HashTrieMap();
        for (net.sf.saxon.ma.map.KeyValuePair pair : map.keyValuePairs()) {
            m2 = m2.addEntry(pair.key, pair.value);
        }
        return m2;
    }

    /**
     * After adding an entry to the map, update the cached type information
     * @param key the new key
     * @param val the new associated value
     * @param wasEmpty true if the map was empty before adding these values
     */

    private void updateTypeInformation(AtomicValue key, Sequence val, boolean wasEmpty) {
//        if (Instrumentation.ACTIVE) {
//            Instrumentation.count("updateTypeInformation");
//        }
        if (wasEmpty) {
            keyUType = key.getUType();
            valueUType = SequenceTool.getUType(val);
            keyAtomicType = key.getItemType();
            valueItemType = MapItem.getItemTypeOfSequence(val);
            valueCardinality = SequenceTool.getCardinality(val);
        } else {
            keyUType = keyUType.union(key.getUType());
            valueUType = valueUType.union(SequenceTool.getUType(val));
            valueCardinality = Cardinality.union(valueCardinality, SequenceTool.getCardinality(val));
            if (key.getItemType() != keyAtomicType) {
                keyAtomicType = null;
            }
            if (!MapItem.isKnownToConform(val, valueItemType)) {
                valueItemType = null;
            }
        }
    }

    /**
     * Get the size of the map
     */

    @Override
    public int size() {
        if (entries >= 0) {
            return entries;
        }
        int count = 0;
        //noinspection UnusedDeclaration
        for (net.sf.saxon.ma.map.KeyValuePair entry: keyValuePairs()) {
            count++;
        }
        return entries = count;
    }

    /**
     * Ask whether the map is empty
     *
     * @return true if and only if the size of the map is zero
     */
    @Override
    @CSharpSuppressWarnings("UnsafeIteratorConversion")
    public boolean isEmpty() {
        return entries == 0 || !imap.iterator().hasNext();
    }

    /**
     * Ask whether the map conforms to a given map type
     *
     * @param requiredKeyType   the required keyType
     * @param requiredValueType the required valueType
     * @param th        the type hierarchy cache for the configuration
     * @return true if the map conforms to the required type
     */
    @Override
    public boolean conforms(AtomicType requiredKeyType, SequenceType requiredValueType, TypeHierarchy th) {
        if (isEmpty()) {
            return true;
        }
        if (keyAtomicType == requiredKeyType && valueItemType == requiredValueType.getPrimaryType() &&
                Cardinality.subsumes(requiredValueType.getCardinality(), valueCardinality)) {
            return true;
        }
        boolean needFullCheck = false;
        if (requiredKeyType != BuiltInAtomicType.ANY_ATOMIC) {
            ItemType upperBoundKeyType = keyUType.toItemType();
            Affinity rel = th.relationship(requiredKeyType, upperBoundKeyType);
            if (rel == Affinity.SAME_TYPE || rel == Affinity.SUBSUMES) {
                // The key type is matched
            } else if (rel == Affinity.DISJOINT) {
                return false;
            } else {
                needFullCheck = true;
            }
        }
        ItemType requiredValueItemType = requiredValueType.getPrimaryType();
        if (requiredValueItemType != BuiltInAtomicType.ANY_ATOMIC) {
            ItemType upperBoundValueType = valueUType.toItemType();
            Affinity rel = th.relationship(requiredValueItemType, upperBoundValueType);
            if (rel == Affinity.SAME_TYPE || rel == Affinity.SUBSUMES) {
                // The value type is matched
            } else if (rel == Affinity.DISJOINT) {
                return false;
            } else {
                needFullCheck = true;
            }
        }
        int requiredValueCard = requiredValueType.getCardinality();
        if (!Cardinality.subsumes(requiredValueCard, valueCardinality)) {
            needFullCheck = true;
        }

        if (needFullCheck) {
            // we need to test the entries individually
            AtomicIterator keyIter = keys();
            AtomicValue key;
            while ((key = keyIter.next()) != null) {
                if (!requiredKeyType.matches(key, th)) {
                    return false;
                }
                if (!requiredValueType.matches(get(key), th)) {
                    return false;
                }
            }
        }

        return true;

    }

    /**
     * Get the type of the map. This method is used largely for diagnostics, to report
     * the type of a map when it differs from the required type. This method has the side-effect
     * of updating the internal type information held within the map.
     *
     * @param th The type hierarchy cache for the configuration
     * @return the type of this map
     */
    @Override
    public MapType getItemType(TypeHierarchy th) {
        AtomicType keyType = null;
        ItemType valueType = null;
        int valueCard = 0;
        // we need to test the entries individually
        AtomicIterator keyIter = keys();
        AtomicValue key;
        while ((key = keyIter.next()) != null) {
            Sequence val = get(key);
            if (keyType == null) {
                keyType = key.getItemType();
                valueType = SequenceTool.getItemType(val, th);
                valueCard = SequenceTool.getCardinality(val);
            } else {
                keyType = (AtomicType)Type.getCommonSuperType(keyType, key.getItemType(), th);
                valueType = Type.getCommonSuperType(valueType, SequenceTool.getItemType(val, th), th);
                valueCard = Cardinality.union(valueCard, SequenceTool.getCardinality(val));
            }
        }
        if (keyType == null) {
            // implies the map is empty
            this.keyUType = UType.VOID;
            this.valueUType = UType.VOID;
            this.valueCardinality = 0;
            return MapType.ANY_MAP_TYPE;
        } else {
            this.keyUType = keyType.getUType();
            this.valueUType = valueType.getUType();
            this.valueCardinality = valueCard;
            return new MapType(keyType, SequenceType.makeSequenceType(valueType, valueCard));
        }
    }

    /**
     * Get the lowest common item type of the keys in the map
     *
     * @return the most specific type to which all the keys belong. If the map is
     *         empty, return UType.VOID (the type with no instances)
     */
    @Override
    public UType getKeyUType() {
        return keyUType;
    }

    /**
     * Create a new map containing the existing entries in the map plus an additional entry,
     * without modifying the original. If there is already an entry with the specified key,
     * this entry is replaced by the new entry.
     *
     * @param key     the key of the new entry
     * @param value   the value associated with the new entry
     * @return the new map containing the additional entry
     */

    @Override
    public HashTrieMap addEntry(AtomicValue key, GroundedValue value) {
        AtomicMatchKey amk = makeKey(key);

        boolean isNew = imap.get(amk) == null;
        boolean empty = isEmpty();
        ImmutableMap imap2 = imap.put(amk, new net.sf.saxon.ma.map.KeyValuePair(key, value));
        HashTrieMap t2 = new HashTrieMap(imap2);
        t2.valueCardinality = this.valueCardinality;
        t2.keyUType = keyUType;
        t2.valueUType = valueUType;
        t2.keyAtomicType = keyAtomicType;
        t2.valueItemType = valueItemType;
        t2.updateTypeInformation(key, value, empty);
        if (entries >= 0) {
            t2.entries = isNew ? entries + 1 : entries;
        }
        return t2;
    }

    /**
     * Add a new entry to this map. Since the map is supposed to be immutable, this method
     * must only be called while initially populating the map, and must not be called if
     * anyone else might already be using the map.
     *
     * @param key     the key of the new entry. Any existing entry with this key is replaced.
     * @param value   the value associated with the new entry
     * @return true if an existing entry with the same key was replaced
     */

    public boolean initialPut(AtomicValue key, GroundedValue value) {
//        if (Instrumentation.ACTIVE) {
//            Instrumentation.count("initialPut");
//        }
        boolean empty = isEmpty();
        AtomicMatchKey amk = makeKey(key);
        boolean exists = imap.get(amk) != null;
        imap = imap.put(amk, new net.sf.saxon.ma.map.KeyValuePair(key, value));
        updateTypeInformation(key, value, empty);
        entries = -1;
        return exists;
    }

    private AtomicMatchKey makeKey(AtomicValue key) {
        return key.asMapKey();
    }


    /**
     * Remove an entry from the map
     *
     * @param key     the key of the entry to be removed
     * @return a new map in which the requested entry has been removed; or this map
     *         unchanged if the specified key was not present
     */

    @Override
    public HashTrieMap remove(AtomicValue key) {
        ImmutableMap m2 = imap.remove(makeKey(key));
        if (m2 == imap) {
            // The key is not present; the map is unchanged
            return this;
        }
        HashTrieMap result = new HashTrieMap(m2);
        result.keyUType = keyUType;
        result.valueUType = valueUType;
        result.valueCardinality = valueCardinality;
        result.entries = entries-1;
        return result;
    }

    /**
     * Get an entry from the Map
     *
     * @param key     the value of the key
     * @return the value associated with the given key, or null if the key is not present in the map
     */

    @Override
    public GroundedValue get(AtomicValue key)  {
        net.sf.saxon.ma.map.KeyValuePair o = imap.get(makeKey(key));
        return o==null ? null : o.value;
    }

    /**
     * Get an key/value pair from the Map
     *
     * @param key     the value of the key
     * @return the key-value-pair associated with the given key, or null if the key is not present in the map
     */

    public net.sf.saxon.ma.map.KeyValuePair getKeyValuePair(AtomicValue key) {
        return imap.get(makeKey(key));
    }

    /**
     * Get the set of all key values in the map
     * @return an iterator over the keys, in undefined order
     */

    @Override
    public AtomicIterator keys() {
        return new AtomicIterator() {

            final Iterator> baseIter = imap.iterator();

            @Override
            @CSharpSuppressWarnings("UnsafeIteratorConversion")
            public AtomicValue next() {
                if (baseIter.hasNext()) {
                    return baseIter.next().value.key;
                } else {
                    return null;
                }
            }
        };

    }

    /**
     * Get the set of all key-value pairs in the map
     *
     * @return an iterator over the key-value pairs
     */
    @Override
    public Iterable keyValuePairs() {
        // For C# - don't use a lambda expression here
        //noinspection Convert2Lambda
        return new Iterable() {
            @Override
            @CSharpSuppressWarnings("UnsafeIteratorConversion")
            public Iterator iterator() {
                return new Iterator() {
                    final Iterator> baseIter = imap.iterator();

                    @Override
                    public boolean hasNext() {
                        return baseIter.hasNext();
                    }

                    @Override
                    public net.sf.saxon.ma.map.KeyValuePair next() {
                        return baseIter.next().value;
                    }

                    @Override
                    public void remove() {
                        baseIter.remove();
                    }
                };
            }
        };
    }


    public void diagnosticDump() {
        System.err.println("Map details:");
        for (TrieKVP entry : imap) {
            AtomicMatchKey k1 = entry.key;
            AtomicValue k2 = entry.value.key;
            Sequence v = entry.value.value;
            System.err.println(k1.getClass() + " " + k1 +
                                       " #:" + k1.hashCode() +
                                       " = (" + k2.getClass() + " " + k2 + " : " + v + ")");
        }
    }

//    public String toShortString() {
//        int size = size();
//        if (size == 0) {
//            return "map{}";
//        } else if (size > 5) {
//            return "map{(:size " + size + ":)}";
//        } else {
//            StringBuilder buff = new StringBuilder(256);
//            buff.append("map{");
//            Iterator> iter = imap.iterator();
//            while (iter.hasNext()) {
//                Tuple2 entry = iter.next();
//                AtomicMatchKey k1 = entry._1;
//                AtomicValue k2 = entry._2.key;
//                Sequence v = entry._2.value;
//                buff.append(k2.toShortString());
//                buff.append(':');
//                buff.append(Err.depictSequence(v).toString().trim());
//                buff.append(", ");
//            }
//            if (size == 1) {
//                buff.append("}");
//            } else {
//                buff.setCharAt(buff.length() - 2, '}');
//            }
//            return buff.toString().trim();
//        }
//    }

    public String toString() {
        return MapItem.mapToString(this);
    }
}

// Copyright (c) 2018-2022 Saxonica Limited







© 2015 - 2024 Weber Informatics LLC | Privacy Policy