f70213f8d6cb3d1f18c6425083f853e61a933b23
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Text / DecoderBestFitFallback.cs
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 //
6 // This is used internally to create best fit behavior as per the original windows best fit behavior.
7 //
8
9 using System;
10 using System.Text;
11 using System.Threading;
12 using System.Diagnostics;
13 using System.Diagnostics.Contracts;
14
15 namespace System.Text
16 {
17     internal sealed class InternalDecoderBestFitFallback : DecoderFallback
18     {
19         // Our variables
20         internal Encoding encoding = null;
21         internal char[] arrayBestFit = null;
22         internal char cReplacement = '?';
23
24         internal InternalDecoderBestFitFallback(Encoding encoding)
25         {
26             // Need to load our replacement characters table.
27             this.encoding = encoding;
28             this.bIsMicrosoftBestFitFallback = true;
29         }
30
31         public override DecoderFallbackBuffer CreateFallbackBuffer()
32         {
33             return new InternalDecoderBestFitFallbackBuffer(this);
34         }
35
36         // Maximum number of characters that this instance of this fallback could return
37         public override int MaxCharCount
38         {
39             get
40             {
41                 return 1;
42             }
43         }
44
45         public override bool Equals(Object value)
46         {
47             InternalDecoderBestFitFallback that = value as InternalDecoderBestFitFallback;
48             if (that != null)
49             {
50                 return (this.encoding.CodePage == that.encoding.CodePage);
51             }
52             return (false);
53         }
54
55         public override int GetHashCode()
56         {
57             return this.encoding.CodePage;
58         }
59     }
60
61     internal sealed class InternalDecoderBestFitFallbackBuffer : DecoderFallbackBuffer
62     {
63         // Our variables
64         internal char cBestFit = '\0';
65         internal int iCount = -1;
66         internal int iSize;
67         private InternalDecoderBestFitFallback oFallback;
68
69         // Private object for locking instead of locking on a public type for SQL reliability work.
70         private static Object s_InternalSyncObject;
71         private static Object InternalSyncObject
72         {
73             get
74             {
75                 if (s_InternalSyncObject == null)
76                 {
77                     Object o = new Object();
78                     Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
79                 }
80                 return s_InternalSyncObject;
81             }
82         }
83
84         // Constructor
85         public InternalDecoderBestFitFallbackBuffer(InternalDecoderBestFitFallback fallback)
86         {
87             oFallback = fallback;
88
89             if (oFallback.arrayBestFit == null)
90             {
91                 // Lock so we don't confuse ourselves.
92                 lock (InternalSyncObject)
93                 {
94                     // Double check before we do it again.
95                     if (oFallback.arrayBestFit == null)
96                         oFallback.arrayBestFit = fallback.encoding.GetBestFitBytesToUnicodeData();
97                 }
98             }
99         }
100
101         // Fallback methods
102         public override bool Fallback(byte[] bytesUnknown, int index)
103         {
104             // We expect no previous fallback in our buffer
105             Debug.Assert(iCount < 1, "[DecoderReplacementFallbackBuffer.Fallback] Calling fallback without a previously empty buffer");
106
107             cBestFit = TryBestFit(bytesUnknown);
108             if (cBestFit == '\0')
109                 cBestFit = oFallback.cReplacement;
110
111             iCount = iSize = 1;
112
113             return true;
114         }
115
116         // Default version is overridden in DecoderReplacementFallback.cs
117         public override char GetNextChar()
118         {
119             // We want it to get < 0 because == 0 means that the current/last character is a fallback
120             // and we need to detect recursion.  We could have a flag but we already have this counter.
121             iCount--;
122
123             // Do we have anything left? 0 is now last fallback char, negative is nothing left
124             if (iCount < 0)
125                 return '\0';
126
127             // Need to get it out of the buffer.
128             // Make sure it didn't wrap from the fast count-- path
129             if (iCount == int.MaxValue)
130             {
131                 iCount = -1;
132                 return '\0';
133             }
134
135             // Return the best fit character
136             return cBestFit;
137         }
138
139         public override bool MovePrevious()
140         {
141             // Exception fallback doesn't have anywhere to back up to.
142             if (iCount >= 0)
143                 iCount++;
144
145             // Return true if we could do it.
146             return (iCount >= 0 && iCount <= iSize);
147         }
148
149         // How many characters left to output?
150         public override int Remaining
151         {
152             get
153             {
154                 return (iCount > 0) ? iCount : 0;
155             }
156         }
157
158         // Clear the buffer
159         public override unsafe void Reset()
160         {
161             iCount = -1;
162             byteStart = null;
163         }
164
165         // This version just counts the fallback and doesn't actually copy anything.
166         internal unsafe override int InternalFallback(byte[] bytes, byte* pBytes)
167         // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
168         // array, and we might need the index, hence the byte*
169         {
170             // return our replacement string Length (always 1 for InternalDecoderBestFitFallback, either
171             // a best fit char or ?
172             return 1;
173         }
174
175         // private helper methods
176         private char TryBestFit(byte[] bytesCheck)
177         {
178             // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
179             int lowBound = 0;
180             int highBound = oFallback.arrayBestFit.Length;
181             int index;
182             char cCheck;
183
184             // Check trivial case first (no best fit)
185             if (highBound == 0)
186                 return '\0';
187
188             // If our array is too small or too big we can't check
189             if (bytesCheck.Length == 0 || bytesCheck.Length > 2)
190                 return '\0';
191
192             if (bytesCheck.Length == 1)
193                 cCheck = unchecked((char)bytesCheck[0]);
194             else
195                 cCheck = unchecked((char)((bytesCheck[0] << 8) + bytesCheck[1]));
196
197             // Check trivial out of range case
198             if (cCheck < oFallback.arrayBestFit[0] || cCheck > oFallback.arrayBestFit[highBound - 2])
199                 return '\0';
200
201             // Binary search the array
202             int iDiff;
203             while ((iDiff = (highBound - lowBound)) > 6)
204             {
205                 // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
206                 // so we don't want index to be odd because it must be word aligned.
207                 // Also note that index can never == highBound (because diff is rounded down)
208                 index = ((iDiff / 2) + lowBound) & 0xFFFE;
209
210                 char cTest = oFallback.arrayBestFit[index];
211                 if (cTest == cCheck)
212                 {
213                     // We found it
214                     Debug.Assert(index + 1 < oFallback.arrayBestFit.Length,
215                         "[InternalDecoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
216                     return oFallback.arrayBestFit[index + 1];
217                 }
218                 else if (cTest < cCheck)
219                 {
220                     // We weren't high enough
221                     lowBound = index;
222                 }
223                 else
224                 {
225                     // We weren't low enough
226                     highBound = index;
227                 }
228             }
229
230             for (index = lowBound; index < highBound; index += 2)
231             {
232                 if (oFallback.arrayBestFit[index] == cCheck)
233                 {
234                     // We found it
235                     Debug.Assert(index + 1 < oFallback.arrayBestFit.Length,
236                         "[InternalDecoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
237                     return oFallback.arrayBestFit[index + 1];
238                 }
239             }
240
241             // Char wasn't in our table            
242             return '\0';
243         }
244     }
245 }
246