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