Browse files

Built all infrastructure to support arbitrary subscriber instances. N…

…ot yet accessible via IBus
  • Loading branch information...
1 parent f67d694 commit 21de4f82ebd3ca9639e8a73c61bb773cb0f5af30 @flq flq committed Nov 21, 2010
View
28 MemBus.Tests/Help/IClassicIHandleStuffI.cs
@@ -0,0 +1,28 @@
+namespace MemBus.Tests.Help
+{
+ public interface IClassicIHandleStuffI<in T>
+ {
+ void Gimme(T theThing);
+ }
+
+ public interface IInvalidHandlerInterfaceBecauseNoParameter
+ {
+ void Gimme();
+ }
+
+ public interface IInvalidHandlerInterfaceBecauseTwoMethodsOfrequestedPattern
+ {
+ void Gimme(object thing);
+ void Gamme(object thang);
+ }
+
+ public interface IInvalidHandlerInterfaceBecauseTwoParams
+ {
+ void Gimme(object thing, object thang);
+ }
+
+ public interface IInvalidHandlerInterfaceBecauseReturnType
+ {
+ int Gimme(object thing);
+ }
+}
View
19 MemBus.Tests/Help/ItfNonGenericForHandles.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace MemBus.Tests.Help
+{
+ public interface ItfNonGenericForHandles
+ {
+ void Handle(MessageA msg);
+ }
+
+ class AHandlerThroughSimpleInterface : ItfNonGenericForHandles
+ {
+ public int MsgCount;
+
+ public void Handle(MessageA msg)
+ {
+ MsgCount++;
+ }
+ }
+}
View
37 MemBus.Tests/Help/SomeCrazyHandler.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace MemBus.Tests.Help
+{
+ public class SomeCrazyHandler : IClassicIHandleStuffI<MessageC>, IClassicIHandleStuffI<MessageB>
+ {
+ public int MessageCCount;
+ public int MessageACount;
+ public int MessageBCount;
+ public int MsgSpecialACount;
+
+ public void Gimme(MessageC theThing)
+ {
+ MessageCCount++;
+ }
+
+ public void Gimme(MessageB theThing)
+ {
+ MessageBCount++;
+ }
+
+ public void Handle(MessageA msg)
+ {
+ MessageACount++;
+ }
+
+ public void Handle(MessageB msg)
+ {
+ MessageBCount++;
+ }
+
+ public void Schmandle(MessageASpecialization msg)
+ {
+ MsgSpecialACount++;
+ }
+ }
+}
View
12 MemBus.Tests/Help/SomeHandler.cs
@@ -0,0 +1,12 @@
+namespace MemBus.Tests.Help
+{
+ public class SomeHandler
+ {
+ public int MsgACalls;
+
+ public void Handle(MessageA msg)
+ {
+ MsgACalls++;
+ }
+ }
+}
View
5 MemBus.Tests/MemBus.Tests.csproj
@@ -56,6 +56,11 @@
<Link>Frame\AssertExtensions.cs</Link>
</Compile>
<Compile Include="Cloning_a_bus.cs" />
+ <Compile Include="Help\IClassicIHandleStuffI.cs" />
+ <Compile Include="Help\ItfNonGenericForHandles.cs" />
+ <Compile Include="Help\SomeCrazyHandler.cs" />
+ <Compile Include="Help\SomeHandler.cs" />
+ <Compile Include="Using_SubscriptionAdapter_Service.cs" />
<Compile Include="When_using_caching_resolver.cs" />
<Compile Include="Documentation_tests.cs" />
<Compile Include="Frame\PublishPipelineTester.cs" />
View
111 MemBus.Tests/Using_SubscriptionAdapter_Service.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Linq;
+using MemBus.Setup;
+using MemBus.Subscribing;
+using MemBus.Tests.Help;
+using Moq;
+using NUnit.Framework;
+using MemBus.Tests.Frame;
+using MemBus.Support;
+
+namespace MemBus.Tests
+{
+ [TestFixture]
+ public class Using_SubscriptionAdapter_Service
+ {
+ [Test]
+ public void Unconfigured_setup_will_throw_invalid_op()
+ {
+ var setup = new FlexibleSubscribeAdapter();
+ Assert.Throws<InvalidOperationException>(() => ((ISetup<IConfigurableBus>)setup).Accept(null));
+ }
+
+ [Test]
+ public void When_having_some_configuration_adapter_adds_itself_as_service()
+ {
+ var setup = new FlexibleSubscribeAdapter();
+ setup.ByMethodName("Handle");
+
+ var bus = new Mock<IConfigurableBus>();
+ ((ISetup<IConfigurableBus>)setup).Accept(bus.Object);
+
+ bus.Verify(c=>c.AddService<IAdapterServices>(setup));
+ }
+
+ [Test]
+ public void Integrative_test_of_finding_all_handlers_in_complex_scenario()
+ {
+ var setup = new FlexibleSubscribeAdapter();
+ setup
+ .ByInterface(typeof(IClassicIHandleStuffI<>))
+ .ByMethodName("Handle")
+ .ByMethodName("Schmandle");
+
+ var handler = new SomeCrazyHandler();
+ var subs = ((IAdapterServices) setup).SubscriptionsFor(handler);
+
+ subs.ShouldHaveCount(5);
+
+ subs.Where(s=>s.Handles(typeof(MessageASpecialization))).Each(s=>s.Push(new MessageASpecialization()));
+ handler.MessageACount.ShouldBeEqualTo(1);
+ handler.MsgSpecialACount.ShouldBeEqualTo(1);
+
+ subs.Where(s => s.Handles(typeof(MessageB))).Each(s => s.Push(new MessageB()));
+ handler.MessageBCount.ShouldBeEqualTo(2); //There are 2 handle methods for MsgB :)
+
+ subs.Where(s => s.Handles(typeof(MessageC))).Each(s => s.Push(new MessageC()));
+ handler.MessageCCount.ShouldBeEqualTo(1);
+ }
+
+ [Test]
+ public void Subscriptions_are_built_for_object_method_based()
+ {
+ var builder = new MethodBasedBuilder("Handle");
+ var subs = builder.BuildSubscriptions(new SomeHandler());
+ subs.ShouldNotBeNull();
+ subs.ShouldHaveCount(1);
+ }
+
+ [Test]
+ public void Subscriptions_for_object_method_based_work_correctly()
+ {
+ var builder = new MethodBasedBuilder("Handle");
+ var handler = new SomeHandler();
+ var subs = builder.BuildSubscriptions(handler);
+ var subscription = subs.First();
+ subscription.Handles(typeof(MessageA)).ShouldBeTrue();
+ subscription.Push(new MessageA());
+ handler.MsgACalls.ShouldBeEqualTo(1);
+ }
+
+ [TestCase(typeof(IInvalidHandlerInterfaceBecauseNoParameter))]
+ [TestCase(typeof(IInvalidHandlerInterfaceBecauseTwoMethodsOfrequestedPattern))]
+ [TestCase(typeof(IInvalidHandlerInterfaceBecauseReturnType))]
+ [TestCase(typeof(IInvalidHandlerInterfaceBecauseTwoParams))]
+ public void These_handler_interfaces_are_invalid_for_usage(Type interfaceType)
+ {
+ Assert.Throws<InvalidOperationException>(() => { new InterfaceBasedBuilder(interfaceType); });
+ }
+
+ [Test]
+ public void Non_generic_interface_is_properly_handled()
+ {
+ var builder = new InterfaceBasedBuilder(typeof(ItfNonGenericForHandles));
+ var targetToAdapt = new AHandlerThroughSimpleInterface();
+ var subs = builder.BuildSubscriptions(targetToAdapt);
+ subs.ShouldHaveCount(1);
+ var s = subs.First();
+ s.Handles(typeof(MessageA)).ShouldBeTrue();
+ s.Push(new MessageA());
+ targetToAdapt.MsgCount.ShouldBeEqualTo(1);
+ }
+
+ [Test]
+ public void Two_subscriptions_expected_from_aquainting_crazy_handler()
+ {
+ var builder = new InterfaceBasedBuilder(typeof (IClassicIHandleStuffI<>));
+ var subs = builder.BuildSubscriptions(new SomeCrazyHandler());
+ subs.ShouldHaveCount(2);
+ }
+ }
+}
View
2 MemBus.Tests/When_subscribing.cs
@@ -7,7 +7,7 @@
namespace MemBus.Tests
{
[TestFixture]
- public class sWhen_subscribing
+ public class When_subscribing
{
[Test]
public void Conventions_allow_changing_the_shape()
View
6 MemBus/MemBus.csproj
@@ -75,6 +75,12 @@
<Compile Include="Configurators\RichClientFrontend.cs" />
<Compile Include="Setup\ISetup.cs" />
<Compile Include="Setup\IConfigurableBus.cs" />
+ <Compile Include="Subscribing\Adapter\ConstructSubscriptionExtension.cs" />
+ <Compile Include="Subscribing\Adapter\FlexibleSubscribeAdapter.cs" />
+ <Compile Include="Subscribing\Adapter\IAdapterServices.cs" />
+ <Compile Include="Subscribing\Adapter\InterfaceBasedBuilder.cs" />
+ <Compile Include="Subscribing\Adapter\ISubscriptionBuilder.cs" />
+ <Compile Include="Subscribing\Adapter\MethodBasedBuilder.cs" />
<Compile Include="Subscribing\ShapeToPassthrough.cs" />
<Compile Include="Subscribing\SubscriptionPipeline.cs" />
<Compile Include="Subscribing\DisposableSubscription.cs" />
View
9 MemBus/Setup/BusSetup.cs
@@ -9,6 +9,8 @@ public class BusSetup
{
private readonly List<ISetup<IConfigurableBus>> configurators = new List<ISetup<IConfigurableBus>>();
+ private BusSetup() { }
+
public BusSetup Apply(params ISetup<IConfigurableBus>[] configurators)
{
this.configurators.AddRange(configurators);
@@ -21,6 +23,13 @@ public BusSetup Apply<T>(params ISetup<IConfigurableBus>[] configurators) where
return Apply(configurators);
}
+ public BusSetup Apply<T>(Action<T> additionalConfig) where T : ISetup<IConfigurableBus>, new()
+ {
+ var t = new T();
+ additionalConfig(t);
+ return Apply(t);
+ }
+
public IBus Construct()
{
var bus = new Bus();
View
28 MemBus/Subscribing/Adapter/ConstructSubscriptionExtension.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Linq;
+
+namespace MemBus.Subscribing
+{
+ public static class ConstructSubscriptionExtension
+ {
+ public static ISubscription ConstructSubscription(this MethodInfo info, object target)
+ {
+ var parameterType = info.GetParameters()[0].ParameterType;
+
+ var fittingDelegateType = typeof(Action<>).MakeGenericType(parameterType);
+ var @delegate = Delegate.CreateDelegate(fittingDelegateType, target, info);
+
+ var fittingMethodSubscription = typeof(MethodInvocation<>).MakeGenericType(parameterType);
+ var sub = Activator.CreateInstance(fittingMethodSubscription, @delegate);
+
+ return (ISubscription)sub;
+ }
+
+ public static IEnumerable<ISubscription> ConstructSubscriptions(this IEnumerable<MethodInfo> infos, object target)
+ {
+ return infos.Select(i => i.ConstructSubscription(target));
+ }
+ }
+}
View
53 MemBus/Subscribing/Adapter/FlexibleSubscribeAdapter.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using MemBus.Setup;
+using System.Linq;
+
+namespace MemBus.Subscribing
+{
+ public class FlexibleSubscribeAdapter : ISetup<IConfigurableBus>, IAdapterServices
+ {
+ private bool configurationAvailable;
+ private readonly List<ISubscriptionBuilder> builders = new List<ISubscriptionBuilder>();
+
+
+ void ISetup<IConfigurableBus>.Accept(IConfigurableBus setup)
+ {
+ if (!configurationAvailable)
+ throw new InvalidOperationException("No adapter rules were set up.");
+ setup.AddService<IAdapterServices>(this);
+ }
+
+ /// <summary>
+ /// Look at an object and look for methods with the provided name. The method must be void
+ /// and accept a single parameter
+ /// </summary>
+ public FlexibleSubscribeAdapter ByMethodName(string methodName)
+ {
+ addToBuilders(new MethodBasedBuilder(methodName));
+ return this;
+ }
+
+ /// <summary>
+ /// Look at an object and check it for interfaces. An interface should adhere to the following rules:
+ /// Interface should define only one void method with a single parameter.
+ /// The interface may be generic and can be implemented multiple times.
+ /// </summary>
+ public FlexibleSubscribeAdapter ByInterface(Type interfaceType)
+ {
+ addToBuilders(new InterfaceBasedBuilder(interfaceType));
+ return this;
+ }
+
+ IEnumerable<ISubscription> IAdapterServices.SubscriptionsFor(object subscriber)
+ {
+ return builders.SelectMany(b => b.BuildSubscriptions(subscriber));
+ }
+
+ private void addToBuilders(ISubscriptionBuilder builder)
+ {
+ builders.Add(builder);
+ configurationAvailable = true;
+ }
+ }
+}
View
9 MemBus/Subscribing/Adapter/IAdapterServices.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MemBus.Subscribing
+{
+ public interface IAdapterServices
+ {
+ IEnumerable<ISubscription> SubscriptionsFor(object subscriber);
+ }
+}
View
9 MemBus/Subscribing/Adapter/ISubscriptionBuilder.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MemBus.Subscribing
+{
+ public interface ISubscriptionBuilder
+ {
+ IEnumerable<ISubscription> BuildSubscriptions(object targetToAdapt);
+ }
+}
View
92 MemBus/Subscribing/Adapter/InterfaceBasedBuilder.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using MemBus.Support;
+
+namespace MemBus.Subscribing
+{
+ public class InterfaceBasedBuilder : ISubscriptionBuilder
+ {
+ private readonly ISubscriptionBuilder innerBuilder;
+
+ public InterfaceBasedBuilder(Type interfaceType)
+ {
+ var suitableMethodsFound = getRelevantMethods(interfaceType).Count() == 1;
+
+ if (!suitableMethodsFound)
+ throw new InvalidOperationException("Membus cannot handle Interface {0} as subscription. Interface should define only one void method with one parameter. Interface may be generic and can be implemented multiple times.".Fmt(interfaceType.Name));
+
+ if (interfaceType.IsGenericTypeDefinition)
+ innerBuilder = new OpenInterfaceBuilder(interfaceType);
+ else
+ innerBuilder = new ClosedInterfaceBuilder(interfaceType);
+
+ }
+
+ private static IEnumerable<MethodInfo> getRelevantMethods(Type interfaceType)
+ {
+ return from mi in interfaceType.GetMethods()
+ where mi.GetParameters().Length == 1 &&
+ mi.ReturnType.Equals(typeof(void))
+ select mi;
+ }
+
+ public IEnumerable<ISubscription> BuildSubscriptions(object targetToAdapt)
+ {
+ if (targetToAdapt == null) throw new ArgumentNullException("targetToAdapt");
+ return innerBuilder.BuildSubscriptions(targetToAdapt);
+ }
+
+ private class OpenInterfaceBuilder : ISubscriptionBuilder
+ {
+ private readonly Type interfaceType;
+
+ public OpenInterfaceBuilder(Type interfaceType)
+ {
+ this.interfaceType = interfaceType;
+ }
+
+ public IEnumerable<ISubscription> BuildSubscriptions(object targetToAdapt)
+ {
+ var foundItfs = from itf in targetToAdapt.GetType().GetInterfaces()
+ where itf.IsGenericType && itf.GetGenericTypeDefinition().Equals(interfaceType)
+ select itf;
+ if (foundItfs.Count() == 0)
+ return new ISubscription[0];
+
+ return
+ foundItfs
+ .Select(itf => new ClosedInterfaceBuilder(itf))
+ .SelectMany(b => b.BuildSubscriptions(targetToAdapt));
+ }
+ }
+
+ private class ClosedInterfaceBuilder : ISubscriptionBuilder
+ {
+ private readonly Type interfaceType;
+
+ public ClosedInterfaceBuilder(Type interfaceType)
+ {
+ this.interfaceType = interfaceType;
+ }
+
+ public IEnumerable<ISubscription> BuildSubscriptions(object targetToAdapt)
+ {
+ var hasInterface = targetToAdapt.GetType().GetInterfaces().Any(t => t.Equals(interfaceType));
+ if (!hasInterface)
+ return new ISubscription[0];
+
+ var itfMi = getRelevantMethods(interfaceType).First();
+
+ var candidates = from mi in targetToAdapt.GetType().GetMethods()
+ where mi.Name == itfMi.Name &&
+ mi.GetParameters().Length == 1 &&
+ mi.GetParameters()[0].ParameterType == itfMi.GetParameters()[0].ParameterType &&
+ mi.ReturnType == itfMi.ReturnType
+ select mi;
+ return candidates.ConstructSubscriptions(targetToAdapt);
+ }
+ }
+ }
+}
View
36 MemBus/Subscribing/Adapter/MethodBasedBuilder.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace MemBus.Subscribing
+{
+ public class MethodBasedBuilder : ISubscriptionBuilder
+ {
+ private readonly string methodName;
+
+ public MethodBasedBuilder(string methodName)
+ {
+ this.methodName = methodName;
+ }
+
+ public IEnumerable<ISubscription> BuildSubscriptions(object targetToAdapt)
+ {
+ if (targetToAdapt == null) throw new ArgumentNullException("targetToAdapt");
+
+ var candidates =
+ (from mi in targetToAdapt.GetType().GetMethods()
+ where
+ mi.Name == methodName &&
+ !mi.IsGenericMethod &&
+ mi.GetParameters().Length == 1 &&
+ mi.ReturnType.Equals(typeof (void))
+ select mi).ToList();
+
+ if (candidates.Count == 0)
+ return new ISubscription[0];
+
+ return candidates.ConstructSubscriptions(targetToAdapt);
+ }
+ }
+}
View
1 MemBus/Subscribing/DisposableSubscription.cs
@@ -9,7 +9,6 @@ public class DisposableSubscription : IDisposableSubscription, IDenyShaper, IDis
public DisposableSubscription() : this(new NullSubscription())
{
- this.action = action;
}
public DisposableSubscription(ISubscription action)
View
15 MemBus/Subscribing/MethodInvocation.cs
@@ -2,10 +2,25 @@
namespace MemBus.Subscribing
{
+ /// <summary>
+ /// Helper to build a <see cref="MethodInvocation{T}"/>. See <see cref="FlexibleSubscribeAdapter"/> why.
+ /// </summary>
+ public class MethodInvocation
+ {
+ public MethodInvocation<T> Build<T>(Action<T> action)
+ {
+ return new MethodInvocation<T>(action);
+ }
+ }
+
public class MethodInvocation<T> : ISubscription
{
private readonly Action<T> action;
+ public MethodInvocation(Delegate action) : this((Action<T>)action)
+ {
+ }
+
public MethodInvocation(Action<T> action)
{
this.action = action;
View
2 TestAids/AssertExtensions.cs
@@ -10,7 +10,7 @@ public static class AssertExtensions
{
public static void ShouldHaveCount(this IEnumerable collection, int expectedCount)
{
- var actual = collection.OfType<object>().Count();
+ var actual = collection.Cast<object>().Count();
Assert.AreEqual(
expectedCount,
actual,

0 comments on commit 21de4f8

Please sign in to comment.