using System; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; #nullable enable namespace Capnp.Rpc { /// /// 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 and . /// There are some intricacies to consider that you usually don't need to care about, since all that stuff will be generated. /// public static class CapabilityReflection { interface IBrokenFactory { System.Exception Exception { get; } } abstract class ProxyFactory { public abstract Proxy NewProxy(); } class ProxyFactory: 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: 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 _proxyMap = new ConditionalWeakTable(); static ConditionalWeakTable _skeletonMap = new ConditionalWeakTable(); static CapabilityReflection() { _proxyMap.Add(typeof(BareProxy), new ProxyFactory()); _proxyMap.Add(typeof(object), new ProxyFactory()); } 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); } }); } /// /// Creates a Skeleton for a given interface implementation. /// /// Interface implementation. Must implement at least one interface which is annotated with a . /// The Skeleton /// is null. /// No found on implemented interface(s). /// Mismatch between generic type arguments (if capability interface is generic). /// Mismatch between generic type arguments (if capability interface is generic). /// Problem with instatiating the Skeleton (constructor threw exception). /// Caller does not have permission to invoke the Skeleton constructor. /// Problem with building the Skeleton type, or problem with loading some dependent class. public static Skeleton CreateSkeleton(object obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); var factory = GetSkeletonFactory(obj.GetType()); var skeleton = factory.NewSkeleton(); skeleton.Bind(obj); return skeleton; } static ProxyFactory GetProxyFactory(Type type) { return _proxyMap.GetValue(type, _ => { try { var attrs = type .GetCustomAttributes(typeof(ProxyAttribute), false) .Cast() .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); } }); } /// /// Validates that a given type qualifies as cpapbility interface, throws on failure. /// /// type to check /// is null. /// Given typ did not qualify as capability interface. /// Message and probably InnterException give more details. 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); } } /// /// Checkes whether a given type qualifies as cpapbility interface./> on failure. /// /// type to check /// true when is a capability interface /// is null. public static bool IsValidCapabilityInterface(Type interfaceType) { if (interfaceType == null) { throw new ArgumentNullException(nameof(interfaceType)); } var proxyFactory = GetProxyFactory(interfaceType); return !(proxyFactory is IBrokenFactory); } /// /// Constructs a Proxy for given capability interface and wraps it around given low-level capability. /// /// Capability interface. Must be annotated with . /// low-level capability /// debugging aid /// debugging aid /// debugging aid /// The Proxy instance which implements . /// is null. /// did not qualify as capability interface. /// Mismatch between generic type arguments (if capability interface is generic). /// Mismatch between generic type arguments (if capability interface is generic). /// Problem with instatiating the Proxy (constructor threw exception). /// Caller does not have permission to invoke the Proxy constructor. /// Problem with building the Proxy type, or problem with loading some dependent class. public static Proxy CreateProxy(ConsumedCapability? cap, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) { var factory = GetProxyFactory(typeof(TInterface)); var proxy = factory.NewProxy(); proxy.Bind(cap); #if DebugFinalizers proxy.CreatorMemberName = memberName; proxy.CreatorFilePath = sourceFilePath; proxy.CreatorLineNumber = sourceLineNumber; if (cap != null) { cap.CreatorFilePath = proxy.CreatorFilePath; cap.CreatorLineNumber = proxy.CreatorLineNumber; cap.CreatorMemberName = proxy.CreatorMemberName; } #endif return proxy; } } } #nullable restore