// 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; } } }