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