268 lines
10 KiB
C#
268 lines
10 KiB
C#
using QFSW.QC.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace QFSW.QC
|
|
{
|
|
/// <summary>
|
|
/// Contains the full data about a command and provides an execution point for invoking the command.
|
|
/// </summary>
|
|
public class CommandData
|
|
{
|
|
public readonly string CommandName;
|
|
public readonly string CommandDescription;
|
|
public readonly string CommandSignature;
|
|
public readonly string ParameterSignature;
|
|
public readonly string GenericSignature;
|
|
|
|
public readonly ParameterInfo[] MethodParamData;
|
|
public readonly Type[] ParamTypes;
|
|
public readonly Type[] GenericParamTypes;
|
|
public readonly MethodInfo MethodData;
|
|
public readonly MonoTargetType MonoTarget;
|
|
|
|
private readonly object[] _defaultParameters;
|
|
|
|
public bool IsGeneric => GenericParamTypes.Length > 0;
|
|
public bool IsStatic => MethodData.IsStatic;
|
|
public bool HasDescription => !string.IsNullOrWhiteSpace(CommandDescription);
|
|
public int ParamCount => ParamTypes.Length - _defaultParameters.Length;
|
|
|
|
public Type[] MakeGenericArguments(params Type[] genericTypeArguments)
|
|
{
|
|
if (genericTypeArguments.Length != GenericParamTypes.Length)
|
|
{
|
|
throw new ArgumentException("Incorrect number of generic substitution types were supplied.");
|
|
}
|
|
|
|
Dictionary<string, Type> substitutionTable = new Dictionary<string, Type>();
|
|
for (int i = 0; i < genericTypeArguments.Length; i++)
|
|
{
|
|
substitutionTable.Add(GenericParamTypes[i].Name, genericTypeArguments[i]);
|
|
}
|
|
|
|
Type[] types = new Type[ParamTypes.Length];
|
|
for (int i = 0; i < types.Length; i++)
|
|
{
|
|
if (ParamTypes[i].ContainsGenericParameters)
|
|
{
|
|
Type substitution = ConstructGenericType(ParamTypes[i], substitutionTable);
|
|
types[i] = substitution;
|
|
}
|
|
else
|
|
{
|
|
types[i] = ParamTypes[i];
|
|
}
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
private Type ConstructGenericType(Type genericType, Dictionary<string, Type> substitutionTable)
|
|
{
|
|
if (!genericType.ContainsGenericParameters) { return genericType; }
|
|
if (substitutionTable.ContainsKey(genericType.Name)) { return substitutionTable[genericType.Name]; }
|
|
if (genericType.IsArray) { return ConstructGenericType(genericType.GetElementType(), substitutionTable).MakeArrayType(); }
|
|
if (genericType.IsGenericType)
|
|
{
|
|
Type baseType = genericType.GetGenericTypeDefinition();
|
|
Type[] typeArguments = genericType.GetGenericArguments();
|
|
for (int i = 0; i < typeArguments.Length; i++)
|
|
{
|
|
typeArguments[i] = ConstructGenericType(typeArguments[i], substitutionTable);
|
|
}
|
|
|
|
return baseType.MakeGenericType(typeArguments);
|
|
}
|
|
|
|
throw new ArgumentException($"Could not construct the generic type {genericType}");
|
|
}
|
|
|
|
public object Invoke(object[] paramData, Type[] genericTypeArguments)
|
|
{
|
|
object[] data = new object[paramData.Length + _defaultParameters.Length];
|
|
Array.Copy(paramData, 0, data, 0, paramData.Length);
|
|
Array.Copy(_defaultParameters, 0, data, paramData.Length, _defaultParameters.Length);
|
|
|
|
MethodInfo invokingMethod = GetInvokingMethod(genericTypeArguments);
|
|
|
|
if (IsStatic)
|
|
{
|
|
return invokingMethod.Invoke(null, data);
|
|
}
|
|
|
|
IEnumerable<object> targets = InvocationTargetFactory.FindTargets(invokingMethod.DeclaringType, MonoTarget);
|
|
return InvocationTargetFactory.InvokeOnTargets(invokingMethod, targets, data);
|
|
}
|
|
|
|
private MethodInfo GetInvokingMethod(Type[] genericTypeArguments)
|
|
{
|
|
if (!IsGeneric)
|
|
{
|
|
return MethodData;
|
|
}
|
|
|
|
T WrapConstruction<T>(Func<T> f)
|
|
{
|
|
try
|
|
{
|
|
return f();
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
throw new ArgumentException($"Supplied generic parameters did not satisfy the generic constraints imposed by '{CommandName}'");
|
|
}
|
|
}
|
|
|
|
Type declaringType = MethodData.DeclaringType;
|
|
MethodInfo method = MethodData;
|
|
|
|
if (declaringType.IsGenericTypeDefinition)
|
|
{
|
|
int typeCount = declaringType.GetGenericArguments().Length;
|
|
|
|
Type[] genericTypes = genericTypeArguments
|
|
.Take(typeCount)
|
|
.ToArray();
|
|
|
|
genericTypeArguments = genericTypeArguments
|
|
.Skip(typeCount)
|
|
.ToArray();
|
|
|
|
declaringType = WrapConstruction(() => declaringType.MakeGenericType(genericTypes));
|
|
method = method.RebaseMethod(declaringType);
|
|
}
|
|
|
|
return genericTypeArguments.Length == 0
|
|
? method
|
|
: WrapConstruction(() => method.MakeGenericMethod(genericTypeArguments));
|
|
}
|
|
|
|
private string BuildPrefix(Type declaringType)
|
|
{
|
|
List<string> prefixes = new List<string>();
|
|
Assembly assembly = declaringType.Assembly;
|
|
|
|
void AddPrefixes(IEnumerable<CommandPrefixAttribute> prefixAttributes, string defaultName)
|
|
{
|
|
foreach (CommandPrefixAttribute prefixAttribute in prefixAttributes.Reverse())
|
|
{
|
|
if (prefixAttribute.Valid)
|
|
{
|
|
string prefix = prefixAttribute.Prefix;
|
|
if (string.IsNullOrWhiteSpace(prefix)) { prefix = defaultName; }
|
|
|
|
prefixes.Add(prefix);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (declaringType != null)
|
|
{
|
|
IEnumerable<CommandPrefixAttribute> typePrefixes = declaringType.GetCustomAttributes<CommandPrefixAttribute>();
|
|
AddPrefixes(typePrefixes, declaringType.Name);
|
|
|
|
declaringType = declaringType.DeclaringType;
|
|
}
|
|
|
|
IEnumerable<CommandPrefixAttribute> assemblyPrefixes = assembly.GetCustomAttributes<CommandPrefixAttribute>();
|
|
AddPrefixes(assemblyPrefixes, assembly.GetName().Name);
|
|
|
|
return string.Join("", prefixes.Reversed());
|
|
}
|
|
|
|
private string BuildGenericSignature(Type[] genericParamTypes)
|
|
{
|
|
if (genericParamTypes.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
IEnumerable<string> names = genericParamTypes.Select(x => x.Name);
|
|
return $"<{string.Join(", ", names)}>";
|
|
}
|
|
|
|
private string BuildParameterSignature(ParameterInfo[] methodParams, int defaultParameterCount)
|
|
{
|
|
string signature = string.Empty;
|
|
for (int i = 0; i < methodParams.Length - defaultParameterCount; i++)
|
|
{
|
|
signature += $"{(i == 0 ? string.Empty : " ")}{methodParams[i].Name}";
|
|
}
|
|
|
|
return signature;
|
|
}
|
|
|
|
private Type[] BuildGenericParamTypes(MethodInfo method, Type declaringType)
|
|
{
|
|
List<Type> types = new List<Type>();
|
|
|
|
if (declaringType.IsGenericTypeDefinition)
|
|
{
|
|
types.AddRange(declaringType.GetGenericArguments());
|
|
}
|
|
|
|
if (method.IsGenericMethodDefinition)
|
|
{
|
|
types.AddRange(method.GetGenericArguments());
|
|
}
|
|
|
|
return types.ToArray();
|
|
}
|
|
|
|
public CommandData(MethodInfo methodData, int defaultParameterCount = 0) : this(methodData, methodData.Name, defaultParameterCount) { }
|
|
public CommandData(MethodInfo methodData, string commandName, int defaultParameterCount = 0)
|
|
{
|
|
CommandName = commandName;
|
|
MethodData = methodData;
|
|
|
|
if (string.IsNullOrWhiteSpace(commandName))
|
|
{
|
|
CommandName = methodData.Name;
|
|
}
|
|
|
|
Type declaringType = methodData.DeclaringType;
|
|
|
|
string prefix = BuildPrefix(declaringType);
|
|
CommandName = $"{prefix}{CommandName}";
|
|
|
|
MethodParamData = methodData.GetParameters();
|
|
ParamTypes = MethodParamData
|
|
.Select(x => x.ParameterType)
|
|
.ToArray();
|
|
|
|
_defaultParameters = new object[defaultParameterCount];
|
|
for (int i = 0; i < defaultParameterCount; i++)
|
|
{
|
|
int j = MethodParamData.Length - defaultParameterCount + i;
|
|
_defaultParameters[i] = MethodParamData[j].DefaultValue;
|
|
}
|
|
|
|
GenericParamTypes = BuildGenericParamTypes(methodData, declaringType);
|
|
|
|
ParameterSignature = BuildParameterSignature(MethodParamData, defaultParameterCount);
|
|
GenericSignature = BuildGenericSignature(GenericParamTypes);
|
|
CommandSignature = ParamCount > 0
|
|
? $"{CommandName}{GenericSignature} {ParameterSignature}"
|
|
: $"{CommandName}{GenericSignature}";
|
|
}
|
|
|
|
public CommandData(MethodInfo methodData, CommandAttribute commandAttribute, int defaultParameterCount = 0) : this(methodData, commandAttribute.Alias, defaultParameterCount)
|
|
{
|
|
CommandDescription = commandAttribute.Description;
|
|
MonoTarget = commandAttribute.MonoTarget;
|
|
}
|
|
|
|
public CommandData(MethodInfo methodData, CommandAttribute commandAttribute, CommandDescriptionAttribute descriptionAttribute, int defaultParameterCount = 0)
|
|
: this(methodData, commandAttribute, defaultParameterCount)
|
|
{
|
|
if ((descriptionAttribute?.Valid ?? false) && string.IsNullOrWhiteSpace(commandAttribute.Description))
|
|
{
|
|
CommandDescription = descriptionAttribute.Description;
|
|
}
|
|
}
|
|
}
|
|
}
|