File: System\Text\EncodingTable.cs
Web Access
Project: src\src\libraries\System.Text.Encoding.CodePages\src\System.Text.Encoding.CodePages.csproj (System.Text.Encoding.CodePages)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Diagnostics;
 
namespace System.Text
{
    internal static partial class EncodingTable
    {
        private static readonly ConcurrentDictionary<string, int> s_nameToCodePageCache = new(StringComparer.OrdinalIgnoreCase);
        private static readonly ConcurrentDictionary<int, string> s_codePageToWebNameCache = new();
        private static readonly ConcurrentDictionary<int, string> s_codePageToEnglishNameCache = new();
        private static readonly ConcurrentDictionary<int, (ushort FamilyCodePage, byte CodePageFlags)> s_codePageToItemCache = new();
 
        internal static (ushort FamilyCodePage, byte CodePageFlags) GetCodePageItem(int codePage)
        {
            if (s_codePageToItemCache.TryGetValue(codePage, out (ushort FamilyCodePage, byte CodePageFlags) item))
            {
                return item;
            }
 
            int i = MappedCodePages.IndexOf((ushort)codePage);
            if (i < 0)
            {
                return ((ushort)codePage, (byte)0);
            }
 
            item = (MappedFamilyCodePage[i], MappedFlags[i]);
            s_codePageToItemCache.TryAdd(codePage, item);
 
            return item;
        }
 
        internal static int GetCodePageFromName(string name)
        {
            if (name is null)
            {
                return 0;
            }
 
            if (s_nameToCodePageCache.TryGetValue(name, out int codePage))
            {
                return codePage;
            }
 
            codePage = InternalGetCodePageFromName(name);
            if (codePage != 0)
            {
                s_nameToCodePageCache.TryAdd(name, codePage);
            }
 
            return codePage;
        }
 
        private static int InternalGetCodePageFromName(string name)
        {
            ReadOnlySpan<int> encodingNameIndices = EncodingNameIndices;
 
            int left = 0;
            int right = encodingNameIndices.Length - 2;
            int index;
            int result;
 
            Debug.Assert(encodingNameIndices.Length == CodePagesByName.Length + 1);
            Debug.Assert(encodingNameIndices[encodingNameIndices.Length - 1] == EncodingNames.Length);
 
            name = name.ToLowerInvariant();
 
            // Binary search the array until we have only a couple of elements left and then
            // just walk those elements.
            while ((right - left) > 3)
            {
                index = ((right - left) / 2) + left;
 
                Debug.Assert(index < encodingNameIndices.Length - 1);
                result = CompareOrdinal(name, EncodingNames, encodingNameIndices[index], encodingNameIndices[index + 1] - encodingNameIndices[index]);
                if (result == 0)
                {
                    //We found the item, return the associated codePage.
                    return CodePagesByName[index];
                }
                else if (result < 0)
                {
                    //The name that we're looking for is less than our current index.
                    right = index;
                }
                else
                {
                    //The name that we're looking for is greater than our current index
                    left = index;
                }
            }
 
            // Walk the remaining elements (it'll be 3 or fewer).
            for (; left <= right; left++)
            {
                Debug.Assert(left < encodingNameIndices.Length - 1);
                if (CompareOrdinal(name, EncodingNames, encodingNameIndices[left], encodingNameIndices[left + 1] - encodingNameIndices[left]) == 0)
                {
                    return CodePagesByName[left];
                }
            }
 
            // The encoding name is not valid.
            return 0;
        }
 
        private static int CompareOrdinal(string s1, string s2, int index, int length)
        {
            int count = s1.Length;
            if (count > length)
                count = length;
 
            int i = 0;
            while (i < count && s1[i] == s2[index + i])
                i++;
 
            if (i < count)
                return (int)(s1[i] - s2[index + i]);
 
            return s1.Length - length;
        }
 
        internal static string? GetWebNameFromCodePage(int codePage)
        {
            return GetNameFromCodePage(codePage, WebNames, WebNameIndices, s_codePageToWebNameCache);
        }
 
        internal static string? GetEnglishNameFromCodePage(int codePage)
        {
            return GetNameFromCodePage(codePage, EnglishNames, EnglishNameIndices, s_codePageToEnglishNameCache);
        }
 
        private static string? GetNameFromCodePage(int codePage, string names, ReadOnlySpan<int> indices, ConcurrentDictionary<int, string> cache)
        {
            Debug.Assert(MappedCodePages.Length + 1 == indices.Length);
            Debug.Assert(indices[indices.Length - 1] == names.Length);
 
            if ((uint)codePage > ushort.MaxValue)
            {
                return null;
            }
 
            if (cache.TryGetValue(codePage, out string? cachedName))
            {
                return cachedName;
            }
 
            int i = MappedCodePages.IndexOf((ushort)codePage);
            if (i < 0)
            {
                return null;
            }
 
            Debug.Assert(i < indices.Length - 1);
 
            string name = names.Substring(indices[i], indices[i + 1] - indices[i]);
            cache.TryAdd(codePage, name);
 
            return name;
        }
    }
}