| File: System\Xml\NameTable.cs | Web Access |
| Project: src\runtime\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; } } }