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
.Globalization
;
11 using System
.Threading
;
15 internal class InternalEncoderBestFitFallback
: EncoderFallback
18 internal Encoding _encoding
;
19 internal char[]? _arrayBestFit
= null;
21 internal InternalEncoderBestFitFallback(Encoding encoding
)
23 // Need to load our replacement characters table.
27 public override EncoderFallbackBuffer
CreateFallbackBuffer() =>
28 new InternalEncoderBestFitFallbackBuffer(this);
30 // Maximum number of characters that this instance of this fallback could return
31 public override int MaxCharCount
=> 1;
33 public override bool Equals(object? value) =>
34 value is InternalEncoderBestFitFallback that
&&
35 _encoding
.CodePage
== that
._encoding
.CodePage
;
37 public override int GetHashCode() => _encoding
.CodePage
;
40 internal sealed class InternalEncoderBestFitFallbackBuffer
: EncoderFallbackBuffer
43 private char _cBestFit
= '\0';
44 private readonly InternalEncoderBestFitFallback _oFallback
;
45 private int _iCount
= -1;
48 // Private object for locking instead of locking on a public type for SQL reliability work.
49 private static object? s_InternalSyncObject
;
50 private static object InternalSyncObject
54 if (s_InternalSyncObject
== null)
56 object o
= new object();
57 Interlocked
.CompareExchange
<object?>(ref s_InternalSyncObject
, o
, null);
59 return s_InternalSyncObject
;
64 public InternalEncoderBestFitFallbackBuffer(InternalEncoderBestFitFallback fallback
)
66 _oFallback
= fallback
;
68 if (_oFallback
._arrayBestFit
== null)
70 // Lock so we don't confuse ourselves.
71 lock (InternalSyncObject
)
73 // Double check before we do it again.
74 if (_oFallback
._arrayBestFit
== null)
75 _oFallback
._arrayBestFit
= fallback
._encoding
.GetBestFitUnicodeToBytesData();
81 public override bool Fallback(char charUnknown
, int index
)
83 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
84 // character in our array.
85 // Shouldn't be able to get here for all of our code pages, table would have to be messed up.
86 Debug
.Assert(_iCount
< 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit
).ToString("X4", CultureInfo
.InvariantCulture
) + " caused recursive fallback");
89 _cBestFit
= TryBestFit(charUnknown
);
90 if (_cBestFit
== '\0')
96 public override bool Fallback(char charUnknownHigh
, char charUnknownLow
, int index
)
98 // Double check input surrogate pair
99 if (!char.IsHighSurrogate(charUnknownHigh
))
100 throw new ArgumentOutOfRangeException(nameof(charUnknownHigh
),
101 SR
.Format(SR
.ArgumentOutOfRange_Range
,
104 if (!char.IsLowSurrogate(charUnknownLow
))
105 throw new ArgumentOutOfRangeException(nameof(charUnknownLow
),
106 SR
.Format(SR
.ArgumentOutOfRange_Range
,
109 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
110 // character in our array. 0 is processing last character, < 0 is not falling back
111 // Shouldn't be able to get here, table would have to be messed up.
112 Debug
.Assert(_iCount
< 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit
).ToString("X4", CultureInfo
.InvariantCulture
) + " caused recursive fallback");
114 // Go ahead and get our fallback, surrogates don't have best fit
116 _iCount
= _iSize
= 2;
121 // Default version is overridden in EncoderReplacementFallback.cs
122 public override char GetNextChar()
124 // We want it to get < 0 because == 0 means that the current/last character is a fallback
125 // and we need to detect recursion. We could have a flag but we already have this counter.
128 // Do we have anything left? 0 is now last fallback char, negative is nothing left
132 // Need to get it out of the buffer.
133 // Make sure it didn't wrap from the fast count-- path
134 if (_iCount
== int.MaxValue
)
140 // Return the best fit character
144 public override bool MovePrevious()
146 // Exception fallback doesn't have anywhere to back up to.
150 // Return true if we could do it.
151 return (_iCount
>= 0 && _iCount
<= _iSize
);
155 // How many characters left to output?
156 public override int Remaining
=> (_iCount
> 0) ? _iCount
: 0;
159 public override unsafe void Reset()
163 bFallingBack
= false;
166 // private helper methods
167 private char TryBestFit(char cUnknown
)
169 // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
171 Debug
.Assert(_oFallback
._arrayBestFit
!= null);
172 int highBound
= _oFallback
._arrayBestFit
.Length
;
175 // Binary search the array
177 while ((iDiff
= (highBound
- lowBound
)) > 6)
179 // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
180 // so we don't want index to be odd because we want to be on word boundaries.
181 // Also note that index can never == highBound (because diff is rounded down)
182 index
= ((iDiff
/ 2) + lowBound
) & 0xFFFE;
184 char cTest
= _oFallback
._arrayBestFit
[index
];
185 if (cTest
== cUnknown
)
188 Debug
.Assert(index
+ 1 < _oFallback
._arrayBestFit
.Length
,
189 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
190 return _oFallback
._arrayBestFit
[index
+ 1];
192 else if (cTest
< cUnknown
)
194 // We weren't high enough
199 // We weren't low enough
204 for (index
= lowBound
; index
< highBound
; index
+= 2)
206 if (_oFallback
._arrayBestFit
[index
] == cUnknown
)
209 Debug
.Assert(index
+ 1 < _oFallback
._arrayBestFit
.Length
,
210 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
211 return _oFallback
._arrayBestFit
[index
+ 1];
215 // Char wasn't in our table