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.
6 // This is used internally to create best fit behavior as per the original windows best fit behavior.
9 using System.Diagnostics;
10 using System.Diagnostics.Contracts;
11 using System.Globalization;
12 using System.Threading;
16 internal class InternalEncoderBestFitFallback : EncoderFallback
19 internal Encoding _encoding = null;
20 internal char[] _arrayBestFit = null;
22 internal InternalEncoderBestFitFallback(Encoding encoding)
24 // Need to load our replacement characters table.
28 public override EncoderFallbackBuffer CreateFallbackBuffer()
30 return new InternalEncoderBestFitFallbackBuffer(this);
33 // Maximum number of characters that this instance of this fallback could return
34 public override int MaxCharCount
42 public override bool Equals(Object value)
44 InternalEncoderBestFitFallback that = value as InternalEncoderBestFitFallback;
47 return (_encoding.CodePage == that._encoding.CodePage);
52 public override int GetHashCode()
54 return _encoding.CodePage;
58 internal sealed class InternalEncoderBestFitFallbackBuffer : EncoderFallbackBuffer
61 private char _cBestFit = '\0';
62 private InternalEncoderBestFitFallback _oFallback;
63 private int _iCount = -1;
66 // Private object for locking instead of locking on a public type for SQL reliability work.
67 private static Object s_InternalSyncObject;
68 private static Object InternalSyncObject
72 if (s_InternalSyncObject == null)
74 Object o = new Object();
75 Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
77 return s_InternalSyncObject;
82 public InternalEncoderBestFitFallbackBuffer(InternalEncoderBestFitFallback fallback)
84 _oFallback = fallback;
86 if (_oFallback._arrayBestFit == null)
88 // Lock so we don't confuse ourselves.
89 lock (InternalSyncObject)
91 // Double check before we do it again.
92 if (_oFallback._arrayBestFit == null)
93 _oFallback._arrayBestFit = fallback._encoding.GetBestFitUnicodeToBytesData();
99 public override bool Fallback(char charUnknown, int index)
101 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
102 // character in our array.
103 // Shouldn't be able to get here for all of our code pages, table would have to be messed up.
104 Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
106 _iCount = _iSize = 1;
107 _cBestFit = TryBestFit(charUnknown);
108 if (_cBestFit == '\0')
114 public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
116 // Double check input surrogate pair
117 if (!Char.IsHighSurrogate(charUnknownHigh))
118 throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
119 SR.Format(SR.ArgumentOutOfRange_Range,
122 if (!Char.IsLowSurrogate(charUnknownLow))
123 throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
124 SR.Format(SR.ArgumentOutOfRange_Range,
126 Contract.EndContractBlock();
128 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
129 // character in our array. 0 is processing last character, < 0 is not falling back
130 // Shouldn't be able to get here, table would have to be messed up.
131 Debug.Assert(_iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
133 // Go ahead and get our fallback, surrogates don't have best fit
135 _iCount = _iSize = 2;
140 // Default version is overridden in EncoderReplacementFallback.cs
141 public override char GetNextChar()
143 // We want it to get < 0 because == 0 means that the current/last character is a fallback
144 // and we need to detect recursion. We could have a flag but we already have this counter.
147 // Do we have anything left? 0 is now last fallback char, negative is nothing left
151 // Need to get it out of the buffer.
152 // Make sure it didn't wrap from the fast count-- path
153 if (_iCount == int.MaxValue)
159 // Return the best fit character
163 public override bool MovePrevious()
165 // Exception fallback doesn't have anywhere to back up to.
169 // Return true if we could do it.
170 return (_iCount >= 0 && _iCount <= _iSize);
174 // How many characters left to output?
175 public override int Remaining
179 return (_iCount > 0) ? _iCount : 0;
184 public override unsafe void Reset()
188 bFallingBack = false;
191 // private helper methods
192 private char TryBestFit(char cUnknown)
194 // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
196 int highBound = _oFallback._arrayBestFit.Length;
199 // Binary search the array
201 while ((iDiff = (highBound - lowBound)) > 6)
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 we want to be on word boundaries.
205 // Also note that index can never == highBound (because diff is rounded down)
206 index = ((iDiff / 2) + lowBound) & 0xFFFE;
208 char cTest = _oFallback._arrayBestFit[index];
209 if (cTest == cUnknown)
212 Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
213 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
214 return _oFallback._arrayBestFit[index + 1];
216 else if (cTest < cUnknown)
218 // We weren't high enough
223 // We weren't low enough
228 for (index = lowBound; index < highBound; index += 2)
230 if (_oFallback._arrayBestFit[index] == cUnknown)
233 Debug.Assert(index + 1 < _oFallback._arrayBestFit.Length,
234 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
235 return _oFallback._arrayBestFit[index + 1];
239 // Char wasn't in our table