using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
namespace Kyoo.Utils
{
///
/// A set of utility functions that can be used everywhere.
///
public static class Utility
{
///
/// Is the lambda expression a member (like x => x.Body).
///
/// The expression that should be checked
/// True if the expression is a member, false otherwise
public static bool IsPropertyExpression(LambdaExpression ex)
{
if (ex == null)
return false;
return ex.Body is MemberExpression ||
ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression;
}
///
/// Get the name of a property. Useful for selectors as members ex: Load(x => x.Shows)
///
/// The expression
/// The name of the expression
/// If the expression is not a property, ArgumentException is thrown.
public static string GetPropertyName(LambdaExpression ex)
{
if (!IsPropertyExpression(ex))
throw new ArgumentException($"{ex} is not a property expression.");
MemberExpression member = ex.Body.NodeType == ExpressionType.Convert
? ((UnaryExpression)ex.Body).Operand as MemberExpression
: ex.Body as MemberExpression;
return member!.Member.Name;
}
///
/// Get the value of a member (property or field)
///
/// The member value
/// The owner of this member
/// The value boxed as an object
/// if or is null.
/// The member is not a field or a property.
public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj)
{
if (member == null)
throw new ArgumentNullException(nameof(member));
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return member switch
{
PropertyInfo property => property.GetValue(obj),
FieldInfo field => field.GetValue(obj),
_ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).")
};
}
///
/// Slugify a string (Replace spaces by -, Uniformize accents é -> e)
///
/// The string to slugify
/// The slug version of the given string
public static string ToSlug([CanBeNull] string str)
{
if (str == null)
return null;
str = str.ToLowerInvariant();
string normalizedString = str.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new();
foreach (char c in normalizedString)
{
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
stringBuilder.Append(c);
}
str = stringBuilder.ToString().Normalize(NormalizationForm.FormC);
str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled);
str = Regex.Replace(str, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled);
str = str.Trim('-', '_');
str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled);
return str;
}
///
/// Get the default value of a type.
///
/// The type to get the default value
/// The default value of the given type.
public static object GetClrDefault(this Type type)
{
return type.IsValueType
? Activator.CreateInstance(type)
: null;
}
///
/// Return every in the inheritance tree of the parameter (interfaces are not returned)
///
/// The starting type
/// A list of types
/// can't be null
public static IEnumerable GetInheritanceTree([NotNull] this Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
for (; type != null; type = type.BaseType)
yield return type;
}
///
/// Check if inherit from a generic type .
///
/// Does this object's type is a
/// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).
/// True if obj inherit from genericType. False otherwise
/// obj and genericType can't be null
public static bool IsOfGenericType([NotNull] object obj, [NotNull] Type genericType)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return IsOfGenericType(obj.GetType(), genericType);
}
///
/// Check if inherit from a generic type .
///
/// The type to check
/// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).
/// True if obj inherit from genericType. False otherwise
/// obj and genericType can't be null
public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (genericType == null)
throw new ArgumentNullException(nameof(genericType));
if (!genericType.IsGenericType)
throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
IEnumerable types = genericType.IsInterface
? type.GetInterfaces()
: type.GetInheritanceTree();
return types.Prepend(type)
.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
}
///
/// Get the generic definition of .
/// For example, calling this function with List<string> and typeof(IEnumerable<>) will return IEnumerable<string>
///
/// The type to check
/// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).
/// The generic definition of genericType that type inherit or null if type does not implement the generic type.
/// and can't be null
/// must be a generic type
public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (genericType == null)
throw new ArgumentNullException(nameof(genericType));
if (!genericType.IsGenericType)
throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
IEnumerable types = genericType.IsInterface
? type.GetInterfaces()
: type.GetInheritanceTree();
return types.Prepend(type)
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
}
///
/// Retrieve a method from an with the given name and respect the
/// amount of parameters and generic parameters. This works for polymorphic methods.
///
///
/// The type owning the method. For non static methods, this is the this.
///
///
/// The binding flags of the method. This allow you to specify public/private and so on.
///
///
/// The name of the method.
///
///
/// The list of generic parameters.
///
///
/// The list of parameters.
///
/// No method match the given constraints.
/// The method handle of the matching method.
[PublicAPI]
[NotNull]
public static MethodInfo GetMethod([NotNull] Type type,
BindingFlags flag,
string name,
[NotNull] Type[] generics,
[NotNull] object[] args)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (generics == null)
throw new ArgumentNullException(nameof(generics));
if (args == null)
throw new ArgumentNullException(nameof(args));
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public)
.Where(x => x.Name == name)
.Where(x => x.GetGenericArguments().Length == generics.Length)
.Where(x => x.GetParameters().Length == args.Length)
.IfEmpty(() => throw new ArgumentException($"A method named {name} with " +
$"{args.Length} arguments and {generics.Length} generic " +
$"types could not be found on {type.Name}."))
// TODO this won't work but I don't know why.
// .Where(x =>
// {
// int i = 0;
// return x.GetGenericArguments().All(y => y.IsAssignableFrom(generics[i++]));
// })
// .IfEmpty(() => throw new NullReferenceException($"No method {name} match the generics specified."))
// TODO this won't work for Type because T is specified in arguments but not in the parameters type.
// .Where(x =>
// {
// int i = 0;
// return x.GetParameters().All(y => y.ParameterType.IsInstanceOfType(args[i++]));
// })
// .IfEmpty(() => throw new NullReferenceException($"No method {name} match the parameters's types."))
.Take(2)
.ToArray();
if (methods.Length == 1)
return methods[0];
throw new ArgumentException($"Multiple methods named {name} match the generics and parameters constraints.");
}
///
/// Run a generic static method for a runtime .
///
///
/// To run for a List where you don't know the type at compile type,
/// you could do:
///
/// Utility.RunGenericMethod<object>(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
///
///
/// The type that owns the method. For non static methods, the type of this.
/// The name of the method. You should use the nameof keyword.
/// The generic type to run the method with.
/// The list of arguments of the method
///
/// The return type of the method. You can put for an unknown one.
///
/// No method match the given constraints.
/// The return of the method you wanted to run.
///
///
public static T RunGenericMethod(
[NotNull] Type owner,
[NotNull] string methodName,
[NotNull] Type type,
params object[] args)
{
return RunGenericMethod(owner, methodName, new[] {type}, args);
}
///
/// Run a generic static method for a multiple runtime .
/// If your generic method only needs one type, see
///
///
///
/// To run for a List where you don't know the type at compile type,
/// you could do:
///
/// Utility.RunGenericMethod<object>(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
///
///
/// The type that owns the method. For non static methods, the type of this.
/// The name of the method. You should use the nameof keyword.
/// The list of generic types to run the method with.
/// The list of arguments of the method
///
/// The return type of the method. You can put for an unknown one.
///
/// No method match the given constraints.
/// The return of the method you wanted to run.
///
///
[PublicAPI]
public static T RunGenericMethod(
[NotNull] Type owner,
[NotNull] string methodName,
[NotNull] Type[] types,
params object[] args)
{
if (owner == null)
throw new ArgumentNullException(nameof(owner));
if (methodName == null)
throw new ArgumentNullException(nameof(methodName));
if (types == null)
throw new ArgumentNullException(nameof(types));
if (types.Length < 1)
throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed.");
MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args);
return (T)method.MakeGenericMethod(types).Invoke(null, args);
}
///
/// Run a generic method for a runtime .
///
///
/// To run for a List where you don't know the type at compile type,
/// you could do:
///
/// Utility.RunGenericMethod<object>(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
///
///
/// The this of the method to run.
/// The name of the method. You should use the nameof keyword.
/// The generic type to run the method with.
/// The list of arguments of the method
///
/// The return type of the method. You can put for an unknown one.
///
/// No method match the given constraints.
/// The return of the method you wanted to run.
///
///
public static T RunGenericMethod(
[NotNull] object instance,
[NotNull] string methodName,
[NotNull] Type type,
params object[] args)
{
return RunGenericMethod(instance, methodName, new[] {type}, args);
}
///
/// Run a generic method for a multiple runtime .
/// If your generic method only needs one type, see
///
///
///
/// To run for a List where you don't know the type at compile type,
/// you could do:
///
/// Utility.RunGenericMethod<object>(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
///
///
/// The this of the method to run.
/// The name of the method. You should use the nameof keyword.
/// The list of generic types to run the method with.
/// The list of arguments of the method
///
/// The return type of the method. You can put for an unknown one.
///
/// No method match the given constraints.
/// The return of the method you wanted to run.
///
///
public static T RunGenericMethod(
[NotNull] object instance,
[NotNull] string methodName,
[NotNull] Type[] types,
params object[] args)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
if (methodName == null)
throw new ArgumentNullException(nameof(methodName));
if (types == null || types.Length == 0)
throw new ArgumentNullException(nameof(types));
MethodInfo method = GetMethod(instance.GetType(), BindingFlags.Instance, methodName, types, args);
return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray());
}
public static string ToQueryString(this Dictionary query)
{
if (!query.Any())
return string.Empty;
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
}
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
public static void ReThrow([NotNull] this Exception ex)
{
if (ex == null)
throw new ArgumentNullException(nameof(ex));
ExceptionDispatchInfo.Capture(ex).Throw();
}
///
/// Get a friendly type name (supporting generics)
/// For example a list of string will be displayed as List<string> and not as List`1.
///
/// The type to use
/// The friendly name of the type
public static string FriendlyName(this Type type)
{
if (!type.IsGenericType)
return type.Name;
string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName()));
return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>";
}
}
}