src/Epam.Kafka/Internals/CompatibilityExtensions.cs (57 lines of code) (raw):
// Copyright © 2024 EPAM Systems
#if !NET6_0_OR_GREATER
using System.Diagnostics;
using System.Text;
namespace Epam.Kafka.Internals;
internal static class CompatibilityExtensions
{
/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another
/// specified string according the type of search to use for the specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrences of <paramref name="oldValue"/>.
/// If value is equal to <c>null</c>, than all occurrences of <paramref name="oldValue"/> will be removed from the <paramref name="str"/>.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param>
/// <returns>A string that is equivalent to the current string except that all instances of <paramref name="oldValue"/> are replaced with <paramref name="newValue"/>.
/// If <paramref name="oldValue"/> is not found in the current instance, the method returns the current instance unchanged.</returns>
[DebuggerStepThrough]
public static string Replace(this string str,
string oldValue, string newValue,
StringComparison comparisonType)
{
// Check inputs.
if (str == null)
{
// Same as original .NET C# string.Replace behavior.
throw new ArgumentNullException(nameof(str));
}
if (oldValue == null)
{
// Same as original .NET C# string.Replace behavior.
throw new ArgumentNullException(nameof(oldValue));
}
if (oldValue.Length == 0)
{
// Same as original .NET C# string.Replace behavior.
throw new ArgumentException("String cannot be of zero length.");
}
if (str.Length == 0)
{
// Same as original .NET C# string.Replace behavior.
return str;
}
// Prepare string builder for storing the processed string.
// Note: StringBuilder has a better performance than String by 30-40%.
var resultStringBuilder = new StringBuilder(str.Length);
// Analyze the replacement: replace or remove.
bool isReplacementNullOrEmpty = string.IsNullOrEmpty(newValue);
// Replace all values.
const int valueNotFound = -1;
int foundAt;
int startSearchFromIndex = 0;
while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound)
{
// Append all characters until the found replacement.
int charsUntilReplacement = foundAt - startSearchFromIndex;
bool isNothingToAppend = charsUntilReplacement == 0;
if (!isNothingToAppend)
{
resultStringBuilder.Append(str, startSearchFromIndex, charsUntilReplacement);
}
// Process the replacement.
if (!isReplacementNullOrEmpty)
{
resultStringBuilder.Append(newValue);
}
// Prepare start index for the next search.
// This needed to prevent infinite loop, otherwise method always start search
// from the start of the string. For example: if an oldValue == "EXAMPLE", newValue == "example"
// and comparisonType == "any ignore case" will conquer to replacing:
// "EXAMPLE" to "example" to "example" to "example" … infinite loop.
startSearchFromIndex = foundAt + oldValue.Length;
if (startSearchFromIndex == str.Length)
{
// It is end of the input string: no more space for the next search.
// The input string ends with a value that has already been replaced.
// Therefore, the string builder with the result is complete and no further action is required.
return resultStringBuilder.ToString();
}
}
// Append the last part to the result.
int charsUntilStringEnd = str.Length - startSearchFromIndex;
resultStringBuilder.Append(str, startSearchFromIndex, charsUntilStringEnd);
return resultStringBuilder.ToString();
}
}
#endif