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