File: src\libraries\System.Private.CoreLib\src\System\Text\EncoderReplacementFallback.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Text
{
    public sealed class EncoderReplacementFallback : EncoderFallback
    {
        internal static readonly EncoderReplacementFallback s_default = new EncoderReplacementFallback();
 
        // Our variables
        private readonly string _strDefault;
 
        // Construction.  Default replacement fallback uses no best fit and ? replacement string
        public EncoderReplacementFallback() : this("?")
        {
        }
 
        public EncoderReplacementFallback(string replacement)
        {
            ArgumentNullException.ThrowIfNull(replacement);
 
            // Make sure it doesn't have bad surrogate pairs
            bool bFoundHigh = false;
            foreach (char c in replacement)
            {
                // Found a surrogate?
                if (char.IsSurrogate(c))
                {
                    // High or Low?
                    if (char.IsHighSurrogate(c))
                    {
                        // if already had a high one, stop
                        if (bFoundHigh)
                            break;  // break & throw at the bFoundHIgh below
                        bFoundHigh = true;
                    }
                    else
                    {
                        // Low, did we have a high?
                        if (!bFoundHigh)
                        {
                            // Didn't have one, make if fail when we stop
                            bFoundHigh = true;
                            break;
                        }
 
                        // Clear flag
                        bFoundHigh = false;
                    }
                }
                // If last was high we're in trouble (not surrogate so not low surrogate, so break)
                else if (bFoundHigh)
                    break;
            }
            if (bFoundHigh)
                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(replacement));
 
            _strDefault = replacement;
        }
 
        public string DefaultString => _strDefault;
 
        public override EncoderFallbackBuffer CreateFallbackBuffer() =>
            new EncoderReplacementFallbackBuffer(this);
 
        // Maximum number of characters that this instance of this fallback could return
        public override int MaxCharCount => _strDefault.Length;
 
        public override bool Equals([NotNullWhen(true)] object? value) =>
            value is EncoderReplacementFallback that &&
            _strDefault == that._strDefault;
 
        public override int GetHashCode() => _strDefault.GetHashCode();
    }
 
    public sealed class EncoderReplacementFallbackBuffer : EncoderFallbackBuffer
    {
        // Store our default string
        private readonly string _strDefault;
        private int _fallbackCount = -1;
        private int _fallbackIndex = -1;
 
        // Construction
        public EncoderReplacementFallbackBuffer(EncoderReplacementFallback fallback)
        {
            // 2X in case we're a surrogate pair
            _strDefault = fallback.DefaultString + fallback.DefaultString;
        }
 
        // Fallback Methods
        public override bool Fallback(char charUnknown, int index)
        {
            // If we had a buffer already we're being recursive, throw, it's probably at the suspect
            // character in our array.
            if (_fallbackCount >= 1)
            {
                // If we're recursive we may still have something in our buffer that makes this a surrogate
                if (char.IsHighSurrogate(charUnknown) && _fallbackCount >= 0 &&
                    char.IsLowSurrogate(_strDefault[_fallbackIndex + 1]))
                    ThrowLastCharRecursive(char.ConvertToUtf32(charUnknown, _strDefault[_fallbackIndex + 1]));
 
                // Nope, just one character
                ThrowLastCharRecursive(unchecked((int)charUnknown));
            }
 
            // Go ahead and get our fallback
            // Divide by 2 because we aren't a surrogate pair
            _fallbackCount = _strDefault.Length / 2;
            _fallbackIndex = -1;
 
            return _fallbackCount != 0;
        }
 
        public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
        {
            // Double check input surrogate pair
            if (!char.IsHighSurrogate(charUnknownHigh))
                throw new ArgumentOutOfRangeException(nameof(charUnknownHigh),
                    SR.Format(SR.ArgumentOutOfRange_Range, 0xD800, 0xDBFF));
 
            if (!char.IsLowSurrogate(charUnknownLow))
                throw new ArgumentOutOfRangeException(nameof(charUnknownLow),
                    SR.Format(SR.ArgumentOutOfRange_Range, 0xDC00, 0xDFFF));
 
            // If we had a buffer already we're being recursive, throw, it's probably at the suspect
            // character in our array.
            if (_fallbackCount >= 1)
                ThrowLastCharRecursive(char.ConvertToUtf32(charUnknownHigh, charUnknownLow));
 
            // Go ahead and get our fallback
            _fallbackCount = _strDefault.Length;
            _fallbackIndex = -1;
 
            return _fallbackCount != 0;
        }
 
        public override char GetNextChar()
        {
            // We want it to get < 0 because == 0 means that the current/last character is a fallback
            // and we need to detect recursion.  We could have a flag but we already have this counter.
            _fallbackCount--;
            _fallbackIndex++;
 
            // Do we have anything left? 0 is now last fallback char, negative is nothing left
            if (_fallbackCount < 0)
                return '\0';
 
            // Need to get it out of the buffer.
            // Make sure it didn't wrap from the fast count-- path
            if (_fallbackCount == int.MaxValue)
            {
                _fallbackCount = -1;
                return '\0';
            }
 
            // Now make sure its in the expected range
            Debug.Assert(_fallbackIndex < _strDefault.Length && _fallbackIndex >= 0,
                            "Index exceeds buffer range");
 
            return _strDefault[_fallbackIndex];
        }
 
        public override bool MovePrevious()
        {
            // Back up one, only if we just processed the last character (or earlier)
            if (_fallbackCount >= -1 && _fallbackIndex >= 0)
            {
                _fallbackIndex--;
                _fallbackCount++;
                return true;
            }
 
            // Return false 'cause we couldn't do it.
            return false;
        }
 
        // How many characters left to output?
        public override int Remaining =>
                // Our count is 0 for 1 character left.
                (_fallbackCount < 0) ? 0 : _fallbackCount;
 
        // Clear the buffer
        public override unsafe void Reset()
        {
            _fallbackCount = -1;
            _fallbackIndex = 0;
            charStart = null;
            bFallingBack = false;
        }
    }
}