Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Text / EncoderBestFitFallback.cs
blob33df140e670838413a27579f0ca6b2b1b4135be6
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 if (_oFallback._arrayBestFit == null)
75 _oFallback._arrayBestFit = fallback._encoding.GetBestFitUnicodeToBytesData();
80 // Fallback methods
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");
88 _iCount = _iSize = 1;
89 _cBestFit = TryBestFit(charUnknown);
90 if (_cBestFit == '\0')
91 _cBestFit = '?';
93 return true;
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,
102 0xD800, 0xDBFF));
104 if (!char.IsLowSurrogate(charUnknownLow))
105 throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
106 SR.Format(SR.ArgumentOutOfRange_Range,
107 0xDC00, 0xDFFF));
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
115 _cBestFit = '?';
116 _iCount = _iSize = 2;
118 return true;
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.
126 _iCount--;
128 // Do we have anything left? 0 is now last fallback char, negative is nothing left
129 if (_iCount < 0)
130 return '\0';
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)
136 _iCount = -1;
137 return '\0';
140 // Return the best fit character
141 return _cBestFit;
144 public override bool MovePrevious()
146 // Exception fallback doesn't have anywhere to back up to.
147 if (_iCount >= 0)
148 _iCount++;
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;
158 // Clear the buffer
159 public override unsafe void Reset()
161 _iCount = -1;
162 charStart = null;
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
170 int lowBound = 0;
171 Debug.Assert(_oFallback._arrayBestFit != null);
172 int highBound = _oFallback._arrayBestFit.Length;
173 int index;
175 // Binary search the array
176 int iDiff;
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)
187 // We found it
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
195 lowBound = index;
197 else
199 // We weren't low enough
200 highBound = index;
204 for (index = lowBound; index < highBound; index += 2)
206 if (_oFallback._arrayBestFit[index] == cUnknown)
208 // We found it
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
216 return '\0';