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.
10 using System.Globalization;
12 using System.Threading;
13 using System.Diagnostics;
14 using System.Diagnostics.Contracts;
18 internal sealed class InternalEncoderBestFitFallback : EncoderFallback
21 internal Encoding encoding = null;
22 internal char[] arrayBestFit = null;
24 internal InternalEncoderBestFitFallback(Encoding encoding)
26 // Need to load our replacement characters table.
27 this.encoding = encoding;
28 this.bIsMicrosoftBestFitFallback = true;
31 public override EncoderFallbackBuffer CreateFallbackBuffer()
33 return new InternalEncoderBestFitFallbackBuffer(this);
36 // Maximum number of characters that this instance of this fallback could return
37 public override int MaxCharCount
45 public override bool Equals(Object value)
47 InternalEncoderBestFitFallback that = value as InternalEncoderBestFitFallback;
50 return (this.encoding.CodePage == that.encoding.CodePage);
55 public override int GetHashCode()
57 return this.encoding.CodePage;
61 internal sealed class InternalEncoderBestFitFallbackBuffer : EncoderFallbackBuffer
64 private char cBestFit = '\0';
65 private InternalEncoderBestFitFallback oFallback;
66 private int iCount = -1;
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
75 if (s_InternalSyncObject == null)
77 Object o = new Object();
78 Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
80 return s_InternalSyncObject;
85 public InternalEncoderBestFitFallbackBuffer(InternalEncoderBestFitFallback fallback)
89 if (oFallback.arrayBestFit == null)
91 // Lock so we don't confuse ourselves.
92 lock (InternalSyncObject)
94 // Double check before we do it again.
95 if (oFallback.arrayBestFit == null)
96 oFallback.arrayBestFit = fallback.encoding.GetBestFitUnicodeToBytesData();
102 public override bool Fallback(char charUnknown, int index)
104 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
105 // character in our array.
106 // Shouldn't be able to get here for all of our code pages, table would have to be messed up.
107 Debug.Assert(iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
110 cBestFit = TryBestFit(charUnknown);
111 if (cBestFit == '\0')
117 public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
119 // Double check input surrogate pair
120 if (!Char.IsHighSurrogate(charUnknownHigh))
121 throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
122 SR.Format(SR.ArgumentOutOfRange_Range, 0xD800, 0xDBFF));
124 if (!Char.IsLowSurrogate(charUnknownLow))
125 throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
126 SR.Format(SR.ArgumentOutOfRange_Range, 0xDC00, 0xDFFF));
127 Contract.EndContractBlock();
129 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
130 // character in our array. 0 is processing last character, < 0 is not falling back
131 // Shouldn't be able to get here, table would have to be messed up.
132 Debug.Assert(iCount < 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)cBestFit).ToString("X4", CultureInfo.InvariantCulture) + " caused recursive fallback");
134 // Go ahead and get our fallback, surrogates don't have best fit
141 // Default version is overridden in EncoderReplacementFallback.cs
142 public override char GetNextChar()
144 // We want it to get < 0 because == 0 means that the current/last character is a fallback
145 // and we need to detect recursion. We could have a flag but we already have this counter.
148 // Do we have anything left? 0 is now last fallback char, negative is nothing left
152 // Need to get it out of the buffer.
153 // Make sure it didn't wrap from the fast count-- path
154 if (iCount == int.MaxValue)
160 // Return the best fit character
164 public override bool MovePrevious()
166 // Exception fallback doesn't have anywhere to back up to.
170 // Return true if we could do it.
171 return (iCount >= 0 && iCount <= iSize);
175 // How many characters left to output?
176 public override int Remaining
180 return (iCount > 0) ? iCount : 0;
185 public override unsafe void Reset()
189 bFallingBack = false;
192 // private helper methods
193 private char TryBestFit(char cUnknown)
195 // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
197 int highBound = oFallback.arrayBestFit.Length;
200 // Binary search the array
202 while ((iDiff = (highBound - lowBound)) > 6)
204 // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
205 // so we don't want index to be odd because we want to be on word boundaries.
206 // Also note that index can never == highBound (because diff is rounded down)
207 index = ((iDiff / 2) + lowBound) & 0xFFFE;
209 char cTest = oFallback.arrayBestFit[index];
210 if (cTest == cUnknown)
213 Debug.Assert(index + 1 < oFallback.arrayBestFit.Length,
214 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
215 return oFallback.arrayBestFit[index + 1];
217 else if (cTest < cUnknown)
219 // We weren't high enough
224 // We weren't low enough
229 for (index = lowBound; index < highBound; index += 2)
231 if (oFallback.arrayBestFit[index] == cUnknown)
234 Debug.Assert(index + 1 < oFallback.arrayBestFit.Length,
235 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
236 return oFallback.arrayBestFit[index + 1];
240 // Char wasn't in our table