// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; namespace dnlib.DotNet { /// /// Compares s /// public sealed class UTF8StringEqualityComparer : IEqualityComparer { /// /// The default instance /// public static readonly UTF8StringEqualityComparer Instance = new UTF8StringEqualityComparer(); /// public bool Equals(UTF8String x, UTF8String y) => UTF8String.Equals(x, y); /// public int GetHashCode(UTF8String obj) => UTF8String.GetHashCode(obj); } /// /// A UTF-8 encoded string where the original data is kept in memory to avoid conversions /// when the data is not really valid UTF-8 encoded data /// /// When comparing strings, a byte compare is performed. The reason is that this /// is what the CLR does when comparing strings in the #Strings stream. [DebuggerDisplay("{String}")] public sealed class UTF8String : IEquatable, IComparable { /// /// An empty /// public static readonly UTF8String Empty = new UTF8String(string.Empty); readonly byte[] data; string asString; /// /// Gets the value as a UTF8 decoded string. Only use it for display purposes, /// not for serialization. /// public string String { get { if (asString is null) asString = ConvertFromUTF8(data); return asString; } } /// /// Gets the original encoded data. Don't modify this data. /// public byte[] Data => data; /// /// Gets the length of the this as a . I.e., it's the same as /// String.Length. /// /// public int Length => String.Length; /// /// Gets the length of the raw data. It's the same as Data.Length /// /// public int DataLength => data is null ? 0 : data.Length; /// /// Checks whether is null or if its data is null. /// /// The instance to check /// true if null or empty, false otherwise public static bool IsNull(UTF8String utf8) => utf8 is null || utf8.data is null; /// /// Checks whether is null or if its data is null or the /// data is zero length. /// /// The instance to check /// true if null or empty, false otherwise public static bool IsNullOrEmpty(UTF8String utf8) => utf8 is null || utf8.data is null || utf8.data.Length == 0; /// Implicit conversion from to public static implicit operator string(UTF8String s) => UTF8String.ToSystemString(s); /// Implicit conversion from to public static implicit operator UTF8String(string s) => s is null ? null : new UTF8String(s); /// /// Converts it to a /// /// The UTF-8 string instace or null /// A or null if is null public static string ToSystemString(UTF8String utf8) { if (utf8 is null || utf8.data is null) return null; if (utf8.data.Length == 0) return string.Empty; return utf8.String; } /// /// Converts it to a or an empty string if is null /// /// The UTF-8 string instace or null /// A (never null) public static string ToSystemStringOrEmpty(UTF8String utf8) => ToSystemString(utf8) ?? string.Empty; /// /// Gets the hash code of a /// /// Input public static int GetHashCode(UTF8String utf8) { if (IsNullOrEmpty(utf8)) return 0; return Utils.GetHashCode(utf8.data); } /// public int CompareTo(UTF8String other) => CompareTo(this, other); /// /// Compares two instances (case sensitive) /// /// Instance #1 or null /// Instance #2 or null /// < 0 if a < b, 0 if a == b, > 0 if a > b public static int CompareTo(UTF8String a, UTF8String b) => Utils.CompareTo(a?.data, b?.data); /// /// Compares two instances (case insensitive) /// /// Instance #1 or null /// Instance #2 or null /// < 0 if a < b, 0 if a == b, > 0 if a > b public static int CaseInsensitiveCompareTo(UTF8String a, UTF8String b) { if ((object)a == (object)b) return 0; var sa = ToSystemString(a); var sb = ToSystemString(b); if ((object)sa == (object)sb) return 0; if (sa is null) return -1; if (sb is null) return 1; return StringComparer.OrdinalIgnoreCase.Compare(sa, sb); } /// /// Compares two instances (case insensitive) /// /// Instance #1 or null /// Instance #2 or null /// true if equals, false otherwise public static bool CaseInsensitiveEquals(UTF8String a, UTF8String b) => CaseInsensitiveCompareTo(a, b) == 0; /// Overloaded operator public static bool operator ==(UTF8String left, UTF8String right) => CompareTo(left, right) == 0; /// Overloaded operator public static bool operator ==(UTF8String left, string right) => ToSystemString(left) == right; /// Overloaded operator public static bool operator ==(string left, UTF8String right) => left == ToSystemString(right); /// Overloaded operator public static bool operator !=(UTF8String left, UTF8String right) => CompareTo(left, right) != 0; /// Overloaded operator public static bool operator !=(UTF8String left, string right) => ToSystemString(left) != right; /// Overloaded operator public static bool operator !=(string left, UTF8String right) => left != ToSystemString(right); /// Overloaded operator public static bool operator >(UTF8String left, UTF8String right) => CompareTo(left, right) > 0; /// Overloaded operator public static bool operator <(UTF8String left, UTF8String right) => CompareTo(left, right) < 0; /// Overloaded operator public static bool operator >=(UTF8String left, UTF8String right) => CompareTo(left, right) >= 0; /// Overloaded operator public static bool operator <=(UTF8String left, UTF8String right) => CompareTo(left, right) <= 0; /// /// Constructor /// /// UTF-8 data that this instance now owns public UTF8String(byte[] data) => this.data = data; /// /// Constructor /// /// The string public UTF8String(string s) : this(s is null ? null : Encoding.UTF8.GetBytes(s)) { } static string ConvertFromUTF8(byte[] data) { if (data is null) return null; try { return Encoding.UTF8.GetString(data); } catch { } return null; } /// /// Compares two instances /// /// First /// Second /// true if equals, false otherwise public static bool Equals(UTF8String a, UTF8String b) => CompareTo(a, b) == 0; /// public bool Equals(UTF8String other) => CompareTo(this, other) == 0; /// public override bool Equals(object obj) { var other = obj as UTF8String; if (other is null) return false; return CompareTo(this, other) == 0; } /// /// Checks whether exists in this string /// /// Value to find /// true if exists in string or is the /// empty string, else false public bool Contains(string value) => String.Contains(value); /// /// Checks whether matches the end of this string /// /// Value /// public bool EndsWith(string value) => String.EndsWith(value); /// /// Checks whether matches the end of this string /// /// Value /// true to ignore case /// Culture info /// public bool EndsWith(string value, bool ignoreCase, CultureInfo culture) => String.EndsWith(value, ignoreCase, culture); /// /// Checks whether matches the end of this string /// /// Value /// Comparison type /// public bool EndsWith(string value, StringComparison comparisonType) => String.EndsWith(value, comparisonType); /// /// Checks whether matches the beginning of this string /// /// Value /// public bool StartsWith(string value) => String.StartsWith(value); /// /// Checks whether matches the beginning of this string /// /// Value /// true to ignore case /// Culture info /// public bool StartsWith(string value, bool ignoreCase, CultureInfo culture) => String.StartsWith(value, ignoreCase, culture); /// /// Checks whether matches the beginning of this string /// /// Value /// Comparison type /// public bool StartsWith(string value, StringComparison comparisonType) => String.StartsWith(value, comparisonType); /// /// Compares this instance with /// /// Other string /// < 0 if a < b, 0 if a == b, > 0 if a > b public int CompareTo(string strB) => String.CompareTo(strB); /// /// Returns the index of the first character in this string /// /// Character /// The index of or -1 if not found public int IndexOf(char value) => String.IndexOf(value); /// /// Returns the index of the first character in this string /// starting from index /// /// Character /// Start index /// The index of or -1 if not found public int IndexOf(char value, int startIndex) => String.IndexOf(value, startIndex); /// /// Returns the index of the first character in this string /// starting from index for max /// characters. /// /// Character /// Start index /// Max number of chars to scan /// The index of or -1 if not found public int IndexOf(char value, int startIndex, int count) => String.IndexOf(value, startIndex, count); /// /// Returns the index of the first sub string in this string /// /// String /// The index of or -1 if not found public int IndexOf(string value) => String.IndexOf(value); /// /// Returns the index of the first sub string in this string /// starting from index /// /// String /// Start index /// The index of or -1 if not found public int IndexOf(string value, int startIndex) => String.IndexOf(value, startIndex); /// /// Returns the index of the first sub string in this string /// starting from index for max /// characters. /// /// String /// Start index /// Max number of chars to scan /// The index of or -1 if not found public int IndexOf(string value, int startIndex, int count) => String.IndexOf(value, startIndex, count); /// /// Returns the index of the first sub string in this string /// starting from index for max /// characters. /// /// String /// Start index /// Max number of chars to scan /// Comparison type /// The index of or -1 if not found public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType) => String.IndexOf(value, startIndex, count, comparisonType); /// /// Returns the index of the first sub string in this string /// starting from index /// /// String /// Start index /// Comparison type /// The index of or -1 if not found public int IndexOf(string value, int startIndex, StringComparison comparisonType) => String.IndexOf(value, startIndex, comparisonType); /// /// Returns the index of the first sub string in this string /// /// String /// Comparison type /// The index of or -1 if not found public int IndexOf(string value, StringComparison comparisonType) => String.IndexOf(value, comparisonType); /// /// Returns the index of the last character in this string /// /// Character /// The index of or -1 if not found public int LastIndexOf(char value) => String.LastIndexOf(value); /// /// Returns the index of the last character in this string /// starting from index /// /// Character /// Start index /// The index of or -1 if not found public int LastIndexOf(char value, int startIndex) => String.LastIndexOf(value, startIndex); /// /// Returns the index of the last character in this string /// starting from index for max /// characters. /// /// Character /// Start index /// Max number of chars to scan /// The index of or -1 if not found public int LastIndexOf(char value, int startIndex, int count) => String.LastIndexOf(value, startIndex, count); /// /// Returns the index of the last sub string in this string /// /// String /// The index of or -1 if not found public int LastIndexOf(string value) => String.LastIndexOf(value); /// /// Returns the index of the last sub string in this string /// starting from index /// /// String /// Start index /// The index of or -1 if not found public int LastIndexOf(string value, int startIndex) => String.LastIndexOf(value, startIndex); /// /// Returns the index of the last sub string in this string /// starting from index for max /// characters. /// /// String /// Start index /// Max number of chars to scan /// The index of or -1 if not found public int LastIndexOf(string value, int startIndex, int count) => String.LastIndexOf(value, startIndex, count); /// /// Returns the index of the last sub string in this string /// starting from index for max /// characters. /// /// String /// Start index /// Max number of chars to scan /// Comparison type /// The index of or -1 if not found public int LastIndexOf(string value, int startIndex, int count, StringComparison comparisonType) => String.LastIndexOf(value, startIndex, count, comparisonType); /// /// Returns the index of the last sub string in this string /// starting from index /// /// String /// Start index /// Comparison type /// The index of or -1 if not found public int LastIndexOf(string value, int startIndex, StringComparison comparisonType) => String.LastIndexOf(value, startIndex, comparisonType); /// /// Returns the index of the last sub string in this string /// /// String /// Comparison type /// The index of or -1 if not found public int LastIndexOf(string value, StringComparison comparisonType) => String.LastIndexOf(value, comparisonType); /// /// Inserts string at a index /// /// Start index /// Value to insert /// A new instance with the inserted at position /// public UTF8String Insert(int startIndex, string value) => new UTF8String(String.Insert(startIndex, value)); /// /// Removes all characters starting from position /// /// Start index /// A new instance public UTF8String Remove(int startIndex) => new UTF8String(String.Remove(startIndex)); /// /// Removes characters starting from position /// /// /// Start index /// Number of characters to remove /// A new instance public UTF8String Remove(int startIndex, int count) => new UTF8String(String.Remove(startIndex, count)); /// /// Replaces all characters with /// /// Character to find /// Character to replace all /// A new instance public UTF8String Replace(char oldChar, char newChar) => new UTF8String(String.Replace(oldChar, newChar)); /// /// Replaces all sub strings with /// /// Sub string to find /// Sub string to replace all /// A new instance public UTF8String Replace(string oldValue, string newValue) => new UTF8String(String.Replace(oldValue, newValue)); /// /// Returns a sub string of this string starting at offset /// /// Start index /// A new instance public UTF8String Substring(int startIndex) => new UTF8String(String.Substring(startIndex)); /// /// Returns a sub string of this string starting at offset . /// Length of sub string is . /// /// Start index /// Length of sub string /// A new instance public UTF8String Substring(int startIndex, int length) => new UTF8String(String.Substring(startIndex, length)); /// /// Returns the lower case version of this string /// /// A new instance public UTF8String ToLower() => new UTF8String(String.ToLower()); /// /// Returns the lower case version of this string /// /// Culture info /// A new instance public UTF8String ToLower(CultureInfo culture) => new UTF8String(String.ToLower(culture)); /// /// Returns the lower case version of this string using the invariant culture /// /// A new instance public UTF8String ToLowerInvariant() => new UTF8String(String.ToLowerInvariant()); /// /// Returns the upper case version of this string /// /// A new instance public UTF8String ToUpper() => new UTF8String(String.ToUpper()); /// /// Returns the upper case version of this string /// /// Culture info /// A new instance public UTF8String ToUpper(CultureInfo culture) => new UTF8String(String.ToUpper(culture)); /// /// Returns the upper case version of this string using the invariant culture /// /// A new instance public UTF8String ToUpperInvariant() => new UTF8String(String.ToUpperInvariant()); /// /// Removes all leading and trailing whitespace characters /// /// A new instance public UTF8String Trim() => new UTF8String(String.Trim()); /// public override int GetHashCode() => UTF8String.GetHashCode(this); /// public override string ToString() => String; } }