// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
namespace EpicGames.Core
{
///
/// Represents a memory region which can be treated as a utf-8 string.
///
public struct ReadOnlyUtf8String : IEquatable
{
///
/// An empty string
///
public static readonly ReadOnlyUtf8String Empty = new ReadOnlyUtf8String();
///
/// The data represented by this string
///
public ReadOnlyMemory Memory { get; }
///
/// Returns read only span for this string
///
public ReadOnlySpan Span
{
get { return Memory.Span; }
}
///
/// Determines if this string is empty
///
public bool IsEmpty
{
get { return Memory.IsEmpty; }
}
///
/// Returns the length of this string
///
public int Length
{
get { return Memory.Length; }
}
///
/// Allows indexing individual bytes of the data
///
/// Byte index
/// Byte at the given index
public byte this[int Index]
{
get { return Span[Index]; }
}
///
/// Constructor
///
/// Text to construct from
public ReadOnlyUtf8String(string Text)
{
this.Memory = Encoding.UTF8.GetBytes(Text);
}
///
/// Constructor
///
/// The data to construct from
public ReadOnlyUtf8String(ReadOnlyMemory Memory)
{
this.Memory = Memory;
}
///
/// Constructor
///
/// The buffer to construct from
/// Offset within the buffer
/// Length of the string within the buffer
public ReadOnlyUtf8String(byte[] Buffer, int Offset, int Length)
{
this.Memory = new ReadOnlyMemory(Buffer, Offset, Length);
}
///
/// Tests two strings for equality
///
/// The first string to compare
/// The second string to compare
/// True if the strings are equal
public static bool operator ==(ReadOnlyUtf8String A, ReadOnlyUtf8String B)
{
return A.Equals(B);
}
///
/// Tests two strings for inequality
///
/// The first string to compare
/// The second string to compare
/// True if the strings are not equal
public static bool operator !=(ReadOnlyUtf8String A, ReadOnlyUtf8String B)
{
return !A.Equals(B);
}
///
/// Tests whether two strings are equal
///
/// The other string to compare to
/// True if the strings are equal
public bool Equals(ReadOnlyUtf8String Other)
{
return Span.SequenceEqual(Other.Span);
}
///
/// Tests if this string starts with another string
///
/// The string to check against
/// True if this string starts with the other string
public bool StartsWith(ReadOnlyUtf8String Other)
{
return Span.StartsWith(Other.Span);
}
///
/// Tests if this string ends with another string
///
/// The string to check against
/// True if this string ends with the other string
public bool EndsWith(ReadOnlyUtf8String Other)
{
return Span.EndsWith(Other.Span);
}
///
/// Slices this string at the given start position
///
/// Start position
/// String starting at the given position
public ReadOnlyUtf8String Slice(int Start)
{
return new ReadOnlyUtf8String(Memory.Slice(Start));
}
///
/// Slices this string at the given start position and length
///
/// Start position
/// String starting at the given position
public ReadOnlyUtf8String Slice(int Start, int Count)
{
return new ReadOnlyUtf8String(Memory.Slice(Start, Count));
}
///
/// Tests if this string is equal to the other object
///
/// Object to compare to
/// True if the objects are equivalent
public override bool Equals(object? obj)
{
ReadOnlyUtf8String? Other = obj as ReadOnlyUtf8String?;
return Other != null && Equals(Other.Value);
}
///
/// Returns the hash code of this string
///
/// Hash code for the string
public override int GetHashCode()
{
int Hash = 5381;
for(int Idx = 0; Idx < Memory.Length; Idx++)
{
Hash += (Hash << 5) + Span[Idx];
}
return Hash;
}
///
/// Gets the string represented by this data
///
/// The utf-8 string
public override string ToString()
{
return Encoding.UTF8.GetString(Span);
}
///
/// Converts a string to a utf-8 string
///
/// Text to convert
public static implicit operator ReadOnlyUtf8String(string Text)
{
return new ReadOnlyUtf8String(new ReadOnlyMemory(Encoding.UTF8.GetBytes(Text)));
}
}
///
/// Comparison classes for utf8 strings
///
public abstract class ReadOnlyUtf8StringComparer : IEqualityComparer, IComparer
{
///
/// Ordinal comparer for utf8 strings
///
class OrdinalComparer : ReadOnlyUtf8StringComparer
{
///
public override bool Equals(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB)
{
return StrA.Equals(StrB);
}
///
public override int GetHashCode(ReadOnlyUtf8String String)
{
return String.GetHashCode();
}
public override int Compare(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB)
{
return StrA.Span.SequenceCompareTo(StrB.Span);
}
}
///
/// Comparison between ReadOnlyUtf8String objects that ignores case for ASCII characters
///
class OrdinalIgnoreCaseComparer : ReadOnlyUtf8StringComparer
{
///
public override bool Equals(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB)
{
if (StrA.Length != StrB.Length)
{
return false;
}
for (int Idx = 0; Idx < StrA.Length; Idx++)
{
if (StrA[Idx] != StrB[Idx] && ToUpper(StrA[Idx]) != ToUpper(StrB[Idx]))
{
return false;
}
}
return true;
}
///
public override int GetHashCode(ReadOnlyUtf8String String)
{
HashCode HashCode = new HashCode();
for (int Idx = 0; Idx < String.Length; Idx++)
{
HashCode.Add(ToUpper(String[Idx]));
}
return HashCode.ToHashCode();
}
///
public override int Compare(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB)
{
ReadOnlySpan SpanA = StrA.Span;
ReadOnlySpan SpanB = StrB.Span;
int Length = Math.Min(StrA.Length, StrB.Length);
for (int Idx = 0; Idx < Length; Idx++)
{
if (SpanA[Idx] != SpanB[Idx])
{
int UpperA = ToUpper(SpanA[Idx]);
int UpperB = ToUpper(SpanB[Idx]);
if (UpperA != UpperB)
{
return UpperA - UpperB;
}
}
}
return StrA.Length - StrB.Length;
}
///
/// Convert a character to uppercase
///
/// Character to convert
/// The uppercase version of the character
static byte ToUpper(byte Character)
{
return (Character >= 'a' && Character <= 'z') ? (byte)(Character - 'a' + 'A') : Character;
}
}
///
/// Static instance of the ordinal utf8 ordinal comparer
///
public static ReadOnlyUtf8StringComparer Ordinal = new OrdinalComparer();
///
/// Static instance of the case-insensitive utf8 ordinal string comparer
///
public static ReadOnlyUtf8StringComparer OrdinalIgnoreCase = new OrdinalIgnoreCaseComparer();
///
public abstract bool Equals(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB);
///
public abstract int GetHashCode(ReadOnlyUtf8String String);
///
public abstract int Compare(ReadOnlyUtf8String StrA, ReadOnlyUtf8String StrB);
}
///
/// Extension methods for ReadOnlyUtf8String objects
///
public static class MemoryWriterExtensions
{
///
/// Reads a null-terminated utf8 string from the buffer
///
/// The string data
public static ReadOnlyUtf8String ReadString(this MemoryReader Reader)
{
ReadOnlySpan Span = Reader.Span;
int Length = Span.IndexOf((byte)0);
ReadOnlyUtf8String Value = new ReadOnlyUtf8String(Reader.ReadFixedLengthBytes(Length));
Reader.ReadInt8();
return Value;
}
///
/// Writes a UTF8 string into memory with a null terminator
///
/// The memory writer to serialize to
/// String to write
public static void WriteString(this MemoryWriter Writer, ReadOnlyUtf8String String)
{
Writer.WriteFixedLengthBytes(String.Span);
Writer.WriteInt8(0);
}
///
/// Determines the size of a serialized utf-8 string
///
/// The string to measure
/// Size of the serialized string
public static int GetSerializedSize(this ReadOnlyUtf8String String)
{
return String.Length + 1;
}
}
}