1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Threading;
9 
10 namespace System.Text
11 {
12     internal static partial class EncodingTable
13     {
14         private static readonly Dictionary<string, int> s_nameToCodePageCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
15         private static readonly Dictionary<int, string> s_codePageToWebNameCache = new Dictionary<int, string>();
16         private static readonly Dictionary<int, string> s_codePageToEnglishNameCache = new Dictionary<int, string>();
17         private static readonly ReaderWriterLockSlim s_cacheLock = new ReaderWriterLockSlim();
18 
GetCodePageFromName(string name)19         internal static int GetCodePageFromName(string name)
20         {
21             if (name == null)
22                 return 0;
23 
24             int codePage;
25 
26             s_cacheLock.EnterUpgradeableReadLock();
27             try
28             {
29                 if (s_nameToCodePageCache.TryGetValue(name, out codePage))
30                 {
31                     return codePage;
32                 }
33                 else
34                 {
35                     // Okay, we didn't find it in the hash table, try looking it up in the unmanaged data.
36                     codePage = InternalGetCodePageFromName(name);
37                     if (codePage == 0)
38                         return 0;
39 
40                     s_cacheLock.EnterWriteLock();
41                     try
42                     {
43                         int cachedCodePage;
44                         if (s_nameToCodePageCache.TryGetValue(name, out cachedCodePage))
45                         {
46                             return cachedCodePage;
47                         }
48                         s_nameToCodePageCache.Add(name, codePage);
49                     }
50                     finally
51                     {
52                         s_cacheLock.ExitWriteLock();
53                     }
54                 }
55             }
56             finally
57             {
58                 s_cacheLock.ExitUpgradeableReadLock();
59             }
60 
61             return codePage;
62         }
63 
InternalGetCodePageFromName(string name)64         private static int InternalGetCodePageFromName(string name)
65         {
66             int left = 0;
67             int right = s_encodingNameIndices.Length - 2;
68             int index;
69             int result;
70 
71             Debug.Assert(s_encodingNameIndices.Length == s_codePagesByName.Length + 1);
72             Debug.Assert(s_encodingNameIndices[s_encodingNameIndices.Length - 1] == s_encodingNames.Length);
73 
74             name = name.ToLowerInvariant();
75 
76             //Binary search the array until we have only a couple of elements left and then
77             //just walk those elements.
78             while ((right - left) > 3)
79             {
80                 index = ((right - left) / 2) + left;
81 
82                 Debug.Assert(index < s_encodingNameIndices.Length - 1);
83                 result = CompareOrdinal(name, s_encodingNames, s_encodingNameIndices[index], s_encodingNameIndices[index + 1] - s_encodingNameIndices[index]);
84                 if (result == 0)
85                 {
86                     //We found the item, return the associated codePage.
87                     return (s_codePagesByName[index]);
88                 }
89                 else if (result < 0)
90                 {
91                     //The name that we're looking for is less than our current index.
92                     right = index;
93                 }
94                 else
95                 {
96                     //The name that we're looking for is greater than our current index
97                     left = index;
98                 }
99             }
100 
101             //Walk the remaining elements (it'll be 3 or fewer).
102             for (; left <= right; left++)
103             {
104                 Debug.Assert(left < s_encodingNameIndices.Length - 1);
105                 if (CompareOrdinal(name, s_encodingNames, s_encodingNameIndices[left], s_encodingNameIndices[left + 1] - s_encodingNameIndices[left]) == 0)
106                 {
107                     return (s_codePagesByName[left]);
108                 }
109             }
110 
111             // The encoding name is not valid.
112             return 0;
113         }
114 
CompareOrdinal(string s1, string s2, int index, int length)115         private static int CompareOrdinal(string s1, string s2, int index, int length)
116         {
117             int count = s1.Length;
118             if (count > length)
119                 count = length;
120 
121             int i = 0;
122             while (i < count && s1[i] == s2[index + i])
123                 i++;
124 
125             if (i < count)
126                 return (int)(s1[i] - s2[index + i]);
127 
128             return s1.Length - length;
129         }
130 
GetWebNameFromCodePage(int codePage)131         internal static string GetWebNameFromCodePage(int codePage)
132         {
133             return GetNameFromCodePage(codePage, s_webNames, s_webNameIndices, s_codePageToWebNameCache);
134         }
135 
GetEnglishNameFromCodePage(int codePage)136         internal static string GetEnglishNameFromCodePage(int codePage)
137         {
138             return GetNameFromCodePage(codePage, s_englishNames, s_englishNameIndices, s_codePageToEnglishNameCache);
139         }
140 
GetNameFromCodePage(int codePage, string names, int[] indices, Dictionary<int, string> cache)141         private static string GetNameFromCodePage(int codePage, string names, int[] indices, Dictionary<int, string> cache)
142         {
143             string name;
144 
145             Debug.Assert(s_mappedCodePages.Length + 1 == indices.Length);
146             Debug.Assert(indices[indices.Length - 1] == names.Length);
147 
148             //This is a linear search, but we probably won't be doing it very often.
149             for (int i = 0; i < s_mappedCodePages.Length; i++)
150             {
151                 if (s_mappedCodePages[i] == codePage)
152                 {
153                     Debug.Assert(i < indices.Length - 1);
154 
155                     s_cacheLock.EnterUpgradeableReadLock();
156                     try
157                     {
158                         if (cache.TryGetValue(codePage, out name))
159                         {
160                             return name;
161                         }
162                         else
163                         {
164                             name = names.Substring(indices[i], indices[i + 1] - indices[i]);
165 
166                             s_cacheLock.EnterWriteLock();
167                             try
168                             {
169                                 string cachedName;
170                                 if (cache.TryGetValue(codePage, out cachedName))
171                                 {
172                                     return cachedName;
173                                 }
174 
175                                 cache.Add(codePage, name);
176                             }
177                             finally
178                             {
179                                 s_cacheLock.ExitWriteLock();
180                             }
181                         }
182                     }
183                     finally
184                     {
185                         s_cacheLock.ExitUpgradeableReadLock();
186                     }
187 
188                     return name;
189                 }
190             }
191 
192             //Nope, we didn't find it.
193             return null;
194         }
195     }
196 }
197