using Amazon.JSII.JsonModel.Api;
using Amazon.JSII.JsonModel.Api.Response;
using Amazon.JSII.JsonModel.Spec;
using Amazon.JSII.Runtime.Services;
using Amazon.JSII.Runtime.Services.Converters;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Authentication.ExtendedProtection;
using Newtonsoft.Json;
using Type = System.Type;
namespace Amazon.JSII.Runtime.Deputy
{
public abstract class DeputyBase : IConvertible
{
///
/// Each deputy type needs a protected constructor that accepts 'create' parameters from
/// children. But some types will also have a public constructor that takes a single object[]
/// argument. So for the protected constructor, we pass DeputyProps instead of object[] to
/// prevent overload ambiguity.
///
protected sealed class DeputyProps
{
public DeputyProps(object?[]? arguments = null)
{
Arguments = arguments ?? Array.Empty();
}
public object?[] Arguments { get; }
}
private const BindingFlags StaticMemberFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags InstanceMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
protected DeputyBase(DeputyProps? props = null)
{
var type = GetType();
JsiiTypeAttributeBase.Load(type.Assembly);
// If this is a native object, it won't have any jsii metadata.
var attribute = ReflectionUtils.GetClassAttribute(type);
var fullyQualifiedName = attribute?.FullyQualifiedName ?? "Object";
var parameters = attribute?.Parameters ?? Array.Empty();
var serviceProvider = ServiceContainer.ServiceProvider;
var client = serviceProvider.GetRequiredService();
var response = client.Create(
fullyQualifiedName,
ConvertArguments(parameters, props?.Arguments ?? Array.Empty()),
GetOverrides(),
GetInterfaces()
);
Reference = new ByRefValue((response["$jsii.byref"] as string)!);
var referenceMap = serviceProvider.GetRequiredService();
referenceMap.AddNativeReference(Reference, this, true);
Override[] GetOverrides()
{
return GetMethodOverrides().Concat(GetPropertyOverrides()).ToArray();
}
IEnumerable GetMethodOverrides()
{
var typeMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(method => !method.DeclaringType?.Equals(typeof(DeputyBase)) ?? false)
.Where(method => !method.DeclaringType?.Equals(typeof(object)) ?? false);
foreach (var method in typeMethods)
{
var inheritedAttribute = method.GetAttribute();
var uninheritedAttribute = method.GetAttribute(false);
if (inheritedAttribute != null && uninheritedAttribute == null)
{
yield return new Override(method: (inheritedAttribute ?? uninheritedAttribute)!.Name);
}
}
}
IEnumerable GetPropertyOverrides()
{
var typeProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(prop => !prop.DeclaringType?.Equals(typeof(DeputyBase)) ?? false)
.Where(prop => !prop.DeclaringType?.Equals(typeof(object)) ?? false);
foreach (var property in typeProperties)
{
var inheritedAttribute = property.GetAttribute();
var uninheritedAttribute = property.GetAttribute(false);
if (inheritedAttribute != null && uninheritedAttribute == null)
{
yield return new Override(property: (inheritedAttribute ?? uninheritedAttribute)!.Name);
}
}
}
string[] GetInterfaces()
{
return type.GetInterfaces()
.Select(iface => iface.GetCustomAttribute())
.Where(jsiiIface => jsiiIface != null)
.Select(jsiiIface => jsiiIface!.FullyQualifiedName)
.ToArray();
}
}
protected DeputyBase(ByRefValue reference)
{
Reference = reference ?? throw new ArgumentNullException(nameof(reference));
if (reference.IsProxy) return;
var serviceProvider = ServiceContainer.ServiceProvider;
var referenceMap = serviceProvider.GetRequiredService();
referenceMap.AddNativeReference(Reference, this);
}
internal ByRefValue Reference { get; }
#region GetProperty
[return: MaybeNull]
protected static T GetStaticProperty(System.Type type, [CallerMemberName] string? propertyName = null)
{
type = type ?? throw new ArgumentNullException(nameof(type));
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
JsiiTypeAttributeBase.Load(type.Assembly);
var classAttribute = ReflectionUtils.GetClassAttribute(type)!;
var propertyAttribute = GetStaticPropertyAttribute(type, propertyName);
return GetPropertyCore(
propertyAttribute,
client => client.StaticGet(classAttribute.FullyQualifiedName, propertyAttribute.Name)
);
}
[return: MaybeNull]
protected T GetInstanceProperty([CallerMemberName] string? propertyName = null)
{
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
var propertyAttribute = GetInstancePropertyAttribute(propertyName);
return GetPropertyCore(
propertyAttribute,
client => client.Get(Reference.ToObjectReference(), propertyAttribute.Name)
);
}
[return: MaybeNull]
private static T GetPropertyCore(JsiiPropertyAttribute propertyAttribute, Func getFunc)
{
var serviceProvider = ServiceContainer.ServiceProvider;
var client = serviceProvider.GetRequiredService();
var response = getFunc(client);
var converter = serviceProvider.GetRequiredService();
var referenceMap = serviceProvider.GetRequiredService();
if (!converter.TryConvert(propertyAttribute, typeof(T), referenceMap, response.Value, out object? frameworkValue))
{
throw new ArgumentException($"Could not convert value '{response.Value}' for property '{propertyAttribute.Name}'", nameof(getFunc));
}
return (T)frameworkValue!;
}
#endregion
#region SetProperty
protected static void SetStaticProperty(System.Type type, T value, [CallerMemberName] string? propertyName = null)
{
type = type ?? throw new ArgumentNullException(nameof(type));
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
JsiiTypeAttributeBase.Load(type.Assembly);
var classAttribute = ReflectionUtils.GetClassAttribute(type)!;
var propertyAttribute = GetStaticPropertyAttribute(type, propertyName);
SetPropertyCore(
value,
propertyAttribute,
(client, jsiiValue) => client.StaticSet(classAttribute.FullyQualifiedName, propertyAttribute.Name, jsiiValue)
);
}
protected void SetInstanceProperty(T value, [CallerMemberName] string? propertyName = null)
{
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
var propertyAttribute = GetInstancePropertyAttribute(propertyName);
SetPropertyCore(
value,
propertyAttribute,
(client, jsiiValue) => client.Set(Reference.ToObjectReference(), propertyAttribute.Name, jsiiValue)
);
}
private static void SetPropertyCore(T value, JsiiPropertyAttribute propertyAttribute, Action setAction)
{
var serviceProvider = ServiceContainer.ServiceProvider;
var converter = serviceProvider.GetRequiredService();
var referenceMap = serviceProvider.GetRequiredService();
if (!converter.TryConvert(propertyAttribute, referenceMap, value, out object? jsiiValue))
{
throw new ArgumentException($"Could not set property '{propertyAttribute.Name}' to '{value}'", nameof(value));
}
var client = serviceProvider.GetRequiredService();
setAction(client, jsiiValue);
}
#endregion
#region InvokeMethod
protected static void InvokeStaticVoidMethod(System.Type type, System.Type[] parameterTypes, object?[] arguments, [CallerMemberName] string methodName = "")
{
InvokeStaticMethod(type, parameterTypes, arguments, methodName);
}
protected void InvokeInstanceVoidMethod(System.Type[] parameterTypes, object?[] arguments, [CallerMemberName] string methodName = "")
{
InvokeInstanceMethod(parameterTypes, arguments, methodName);
}
[return: MaybeNull]
protected static T InvokeStaticMethod(System.Type type, System.Type[] parameterTypes, object?[] arguments, [CallerMemberName] string methodName = "")
{
JsiiTypeAttributeBase.Load(type.Assembly);
var methodAttribute = GetStaticMethodAttribute(type, methodName, parameterTypes);
var classAttribute = ReflectionUtils.GetClassAttribute(type)!;
return InvokeMethodCore(
methodAttribute,
arguments,
(client, args) => throw new NotSupportedException("Async static methods are currently not supported"),
(client, args) => client.StaticInvoke(
classAttribute.FullyQualifiedName,
methodAttribute.Name,
ConvertArguments(methodAttribute.Parameters, arguments)
)
);
}
[return: MaybeNull]
protected T InvokeInstanceMethod(System.Type[] parameterTypes, object?[] arguments, [CallerMemberName] string methodName = "")
{
var methodAttribute = GetInstanceMethodAttribute(methodName, parameterTypes);
return InvokeMethodCore(
methodAttribute,
arguments,
(client, args) => client.Begin(
Reference.ToObjectReference(),
methodAttribute.Name,
ConvertArguments(methodAttribute.Parameters, arguments)
),
(client, args) => client.Invoke(
Reference.ToObjectReference(),
methodAttribute.Name,
ConvertArguments(methodAttribute.Parameters, arguments)
)
);
}
[return: MaybeNull]
private static T InvokeMethodCore(
JsiiMethodAttribute methodAttribute,
object?[] arguments,
Func beginFunc,
Func invokeFunc
)
{
var serviceProvider = ServiceContainer.ServiceProvider;
var client = serviceProvider.GetRequiredService();
var converter = serviceProvider.GetRequiredService();
var referenceMap = serviceProvider.GetRequiredService();
var result = GetResult();
if (!converter.TryConvert(methodAttribute.Returns, typeof(T), referenceMap, result, out object? frameworkValue))
{
throw new NotSupportedException($"Could not convert result '{result}' for method '{methodAttribute.Name}'");
}
return (T)frameworkValue!;
object? GetResult()
{
var args = ConvertArguments(methodAttribute.Parameters, arguments);
if (methodAttribute.IsAsync)
{
var beginResponse = beginFunc(client, args);
InvokeCallbacks();
return client.End(beginResponse.PromiseId).Result;
}
var invokeResponse = invokeFunc(client, args);
return invokeResponse.Result;
}
}
private static void InvokeCallbacks()
{
var serviceProvider = ServiceContainer.ServiceProvider;
var client = serviceProvider.GetRequiredService();
var converter = serviceProvider.GetRequiredService();
var referenceMap = serviceProvider.GetRequiredService();
var callbacks = client.Callbacks();
while (callbacks.Callbacks.Any())
{
foreach (var callback in callbacks.Callbacks)
{
var result = callback.InvokeCallback(referenceMap, converter, out var error);
var namedError = error is null ? null : new NamedError(error);
client.Complete(callback.CallbackId, namedError, result);
}
callbacks = client.Callbacks();
}
}
#endregion
#region ConvertArguments
private static object?[] ConvertArguments(Parameter[] parameters, params object?[] arguments)
{
var serviceProvider = ServiceContainer.ServiceProvider;
if (parameters.Length == 0 && arguments.Length == 0)
{
return Array.Empty();
}
if (parameters.Length != arguments.Length)
{
throw new ArgumentException(
$"Arguments do not match method parameters (method has {parameters.Length} parameters, {arguments.Length} arguments received)",
nameof(arguments));
}
var cleanedArgs = new List(arguments);
var cleanedParams = new List(parameters);
// Handling variadic parameters (null array, empty array, one value array, n values array..)
if (parameters.Length > 0 && parameters.Last().IsVariadic)
{
// Last parameter is variadic, let's explode the .NET attributes
var variadicValues = arguments.Last() as Array;
// We remove the last argument (the variadic array);
cleanedArgs.RemoveAt(cleanedArgs.Count - 1);
// A null value could be passed as a params
if (variadicValues != null)
{
// We save the last parameter to backfill the parameters list
var lastParameter = cleanedParams.Last();
for (var i = 0; i < variadicValues.Length; i++)
{
// Backfill the arguments
cleanedArgs.Add(variadicValues.GetValue(i));
// Backfill the parameters if necessary, for a 1:1 mirror with the cleanedArgs
if (cleanedArgs.Count != cleanedParams.Count)
cleanedParams.Add(lastParameter);
}
}
}
var converter = serviceProvider.GetRequiredService();
var referenceMap = serviceProvider.GetRequiredService();
return cleanedParams.Zip(cleanedArgs, (parameter, frameworkArgument) =>
{
if (!converter.TryConvert(parameter, referenceMap, frameworkArgument, out object? jsiiArgument))
{
throw new ArgumentException($"Could not convert argument '{frameworkArgument}' to Jsii", nameof(arguments));
}
return jsiiArgument;
}).ToArray();
}
#endregion
#region GetPropertyAttribute
private static JsiiPropertyAttribute GetStaticPropertyAttribute(System.Type type, string propertyName)
{
return GetPropertyAttributeCore(type, propertyName, StaticMemberFlags);
}
private JsiiPropertyAttribute GetInstancePropertyAttribute(string propertyName)
{
return GetPropertyAttributeCore(GetType(), propertyName, InstanceMemberFlags);
}
private static JsiiPropertyAttribute GetPropertyAttributeCore(System.Type type, string propertyName, BindingFlags bindingFlags)
{
type = type ?? throw new ArgumentNullException(nameof(type));
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
var propertyInfo = type.GetProperty(propertyName, bindingFlags);
if (propertyInfo == null)
{
throw new ArgumentException($"Property {propertyName} does not exist", nameof(propertyName));
}
var attribute = propertyInfo.GetCustomAttribute();
if (attribute == null)
{
throw new ArgumentException($"Property {propertyName} is missing JsiiPropertyAttribute", nameof(propertyName));
}
return attribute;
}
#endregion
#region GetMethodAttribute
private static JsiiMethodAttribute GetStaticMethodAttribute(System.Type type, string methodName, System.Type[] parameterTypes)
{
return GetMethodAttributeCore(type, methodName, parameterTypes, StaticMemberFlags);
}
private JsiiMethodAttribute GetInstanceMethodAttribute(string methodName, System.Type[] parameterTypes)
{
return GetMethodAttributeCore(GetType(), methodName, parameterTypes, InstanceMemberFlags);
}
private static JsiiMethodAttribute GetMethodAttributeCore(System.Type type, string methodName, System.Type[] parameterTypes, BindingFlags bindingFlags)
{
methodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
type = type ?? throw new ArgumentNullException(nameof(type));
parameterTypes = parameterTypes ?? throw new ArgumentNullException(nameof(parameterTypes));
var methodInfo = type.GetMethod(methodName, bindingFlags, null, parameterTypes, Array.Empty());
if (methodInfo == null)
{
throw new ArgumentException($"Method {methodName} does not exist", nameof(methodName));
}
var methodAttribute = methodInfo.GetCustomAttribute();
if (methodAttribute == null)
{
throw new ArgumentException($"Method {methodName} is missing JsiiMethodAttribute", nameof(methodName));
}
return methodAttribute;
}
#endregion
#region IConvertible
///
/// Unsafely obtains a proxy of a given type for this instance. This method allows obtaining a proxy instance
/// that is not known to be supported by the backing object instance; in which case the behavior of any
/// operation that is not supported by the backing instance is undefined.
///
///
/// A jsii-managed interface to obtain a proxy for.
/// This interface must carry a attribute.
///
///
/// An instance of T
///
///
/// If the type provided for T does not carry the attribute.
///
public T UnsafeCast() where T: class
{
if (this is T result)
{
return result;
}
try
{
return (T) Convert.ChangeType(this, typeof(T), CultureInfo.InvariantCulture);
}
catch (InvalidCastException)
{
// At this point, we are converting to a type that we don't know for sure is applicable
if (MakeProxy(typeof(T), true, out var proxy))
{
return (T)proxy;
}
throw;
}
}
private IDictionary Proxies { get; } = new Dictionary();
TypeCode IConvertible.GetTypeCode()
{
return TypeCode.Object;
}
object IConvertible.ToType(System.Type conversionType, IFormatProvider? provider)
{
if (Proxies.ContainsKey(conversionType))
{
return Proxies[conversionType];
}
if (ToTypeCore(out var converted))
{
return Proxies[conversionType] = converted!;
}
throw new InvalidCastException($"Unable to cast {this.GetType().FullName} into {conversionType.FullName}");
bool ToTypeCore(out object? result)
{
if (!conversionType.IsInstanceOfType(this)) return MakeProxy(conversionType, false, out result);
result = this;
return true;
}
}
private bool MakeProxy(Type interfaceType, bool force, [NotNullWhen(true)] out object? result)
{
if (!interfaceType.IsInterface)
{
result = null;
return false;
}
var interfaceAttribute = interfaceType.GetCustomAttribute();
if (interfaceAttribute == null)
{
// We can only convert to interfaces decorated with the JsiiInterfaceAttribute
result = null;
return false;
}
var types = ServiceContainer.ServiceProvider.GetRequiredService();
if (!TryFindSupportedInterface(interfaceAttribute.FullyQualifiedName, Reference.Interfaces, types, out var adequateFqn))
{
// We can only convert to interfaces declared by this Reference
result = null;
return false;
}
var proxyType = types.GetProxyType(interfaceAttribute.FullyQualifiedName);
var constructorInfo = proxyType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] {typeof(ByRefValue)},
null
);
if (constructorInfo == null)
{
throw new JsiiError($"Could not find constructor to instantiate {proxyType.FullName}");
}
result = constructorInfo.Invoke(new object[]{ Reference.ForProxy() });
return true;
bool TryFindSupportedInterface(string declaredFqn, string[] availableFqns, ITypeCache types, out string? foundFqn)
{
var declaredType = types.GetInterfaceType(declaredFqn);
foreach (var candidate in availableFqns)
{
var candidateType = types.GetInterfaceType(candidate);
if (!declaredType.IsAssignableFrom(candidateType)) continue;
foundFqn = candidate;
return true;
}
foundFqn = declaredFqn;
return force;
}
}
#region Impossible Conversions
bool IConvertible.ToBoolean(IFormatProvider? provider)
{
throw new InvalidCastException();
}
byte IConvertible.ToByte(IFormatProvider? provider)
{
throw new InvalidCastException();
}
char IConvertible.ToChar(IFormatProvider? provider)
{
throw new InvalidCastException();
}
DateTime IConvertible.ToDateTime(IFormatProvider? provider)
{
throw new InvalidCastException();
}
decimal IConvertible.ToDecimal(IFormatProvider? provider)
{
throw new InvalidCastException();
}
double IConvertible.ToDouble(IFormatProvider? provider)
{
throw new InvalidCastException();
}
short IConvertible.ToInt16(IFormatProvider? provider)
{
throw new InvalidCastException();
}
int IConvertible.ToInt32(IFormatProvider? provider)
{
throw new InvalidCastException();
}
long IConvertible.ToInt64(IFormatProvider? provider)
{
throw new InvalidCastException();
}
sbyte IConvertible.ToSByte(IFormatProvider? provider)
{
throw new InvalidCastException();
}
float IConvertible.ToSingle(IFormatProvider? provider)
{
throw new InvalidCastException();
}
string IConvertible.ToString(IFormatProvider? provider)
{
throw new InvalidCastException();
}
ushort IConvertible.ToUInt16(IFormatProvider? provider)
{
throw new InvalidCastException();
}
uint IConvertible.ToUInt32(IFormatProvider? provider)
{
throw new InvalidCastException();
}
ulong IConvertible.ToUInt64(IFormatProvider? provider)
{
throw new InvalidCastException();
}
#endregion
#endregion
}
}