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 _oFallback
._arrayBestFit
??= fallback
._encoding
.GetBestFitUnicodeToBytesData();
80 public override bool Fallback(char charUnknown
, int index
)
82 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
83 // character in our array.
84 // Shouldn't be able to get here for all of our code pages, table would have to be messed up.
85 Debug
.Assert(_iCount
< 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(non surrogate)] Fallback char " + ((int)_cBestFit
).ToString("X4", CultureInfo
.InvariantCulture
) + " caused recursive fallback");
88 _cBestFit
= TryBestFit(charUnknown
);
89 if (_cBestFit
== '\0')
95 public override bool Fallback(char charUnknownHigh
, char charUnknownLow
, int index
)
97 // Double check input surrogate pair
98 if (!char.IsHighSurrogate(charUnknownHigh
))
99 throw new ArgumentOutOfRangeException(nameof(charUnknownHigh
),
100 SR
.Format(SR
.ArgumentOutOfRange_Range
,
103 if (!char.IsLowSurrogate(charUnknownLow
))
104 throw new ArgumentOutOfRangeException(nameof(charUnknownLow
),
105 SR
.Format(SR
.ArgumentOutOfRange_Range
,
108 // If we had a buffer already we're being recursive, throw, it's probably at the suspect
109 // character in our array. 0 is processing last character, < 0 is not falling back
110 // Shouldn't be able to get here, table would have to be messed up.
111 Debug
.Assert(_iCount
< 1, "[InternalEncoderBestFitFallbackBuffer.Fallback(surrogate)] Fallback char " + ((int)_cBestFit
).ToString("X4", CultureInfo
.InvariantCulture
) + " caused recursive fallback");
113 // Go ahead and get our fallback, surrogates don't have best fit
115 _iCount
= _iSize
= 2;
120 // Default version is overridden in EncoderReplacementFallback.cs
121 public override char GetNextChar()
123 // We want it to get < 0 because == 0 means that the current/last character is a fallback
124 // and we need to detect recursion. We could have a flag but we already have this counter.
127 // Do we have anything left? 0 is now last fallback char, negative is nothing left
131 // Need to get it out of the buffer.
132 // Make sure it didn't wrap from the fast count-- path
133 if (_iCount
== int.MaxValue
)
139 // Return the best fit character
143 public override bool MovePrevious()
145 // Exception fallback doesn't have anywhere to back up to.
149 // Return true if we could do it.
150 return _iCount
>= 0 && _iCount
<= _iSize
;
153 // How many characters left to output?
154 public override int Remaining
=> (_iCount
> 0) ? _iCount
: 0;
157 public override unsafe void Reset()
161 bFallingBack
= false;
164 // private helper methods
165 private char TryBestFit(char cUnknown
)
167 // Need to figure out our best fit character, low is beginning of array, high is 1 AFTER end of array
169 Debug
.Assert(_oFallback
._arrayBestFit
!= null);
170 int highBound
= _oFallback
._arrayBestFit
.Length
;
173 // Binary search the array
175 while ((iDiff
= (highBound
- lowBound
)) > 6)
177 // Look in the middle, which is complicated by the fact that we have 2 #s for each pair,
178 // so we don't want index to be odd because we want to be on word boundaries.
179 // Also note that index can never == highBound (because diff is rounded down)
180 index
= ((iDiff
/ 2) + lowBound
) & 0xFFFE;
182 char cTest
= _oFallback
._arrayBestFit
[index
];
183 if (cTest
== cUnknown
)
186 Debug
.Assert(index
+ 1 < _oFallback
._arrayBestFit
.Length
,
187 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
188 return _oFallback
._arrayBestFit
[index
+ 1];
190 else if (cTest
< cUnknown
)
192 // We weren't high enough
197 // We weren't low enough
202 for (index
= lowBound
; index
< highBound
; index
+= 2)
204 if (_oFallback
._arrayBestFit
[index
] == cUnknown
)
207 Debug
.Assert(index
+ 1 < _oFallback
._arrayBestFit
.Length
,
208 "[InternalEncoderBestFitFallbackBuffer.TryBestFit]Expected replacement character at end of array");
209 return _oFallback
._arrayBestFit
[index
+ 1];
213 // Char wasn't in our table