using System; using System.Linq; using System.Runtime.CompilerServices; namespace Capnp.Rpc { /// <summary> /// Provides functionality to construct Proxy and Skeleton instances from capability interfaces and objects implementing capability interfaces. /// A capability interface is any .NET interface which is annotated with <see cref="ProxyAttribute"/> and <see cref="SkeletonAttribute"/>. /// There are some intricacies to consider that you usually don't need to care about, since all that stuff will be generated. /// </summary> public static class CapabilityReflection { interface IBrokenFactory { System.Exception Exception { get; } } abstract class ProxyFactory { public abstract Proxy NewProxy(); } class ProxyFactory<T>: ProxyFactory where T: Proxy, new() { public override Proxy NewProxy() => new T(); } class BrokenProxyFactory: ProxyFactory, IBrokenFactory { readonly System.Exception _exception; public BrokenProxyFactory(System.Exception exception) { _exception = exception; } public System.Exception Exception => _exception; public override Proxy NewProxy() { throw _exception; } } abstract class SkeletonFactory { public abstract Skeleton NewSkeleton(); } class SkeletonFactory<T>: SkeletonFactory where T: Skeleton, new() { public override Skeleton NewSkeleton() => new T(); } class BrokenSkeletonFactory: SkeletonFactory, IBrokenFactory { System.Exception _exception; public BrokenSkeletonFactory(System.Exception exception) { _exception = exception; } public System.Exception Exception => _exception; public override Skeleton NewSkeleton() { throw _exception; } } class PolySkeletonFactory: SkeletonFactory { readonly SkeletonFactory[] _monoFactories; public PolySkeletonFactory(SkeletonFactory[] monoFactories) { _monoFactories = monoFactories; } public override Skeleton NewSkeleton() { var poly = new PolySkeleton(); foreach (var fac in _monoFactories) { poly.AddInterface(fac.NewSkeleton()); } return poly; } } static ConditionalWeakTable<Type, ProxyFactory> _proxyMap = new ConditionalWeakTable<Type, ProxyFactory>(); static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap = new ConditionalWeakTable<Type, SkeletonFactory>(); static ConditionalWeakTable<object, Skeleton> _implMap = new ConditionalWeakTable<object, Skeleton>(); static CapabilityReflection() { _proxyMap.Add(typeof(BareProxy), new ProxyFactory<BareProxy>()); _proxyMap.Add(typeof(object), new ProxyFactory<BareProxy>()); } static SkeletonFactory CreateMonoSkeletonFactory(SkeletonAttribute attr, Type[] genericArguments) { var skeletonClass = attr.SkeletonClass; if (genericArguments.Length > 0) skeletonClass = skeletonClass.MakeGenericType(genericArguments); return (SkeletonFactory)Activator.CreateInstance( typeof(SkeletonFactory<>) .MakeGenericType(skeletonClass))!; } static SkeletonFactory GetSkeletonFactory(Type type) { return _skeletonMap.GetValue(type, _ => { try { var attrs = (from iface in _.GetInterfaces() let generics = iface.GetGenericArguments() from attr in iface.GetCustomAttributes(typeof(SkeletonAttribute), false) select (Attr: (SkeletonAttribute)attr, Generics: generics)).ToArray(); if (attrs.Length == 0) throw new InvalidCapabilityInterfaceException("No 'Skeleton' attribute defined, don't know how to create the skeleton"); if (attrs.Length == 1) { return CreateMonoSkeletonFactory(attrs[0].Attr, attrs[0].Generics); } else { var monoFactories = attrs.Select(a => CreateMonoSkeletonFactory(a.Attr, a.Generics)).ToArray(); return new PolySkeletonFactory(monoFactories); } } catch (System.Exception exception) { return new BrokenSkeletonFactory(exception); } }); } /// <summary> /// Creates a Skeleton for a given interface implementation. /// </summary> /// <param name="obj">Interface implementation. Must implement at least one interface which is annotated with a <see cref="SkeletonAttribute"/>.</param> /// <returns>The Skeleton</returns> /// <exception cref="ArgumentNullException"><paramref name="obj"/> is null.</exception> /// <exception cref="InvalidCapabilityInterfaceException">No <see cref="SkeletonAttribute"/> found on implemented interface(s).</exception> /// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception> /// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception> /// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception> [Obsolete("Do not use this method directly. Instead, pass objects directly or use Proxy.Share<T>(). This method will be removed with next release.")] public static Skeleton CreateSkeleton(object obj) => CreateSkeletonInternal(obj); internal static Skeleton CreateSkeletonInternal(object obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); var result = _implMap.GetValue(obj, _ => { var factory = GetSkeletonFactory(_.GetType()); var skeleton = factory.NewSkeleton(); skeleton.Bind(obj); return skeleton; }); return result; } static ProxyFactory GetProxyFactory(Type type) { return _proxyMap.GetValue(type, _ => { try { var attrs = type .GetCustomAttributes(typeof(ProxyAttribute), false) .Cast<ProxyAttribute>() .ToArray(); if (attrs.Length == 0) throw new InvalidCapabilityInterfaceException("No 'Proxy' attribute defined, don't know how to create the proxy"); if (attrs.Length == 1) { Type proxyClass = attrs[0].ProxyClass; Type[] genericArguments = type.GetGenericArguments(); if (genericArguments.Length > 0) proxyClass = proxyClass.MakeGenericType(genericArguments); return (ProxyFactory)Activator.CreateInstance( typeof(ProxyFactory<>) .MakeGenericType(proxyClass))!; } else { throw new InvalidCapabilityInterfaceException("Multiple 'Proxy' attributes defined, don't know which one to take"); } } catch (System.Exception exception) { return new BrokenProxyFactory(exception); } }); } /// <summary> /// Validates that a given type qualifies as cpapbility interface, throws <see cref="InvalidCapabilityInterfaceException"/> on failure. /// </summary> /// <param name="interfaceType">type to check</param> /// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception> /// <exception cref="InvalidCapabilityInterfaceException">Given typ did not qualify as capability interface. /// Message and probably InnterException give more details.</exception> public static void ValidateCapabilityInterface(Type interfaceType) { if (interfaceType == null) { throw new ArgumentNullException(nameof(interfaceType)); } var proxyFactory = GetProxyFactory(interfaceType); if (proxyFactory is IBrokenFactory brokenFactory) { throw new InvalidCapabilityInterfaceException( "Given type did not qualify as capability interface, see inner exception.", brokenFactory.Exception); } } /// <summary> /// Checks whether a given type qualifies as capability interface. /// </summary> /// <param name="interfaceType">type to check</param> /// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns> /// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception> public static bool IsValidCapabilityInterface(Type interfaceType) { if (interfaceType == null) { throw new ArgumentNullException(nameof(interfaceType)); } var proxyFactory = GetProxyFactory(interfaceType); return !(proxyFactory is IBrokenFactory); } /// <summary> /// Constructs a Proxy for given capability interface and wraps it around given low-level capability. /// </summary> /// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam> /// <param name="cap">low-level capability</param> /// <returns>The Proxy instance which implements <typeparamref name="TInterface"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</exception> /// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not qualify as capability interface.</exception> /// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception> /// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception> /// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception> public static Proxy CreateProxy<TInterface>(ConsumedCapability cap) { var factory = GetProxyFactory(typeof(TInterface)); var proxy = factory.NewProxy(); proxy.Bind(cap); return proxy; } } }