File: System\Xml\NameTable.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace System.Xml
{
    /// <devdoc>
    ///    <para>
    ///       XmlNameTable implemented as a simple hash table.
    ///    </para>
    /// </devdoc>
    public class NameTable : XmlNameTable
    {
        //
        // Private types
        //
        private sealed class Entry
        {
            internal string str;
            internal int hashCode;
            internal Entry? next;
 
            internal Entry(string str, int hashCode, Entry? next)
            {
                this.str = str;
                this.hashCode = hashCode;
                this.next = next;
            }
        }
 
        //
        // Fields
        //
        private Entry?[] _entries;
        private int _count;
        private int _mask;
 
        //
        // Constructor
        /// <devdoc>
        ///      Public constructor.
        /// </devdoc>
        public NameTable()
        {
            _mask = 31;
            _entries = new Entry?[_mask + 1];
        }
 
        //
        // XmlNameTable public methods
        /// <devdoc>
        ///      Add the given string to the NameTable or return
        ///      the existing string if it is already in the NameTable.
        /// </devdoc>
        public override string Add(string key)
        {
            ArgumentNullException.ThrowIfNull(key);
 
            int len = key.Length;
            if (len == 0)
            {
                return string.Empty;
            }
 
            int hashCode = ComputeHash32(key);
 
            for (Entry? e = _entries[hashCode & _mask]; e != null; e = e.next)
            {
                if (e.hashCode == hashCode && e.str.Equals(key))
                {
                    return e.str;
                }
            }
 
            return AddEntry(key, hashCode);
        }
 
        /// <devdoc>
        ///      Add the given string to the NameTable or return
        ///      the existing string if it is already in the NameTable.
        /// </devdoc>
        public override string Add(char[] key, int start, int len)
        {
            if (len == 0)
            {
                return string.Empty;
            }
 
            // Compatibility check to ensure same exception as previous versions
            // independently of any exceptions throw by the hashing function.
            // note that NullReferenceException is the first one if key is null.
            if (start >= key.Length || start < 0 || (long)start + len > (long)key.Length)
            {
                throw new IndexOutOfRangeException();
            }
 
            // Compatibility check for len < 0, just throw the same exception as new string(key, start, len)
            ArgumentOutOfRangeException.ThrowIfNegative(len);
 
            int hashCode = string.GetHashCode(key.AsSpan(start, len));
 
            for (Entry? e = _entries[hashCode & _mask]; e != null; e = e.next)
            {
                if (e.hashCode == hashCode && e.str.AsSpan().SequenceEqual(key.AsSpan(start, len)))
                {
                    return e.str;
                }
            }
 
            return AddEntry(new string(key, start, len), hashCode);
        }
 
        /// <devdoc>
        ///      Find the matching string in the NameTable.
        /// </devdoc>
        public override string? Get(string value)
        {
            ArgumentNullException.ThrowIfNull(value);
 
            if (value.Length == 0)
            {
                return string.Empty;
            }
 
            int hashCode = ComputeHash32(value);
 
            for (Entry? e = _entries[hashCode & _mask]; e != null; e = e.next)
            {
                if (e.hashCode == hashCode && e.str.Equals(value))
                {
                    return e.str;
                }
            }
 
            return null;
        }
 
        /// <devdoc>
        ///      Find the matching string atom given a range of
        ///      characters.
        /// </devdoc>
        public override string? Get(char[] key, int start, int len)
        {
            if (len == 0)
            {
                return string.Empty;
            }
 
            if (start >= key.Length || start < 0 || (long)start + len > (long)key.Length)
            {
                throw new IndexOutOfRangeException();
            }
 
            // Compatibility check for len < 0, just return null
            if (len < 0)
            {
                return null;
            }
 
            int hashCode = string.GetHashCode(key.AsSpan(start, len));
 
            for (Entry? e = _entries[hashCode & _mask]; e != null; e = e.next)
            {
                if (e.hashCode == hashCode && e.str.AsSpan().SequenceEqual(key.AsSpan(start, len)))
                {
                    return e.str;
                }
            }
 
            return null;
        }
 
        internal string GetOrAddEntry(string str, int hashCode)
        {
            for (Entry? e = _entries[hashCode & _mask]; e != null; e = e.next)
            {
                if (e.hashCode == hashCode && e.str.Equals(str))
                {
                    return e.str;
                }
            }
 
            return AddEntry(str, hashCode);
        }
 
        internal static int ComputeHash32(string key)
        {
            // We rely on string.GetHashCode(ROS<char>) being randomized.
            // n.b. not calling string.GetHashCode() because we want hash code computation to match
            // char[]-based overload later in this file, so we normalize everything to ROS<char>.
 
            return string.GetHashCode(key.AsSpan());
        }
 
        //
        // Private methods
        //
 
        private string AddEntry(string str, int hashCode)
        {
            int index = hashCode & _mask;
            Entry e = new Entry(str, hashCode, _entries[index]);
            _entries[index] = e;
 
            if (_count++ == _mask)
            {
                Grow();
            }
 
            return e.str;
        }
 
        private void Grow()
        {
            int newMask = _mask * 2 + 1;
            Entry?[] oldEntries = _entries;
            Entry?[] newEntries = new Entry?[newMask + 1];
 
            // use oldEntries.Length to eliminate the range check
            for (int i = 0; i < oldEntries.Length; i++)
            {
                Entry? e = oldEntries[i];
                while (e != null)
                {
                    int newIndex = e.hashCode & newMask;
                    Entry? tmp = e.next;
                    e.next = newEntries[newIndex];
                    newEntries[newIndex] = e;
                    e = tmp;
                }
            }
 
            _entries = newEntries;
            _mask = newMask;
        }
    }
}