// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Localisation; using osu.Framework.Platform; using osuTK; // this is an abusive thing to do, but it increases the visibility of Extension Methods to virtually every file. namespace osu.Framework.Extensions { /// /// This class holds extension methods for various purposes and should not be used explicitly, ever. /// public static class ExtensionMethods { /// /// Adds the given item to the list according to standard sorting rules. Do not use on unsorted lists. /// /// The list to take values /// The item that should be added. /// The index in the list where the item was inserted. public static int AddInPlace(this List list, T item) { int index = list.BinarySearch(item); if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. list.Insert(index, item); return index; } /// /// Adds the given item to the list according to the comparers sorting rules. Do not use on unsorted lists. /// /// The list to take values /// The item that should be added. /// The comparer that should be used for sorting. /// The index in the list where the item was inserted. public static int AddInPlace(this List list, T item, IComparer comparer) { int index = list.BinarySearch(item, comparer); if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. list.Insert(index, item); return index; } /// /// Try to get a value from the . Returns a default(TValue) if the key does not exist. /// /// The dictionary. /// The lookup key. [Obsolete("Use System.Collections.Generic.CollectionExtensions.GetValueOrDefault instead.")] // Can be removed 20220115 public static TValue GetOrDefault(this Dictionary dictionary, TKey lookup) => dictionary.GetValueOrDefault(lookup); /// /// Converts a rectangular array to a jagged array. /// /// The jagged array will contain empty arrays if there are no columns in the rectangular array. /// /// /// The rectangular array. /// The jagged array. public static T[][] ToJagged(this T[,] rectangular) { if (rectangular == null) return null; var jagged = new T[rectangular.GetLength(0)][]; for (int r = 0; r < rectangular.GetLength(0); r++) { jagged[r] = new T[rectangular.GetLength(1)]; for (int c = 0; c < rectangular.GetLength(1); c++) jagged[r][c] = rectangular[r, c]; } return jagged; } /// /// Converts a jagged array to a rectangular array. /// /// All elements that did not exist in the original jagged array are initialized to their default values. /// /// /// The jagged array. /// The rectangular array. public static T[,] ToRectangular(this T[][] jagged) { if (jagged == null) return null; var rows = jagged.Length; var cols = rows == 0 ? 0 : jagged.Max(c => c?.Length ?? 0); var rectangular = new T[rows, cols]; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { if (jagged[r] == null) continue; if (c >= jagged[r].Length) continue; rectangular[r, c] = jagged[r][c]; } } return rectangular; } /// /// Inverts the rows and columns of a rectangular array. /// /// The array to invert. /// The inverted array. public static T[,] Invert(this T[,] array) { if (array == null) return null; int rows = array.GetLength(0); int cols = array.GetLength(1); var result = new T[cols, rows]; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) result[c, r] = array[r, c]; } return result; } /// /// Inverts the rows and columns of a jagged array. /// /// The array to invert. /// The inverted array. This is always a square array. public static T[][] Invert(this T[][] array) => array.ToRectangular().Invert().ToJagged(); public static string ToResolutionString(this Size size) => $"{size.Width}x{size.Height}"; public static Type[] GetLoadableTypes(this Assembly assembly) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException e) { // the following warning disables are caused by netstandard2.1 and net5.0 differences // the former declares Types as Type[], while the latter declares as Type?[]: // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.reflectiontypeloadexception.types?view=net-5.0#property-value // which trips some inspectcode errors which are only "valid" for the first of the two. // TODO: remove if netstandard2.1 is removed // ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConstantConditionalAccessQualifier // ReSharper disable once ConstantNullCoalescingCondition return e.Types?.Where(t => t != null).ToArray() ?? Array.Empty(); } } /// /// Returns the localisable description of a given object, via (in order): /// /// /// Any attached . /// /// /// /// /// /// /// /// When the specified in the /// does not match any of the existing members in . /// public static LocalisableString GetLocalisableDescription(this T value) { MemberInfo type; if (value is Enum) type = value.GetType().GetField(value.ToString()); else type = value.GetType(); var attribute = type.GetCustomAttribute(); if (attribute == null) return GetDescription(value); var property = attribute.DeclaringType.GetMember(attribute.Name, BindingFlags.Static | BindingFlags.Public).FirstOrDefault(); switch (property) { case FieldInfo f: return (LocalisableString)f.GetValue(null).AsNonNull(); case PropertyInfo p: return (LocalisableString)p.GetValue(null).AsNonNull(); default: throw new InvalidOperationException($"Member \"{attribute.Name}\" was not found in type {attribute.DeclaringType} (must be a static field or property)"); } } /// /// Returns the description of a given object, via (in order): /// /// /// Any attached . /// /// /// The object's . /// /// /// public static string GetDescription(this object value) => value.GetType() .GetField(value.ToString())? .GetCustomAttribute()?.Description ?? value.ToString(); private static string toLowercaseHex(this byte[] bytes) { // Convert.ToHexString is upper-case, so we are doing this ourselves return string.Create(bytes.Length * 2, bytes, (span, b) => { for (int i = 0; i < b.Length; i++) _ = b[i].TryFormat(span[(i * 2)..], out _, "x2"); }); } /// /// Gets a SHA-2 (256bit) hash for the given stream, seeking the stream before and after. /// /// The stream to create a hash from. /// A lower-case hex string representation of the hash (64 characters). public static string ComputeSHA2Hash(this Stream stream) { string hash; stream.Seek(0, SeekOrigin.Begin); using (var alg = SHA256.Create()) hash = alg.ComputeHash(stream).toLowercaseHex(); stream.Seek(0, SeekOrigin.Begin); return hash; } /// /// Gets a SHA-2 (256bit) hash for the given string. /// /// The string to create a hash from. /// A lower-case hex string representation of the hash (64 characters). public static string ComputeSHA2Hash(this string str) { using (var alg = SHA256.Create()) return alg.ComputeHash(Encoding.UTF8.GetBytes(str)).toLowercaseHex(); } public static string ComputeMD5Hash(this Stream stream) { string hash; stream.Seek(0, SeekOrigin.Begin); using (var md5 = MD5.Create()) hash = md5.ComputeHash(stream).toLowercaseHex(); stream.Seek(0, SeekOrigin.Begin); return hash; } public static string ComputeMD5Hash(this string input) { using (var md5 = MD5.Create()) return md5.ComputeHash(Encoding.UTF8.GetBytes(input)).toLowercaseHex(); } public static DisplayIndex GetIndex(this DisplayDevice display) { if (display == null) return DisplayIndex.Default; for (int i = 0; true; i++) { var device = DisplayDevice.GetDisplay((DisplayIndex)i); if (device == null) return DisplayIndex.Default; if (device == display) return (DisplayIndex)i; } } /// /// Standardise the path string using '/' as directory separator. /// Useful as output. /// /// The path string to standardise. /// The standardised path string. public static string ToStandardisedPath(this string path) => path.Replace('\\', '/'); /// /// Converts an osuTK to a structure. /// /// The to convert. /// A structure populated with the corresponding properties and s. internal static Display ToDisplay(this DisplayDevice device) => new Display((int)device.GetIndex(), device.GetIndex().ToString(), device.Bounds, device.AvailableResolutions.Select(ToDisplayMode).ToArray()); /// /// Converts an osuTK to a structure. /// It is not possible to retrieve the pixel format from . /// /// The to convert. /// A structure populated with the corresponding properties. internal static DisplayMode ToDisplayMode(this DisplayResolution resolution) => new DisplayMode(null, new Size(resolution.Width, resolution.Height), resolution.BitsPerPixel, (int)Math.Round(resolution.RefreshRate), 0, 0); } }