More Corelib cleanup (dotnet/coreclr#26872)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Text / EncoderBestFitFallback.cs
blob19851fa0c4e77a1ce80b66e7e3a33098d7c9be2c
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.
5 //
6 // This is used internally to create best fit behavior as per the original windows best fit behavior.
7 //
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.Threading;
13 namespace System.Text
15 internal class InternalEncoderBestFitFallback : EncoderFallback
17 // Our variables
18 internal Encoding _encoding;
19 internal char[]? _arrayBestFit = null;
21 internal InternalEncoderBestFitFallback(Encoding encoding)
23 // Need to load our replacement characters table.
24 _encoding = encoding;
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
42 // Our variables
43 private char _cBestFit = '\0';
44 private readonly InternalEncoderBestFitFallback _oFallback;
45 private int _iCount = -1;
46 private int _iSize;
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
52 get
54 if (s_InternalSyncObject == null)
56 object o = new object();
57 Interlocked.CompareExchange<object?>(ref s_InternalSyncObject, o, null);
59 return s_InternalSyncObject;
63 // Constructor
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();
79 // Fallback methods
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");
87 _iCount = _iSize = 1;
88 _cBestFit = TryBestFit(charUnknown);
89 if (_cBestFit == '\0')
90 _cBestFit = '?';
92 return true;
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,
101 0xD800, 0xDBFF));
103 if (!char.IsLowSurrogate(charUnknownLow))
104 throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
105 SR.Format(SR.ArgumentOutOfRange_Range,
106 0xDC00, 0xDFFF));
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
114 _cBestFit = '?';
115 _iCount = _iSize = 2;
117 return true;
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.
125 _iCount--;
127 // Do we have anything left? 0 is now last fallback char, negative is nothing left
128 if (_iCount < 0)
129 return '\0';
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)
135 _iCount = -1;
136 return '\0';
139 // Return the best fit character
140 return _cBestFit;
143 public override bool MovePrevious()
145 // Exception fallback doesn't have anywhere to back up to.
146 if (_iCount >= 0)
147 _iCount++;
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;
156 // Clear the buffer
157 public override unsafe void Reset()
159 _iCount = -1;
160 charStart = null;
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
168 int lowBound = 0;
169 Debug.Assert(_oFallback._arrayBestFit != null);
170 int highBound = _oFallback._arrayBestFit.Length;
171 int index;
173 // Binary search the array
174 int iDiff;
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)
185 // We found it
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
193 lowBound = index;
195 else
197 // We weren't low enough
198 highBound = index;
202 for (index = lowBound; index < highBound; index += 2)
204 if (_oFallback._arrayBestFit[index] == cUnknown)
206 // We found it
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
214 return '\0';