Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

@@ -0,0 +1,37 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @enum strange.extensions.command.api.CommandExceptionType
*
* Errors relating to the CommandBinder
*/

using System;

namespace strange.extensions.command.api
{
public enum CommandExceptionType
{
/// Commands must always override the Execute() method.
EXECUTE_OVERRIDE,
/// Binding wasn't found
NULL_BINDING,
/// Something went wrong during construction, so the Command resolved to null
BAD_CONSTRUCTOR
}
}

@@ -0,0 +1,27 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.command.api
{
public enum CommandKeys
{
/// Temporary marker for any pool instantiated by the CommandBinder
COMMAND_POOL
}
}

@@ -0,0 +1,77 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.command.api.ICommand
*
* Interface for Commands, which is where you place your business logic.
*
* In the default StrangeIoC setup, commands are mapped to IEvents.
* The firing of a specific event on the global event bus triggers
* the instantiation, injection and execution of any Command(s) bound to that event.
*
* By default, commands are cleaned up immediately on completion of the `Execute()` method.
* For asynchronous Commands (e.g., calling a service and awaiting a response),
* call `Retain()` at the top of your `Execute()` method, which will prevent
* premature cleanup. But remember, having done so it is your responsipility
* to call `Release()` once the Command is complete.
*
* Calling `Fail()` will terminate any sequence in which the Command is operating, but
* has no effect on Commands operating in parallel.
*/

using System;

namespace strange.extensions.command.api
{
public interface ICommand
{
/// Override this! `Execute()` is where you place the logic for your Command.
void Execute();

/// Keeps the Command in memory. Use only in conjunction with `Release()`
void Retain();

/// Allows a previous Retained Command to be disposed.
void Release();

/// Inidcates that the Command failed
/// Used in sequential command groups to terminate the sequence
void Fail();

/// Inform the Command that further Execution has been terminated
void Cancel ();

/// Flag to indicate that a pooled Command has been restored to its pristine state.
/// The CommandBinder will use this to determine if re-Injection is required.
bool IsClean { get; set; }

/// The property set by `Retain` and `Release` to indicate whether the Command should be cleaned up on completion of the `Execute()` method.
bool retain { get; }

/// The property set to true by a Cancel() call.
/// Use cancelled internally to determine if further execution is warranted, especially in
/// asynchronous calls.
bool cancelled { get; set; }

/// A payload injected into the Command. Most commonly, this an IEvent.
object data { get; set; }

//The ordered id of this Command, used in sequencing to find the next Command.
int sequenceId{ get; set; }
}
}

@@ -0,0 +1,86 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.command.api.ICommandBinder
*
* Interface for a Binder that triggers the instantiation of Commands.
*
* Commands are where the logic of your application belongs.
* These Commands typically focus on a single function, such as
* adding a View, requesting a service, reading from or saving to a model.
*
* The act of binding events to Commands means that code needn't know
* anything about an event recipient, or even how the event will be used.
* For example, a Mediator might send out an event that two View objects
* collided. A Command would then determine that the result of that event
* was to Destroy both objects, tell a ScoreKeeper model to change the
* score and request a message be sent to the server. Whether that
* example means one Command or three is up to your coding preference...
* CommandBinder can trigger one Command or multiple Commands off the
* same event.
*
* Note that CommandBinder also a features sequencing. By default, CommandBinder fires all
* Commands in parallel. If your binding specifies `InSequence()`, commands will run serially,
* with the option of suspending the chain at any time.
*
* Example bindings:
Bind("someEvent").To<SomeCommand>(); //Works, but poor form to use strings. Use the next example instead
Bind(EventMap.SOME_EVENT).To<SomeCommand>(); //Make it a constant
Bind(ContextEvent.START).To<StartCommand>().Once(); //Destroy the binding immediately after a single use
Bind(EventMap.END_GAME_EVENT).To<FirstCommand>().To<SecondCommand>().To<ThirdGCommand>().InSequence();
*
* See Command for details on asynchronous Commands and cancelling sequences.
*/

using System;
using strange.extensions.injector.api;
using strange.framework.api;

namespace strange.extensions.command.api
{
public interface ICommandBinder : IBinder
{

/// Trigger a key that unlocks one or more Commands
void ReactTo (object trigger);

/// Trigger a key that unlocks one or more Commands and provide a data injection to that Command
void ReactTo (object trigger, object data);

/// Release a previously retained Command.
/// By default, a Command is garbage collected at the end of its `Execute()` method.
/// But the Command can be retained for asynchronous calls.
void ReleaseCommand(ICommand command);

/// Called to halt execution of a currently running command group
void Stop(object key);

/// Bind a trigger Key by generic Type
new ICommandBinding Bind<T>();

/// Bind a trigger Key by value
new ICommandBinding Bind(object value);

new ICommandBinding GetBinding<T>();
}
}

@@ -0,0 +1,67 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.command.api.ICommandBinding
*
* Defines the form of a Binding for use with the CommandBinder.
*
* The only real distinctions between CommandBinding and Binding
* are:
* - the addition of `Once()`, which signals that the Binding should be destroyed immediately after a single use.
* - a marker for running multiple commands in parallel (default) or sequentially.
*/

using System;
using strange.framework.api;

namespace strange.extensions.command.api
{
public interface ICommandBinding : IBinding
{
/// Declares that the Binding is a one-off. As soon as it's satisfied, it will be unmapped.
ICommandBinding Once();

/// Get/set the property set to `true` by `Once()`
bool isOneOff{ get; set;}

/// Declares that the bound commands will be run in parallel.
/// Parallel is the default. There is no need to call this unless you're changing from sequence.
ICommandBinding InParallel();

/// Declares that the bound commands will be run as a sequence, rather than in parallel
ICommandBinding InSequence ();

/// Declares that the Commands generated by this binding will be pooled
ICommandBinding Pooled();

/// Get/set the propterty set by InSequence() and InParallel()
bool isSequence{ get; set;}

/// Get/set the propterty set by Pooled()
bool isPooled{ get; set;}

new ICommandBinding Bind<T>();
new ICommandBinding Bind(object key);
new ICommandBinding To<T>();
new ICommandBinding To(object o);
new ICommandBinding ToName<T> ();
new ICommandBinding ToName (object o);
new ICommandBinding Named<T>();
new ICommandBinding Named(object o);
}
}

@@ -0,0 +1,39 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @interface strange.extensions.command.api.IPooledCommandBinder
*
* Interface for a CommandBinder that allows pooling. Pooling allows Commands to
* be recycled, which can be more efficient.
*/

using System;
using System.Collections.Generic;
using strange.extensions.pool.impl;
using strange.extensions.command.impl;

namespace strange.extensions.command.api
{
public interface IPooledCommandBinder
{
/// Retrieve the Pool of the specified type
Pool<T> GetPool<T>();

/// Switch to disable pooling for those that don't want to use it.
bool usePooling{ get; set; }
}
}

@@ -0,0 +1,112 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.Command
*
* Commands are where you place your business logic.
*
* In the MVCSContext setup, commands are mapped to IEvents.
* The firing of a specific event on the global event bus triggers
* the instantiation, injection and execution of any Command(s) bound to that event.
*
* By default, commands are cleaned up immediately on completion of the `Execute()` method.
* For asynchronous Commands (e.g., calling a service and awaiting a response),
* call `Retain()` at the top of your `Execute()` method, which will prevent
* premature cleanup. But remember, having done so it is your responsipility
* to call `Release()` once the Command is complete.
*
* Calling `Fail()` will terminate any sequence in which the Command is operating, but
* has no effect on Commands operating in parallel.
*/

using System;
using strange.extensions.command.api;
using strange.extensions.injector.api;
using strange.framework.api;
using strange.extensions.pool.api;

namespace strange.extensions.command.impl
{
public class Command : ICommand, IPoolable
{
/// Back reference to the CommandBinder that instantiated this Commmand
[Inject]
public ICommandBinder commandBinder{ get; set;}

/// The InjectionBinder for this Context
[Inject]
public IInjectionBinder injectionBinder{ get; set; }

public object data{ get; set; }

public bool cancelled{ get; set; }

public bool IsClean{ get; set; }

public int sequenceId{ get; set; }

public Command ()
{
//Set to false on construction to ensure that it's not double-injected on first use.
//The pool will satisfy all injections on first use. The CommandBinder re-injects
//every time the Command is recycled.
IsClean = false;
}

virtual public void Execute()
{
throw new CommandException ("You must override the Execute method in every Command", CommandExceptionType.EXECUTE_OVERRIDE);
}

public virtual void Retain()
{
retain = true;
}

public virtual void Release()
{
retain = false;
if (commandBinder != null)
{
commandBinder.ReleaseCommand (this);
}
}

/// Use/override this method to clean up the Command for recycling
virtual public void Restore()
{
injectionBinder.injector.Uninject (this);
IsClean = true;
}

public virtual void Fail()
{
if (commandBinder != null)
{
commandBinder.Stop (this);
}
}

public void Cancel()
{
cancelled = true;
}

public bool retain { get; set; }
}
}

@@ -0,0 +1,343 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.CommandBinder
*
* A Binder that triggers the instantiation of Commands.
*
* Commands are where the logic of your application belongs.
* These Commands typically focus on a single function, such as
* adding a View, requesting a service, reading from or saving to a model.
*
* The act of binding events to Commands means that code needn't know
* anything about an event recipient, or even how the event will be used.
* For example, a Mediator might send out an event that two View objects
* collided. A Command would then determine that the result of that event
* was to Destroy both objects, tell a ScoreKeeper model to change the
* score and request a message be sent to the server. Whether that
* example means one Command or three is up to your coding preference...
* CommandBinder can trigger one Command or multiple Commands off the
* same event.
*
* Note that CommandBinder also features sequencing. By default, CommandBinder fires all
* Commands in parallel. If your binding specifies `InSequence()`, commands will run serially,
* with the option of suspending the chain at any time.
*
* Example bindings:
Bind("someEvent").To<SomeCommand>(); //Works, but poor form to use strings. Use the next example instead
Bind(EventMap.SOME_EVENT).To<SomeCommand>(); //Make it a constant
Bind(ContextEvent.START).To<StartCommand>().Once(); //Destroy the binding immediately after a single use
Bind(EventMap.END_GAME_EVENT).To<FirstCommand>().To<SecondCommand>().To<ThirdGCommand>().InSequence();
*
* See Command for details on asynchronous Commands and cancelling sequences.
*/

using System;
using System.Collections.Generic;
using System.Reflection;
using strange.extensions.command.api;
using strange.extensions.dispatcher.api;
using strange.extensions.injector.api;
using strange.extensions.pool.impl;
using strange.framework.api;
using strange.framework.impl;
using strange.extensions.pool.api;

namespace strange.extensions.command.impl
{
public class CommandBinder : Binder, ICommandBinder, IPooledCommandBinder, ITriggerable
{
[Inject]
public IInjectionBinder injectionBinder { get; set; }

protected Dictionary<Type, Pool> pools = new Dictionary<Type, Pool> ();

/// Tracker for parallel commands in progress
protected HashSet<ICommand> activeCommands = new HashSet<ICommand>();

/// Tracker for sequences in progress
protected Dictionary<ICommand, ICommandBinding> activeSequences = new Dictionary<ICommand, ICommandBinding> ();

public CommandBinder ()
{
usePooling = true;
}

public override IBinding GetRawBinding ()
{
return new CommandBinding(resolver);
}

virtual public void ReactTo (object trigger)
{
ReactTo (trigger, null);
}

virtual public void ReactTo(object trigger, object data)
{
if (data is IPoolable)
{
(data as IPoolable).Retain ();
}
ICommandBinding binding = GetBinding (trigger) as ICommandBinding;
if (binding != null)
{
if (binding.isSequence)
{
next (binding, data, 0);
}
else
{
object[] values = binding.value as object[];
int aa = values.Length + 1;
for (int a = 0; a < aa; a++)
{
next (binding, data, a);
}
}
}
}

protected void next(ICommandBinding binding, object data, int depth)
{
object[] values = binding.value as object[];
if (depth < values.Length)
{
Type cmd = values [depth] as Type;
ICommand command = invokeCommand (cmd, binding, data, depth);
ReleaseCommand (command);
}
else
{
disposeOfSequencedData (data);
if (binding.isOneOff)
{
Unbind (binding);
}
}
}

//EventCommandBinder (and perhaps other sub-classes) use this method to dispose of the data in sequenced commands
virtual protected void disposeOfSequencedData(object data)
{
//No-op. Override if necessary.
}

virtual protected ICommand invokeCommand(Type cmd, ICommandBinding binding, object data, int depth)
{
ICommand command = createCommand (cmd, data);
command.sequenceId = depth;
trackCommand (command, binding);
executeCommand (command);
return command;
}

virtual protected ICommand createCommand(object cmd, object data)
{
ICommand command = getCommand (cmd as Type);

if (command == null)
{
string msg = "A Command ";
if (data != null)
{
msg += "tied to data " + data.ToString ();
}
msg += " could not be instantiated.\nThis might be caused by a null pointer during instantiation or failing to override Execute (generally you shouldn't have constructor code in Commands).";
throw new CommandException(msg, CommandExceptionType.BAD_CONSTRUCTOR);
}

command.data = data;
return command;
}

protected ICommand getCommand(Type type)
{
if (usePooling && pools.ContainsKey(type))
{
Pool pool = pools [type];
ICommand command = pool.GetInstance () as ICommand;
if (command.IsClean)
{
injectionBinder.injector.Inject (command);
command.IsClean = false;
}
return command;
}
else
{
// trying to use names to avoid ICommand binding conflict when instantiation chain has to create multiple commands at once
injectionBinder.Bind<ICommand> ().To (type).ToName(""+type);
ICommand command = injectionBinder.GetInstance<ICommand> (""+type);
injectionBinder.Unbind<ICommand> (""+type);
return command;
}
}

protected void trackCommand (ICommand command, ICommandBinding binding)
{
if (binding.isSequence)
{
activeSequences.Add(command, binding);
}
else
{
activeCommands.Add(command);
}
}

protected void executeCommand(ICommand command)
{
if (command == null)
{
return;
}
command.Execute ();
}

public virtual void Stop(object key)
{
if (key is ICommand && activeSequences.ContainsKey(key as ICommand))
{
removeSequence (key as ICommand);
}
else
{
ICommandBinding binding = GetBinding (key) as ICommandBinding;
if (binding != null)
{
if (activeSequences.ContainsValue (binding))
{
foreach(KeyValuePair<ICommand, ICommandBinding> sequence in activeSequences)
{
if (sequence.Value == binding)
{
ICommand command = sequence.Key;
removeSequence (command);
}
}
}
}
}
}

public virtual void ReleaseCommand (ICommand command)
{
if (command.retain == false)
{
Type t = command.GetType ();
if (usePooling && pools.ContainsKey (t))
{
pools [t].ReturnInstance (command);
}
if (activeCommands.Contains(command))
{
activeCommands.Remove (command);
}
else if (activeSequences.ContainsKey(command))
{
ICommandBinding binding = activeSequences [command];
object data = command.data;
activeSequences.Remove (command);
next (binding, data, command.sequenceId + 1);
}
}
}

public bool usePooling { get; set; }

public Pool<T> GetPool<T>()
{
Type t = typeof (T);
if (pools.ContainsKey(t as Type))
return pools[t] as Pool<T>;
return null;
}

private void removeSequence(ICommand command)
{
if (activeSequences.ContainsKey (command))
{
command.Cancel();
activeSequences.Remove (command);
}
}

public bool Trigger<T>(object data)
{
return Trigger (typeof(T), data);
}

public bool Trigger(object key, object data)
{
ReactTo(key, data);
return true;
}

new public virtual ICommandBinding Bind<T> ()
{
return base.Bind<T> () as ICommandBinding;
}

new public virtual ICommandBinding Bind (object value)
{
return base.Bind (value) as ICommandBinding;
}

override protected void resolver(IBinding binding)
{
base.resolver (binding);
if (usePooling && (binding as ICommandBinding).isPooled)
{
if (binding.value != null)
{
object[] values = binding.value as object[];
foreach (Type value in values)
{
if (pools.ContainsKey (value) == false)
{
var myPool = makePoolFromType (value);
pools [value] = myPool;
}
}
}
}
}

virtual protected Pool makePoolFromType(Type type)
{
Type poolType = typeof(Pool<>).MakeGenericType(type);

injectionBinder.Bind (type).To (type);
injectionBinder.Bind<Pool>().To(poolType).ToName (CommandKeys.COMMAND_POOL);
Pool pool = injectionBinder.GetInstance<Pool> (CommandKeys.COMMAND_POOL) as Pool;
injectionBinder.Unbind<Pool> (CommandKeys.COMMAND_POOL);
return pool;
}

new public virtual ICommandBinding GetBinding<T>()
{
return base.GetBinding<T>() as ICommandBinding;
}
}
}

@@ -0,0 +1,118 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.CommandBinding
*
* The Binding for CommandBinder.
*
* The only real distinction between CommandBinding and Binding
* is the addition of `Once()`, which signals that the Binding
* should be destroyed immediately after a single use.
*/

using System;
using strange.extensions.command.api;
using strange.framework.impl;

namespace strange.extensions.command.impl
{
public class CommandBinding : Binding, ICommandBinding
{
public bool isOneOff{ get; set; }

public bool isSequence{ get; set; }

public bool isPooled{ get; set; }

public CommandBinding() : base()
{
}

public CommandBinding (Binder.BindingResolver resolver) : base(resolver)
{
}

public ICommandBinding Once()
{
isOneOff = true;
return this;
}

public ICommandBinding InParallel()
{
isSequence = false;
return this;
}

public ICommandBinding InSequence()
{
isSequence = true;
return this;
}

public ICommandBinding Pooled()
{
isPooled = true;
resolver (this);
return this;
}

//Everything below this point is simply facade on Binding to ensure fluent interface


new public ICommandBinding Bind<T>()
{
return base.Bind<T> () as ICommandBinding;
}

new public ICommandBinding Bind(object key)
{
return base.Bind (key) as ICommandBinding;
}

new public ICommandBinding To<T>()
{
return base.To<T> () as ICommandBinding;
}

new public ICommandBinding To(object o)
{
return base.To (o) as ICommandBinding;
}

new public ICommandBinding ToName<T>()
{
return base.ToName<T> () as ICommandBinding;
}

new public ICommandBinding ToName(object o)
{
return base.ToName (o) as ICommandBinding;
}

new public ICommandBinding Named<T>()
{
return base.Named<T> () as ICommandBinding;
}

new public ICommandBinding Named(object o)
{
return base.Named (o) as ICommandBinding;
}
}
}

@@ -0,0 +1,45 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.CommandException
*
* An exception raised by the Command system.
*
* @see strange.extensions.context.api.CommandExceptionType
*/

using System;
using strange.extensions.command.api;

namespace strange.extensions.command.impl
{
public class CommandException : Exception
{
public CommandExceptionType type{ get; set;}

public CommandException () : base()
{
}

/// Constructs a CommandException with a message and CommandExceptionType
public CommandException(string message, CommandExceptionType exceptionType) : base(message)
{
type = exceptionType;
}
}
}

@@ -0,0 +1,53 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.EventCommand
*
* Subclass of Command with injections for dispatcher and events.
*
* EventCommand extends Command to provide access to EventDispatcher as the common system bus.
* Commands which extend Event Command will automatically inject the source IEvent.
*/

using System;
using strange.extensions.context.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.command.impl;
using strange.extensions.pool.api;

namespace strange.extensions.command.impl
{
public class EventCommand : Command
{
[Inject(ContextKeys.CONTEXT_DISPATCHER)]
public IEventDispatcher dispatcher{ get; set;}

[Inject]
public IEvent evt{ get; set;}

public override void Retain ()
{
base.Retain ();
}

public override void Release ()
{
base.Release ();
}
}
}

@@ -0,0 +1,86 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.EventCommandBinder
*
* A subclass of CommandBinder which relies on an IEventDispatcher as the common system bus.
*/

using System;
using strange.extensions.command.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.pool.api;

namespace strange.extensions.command.impl
{
public class EventCommandBinder : CommandBinder
{
public EventCommandBinder ()
{
}
///
override protected ICommand createCommand(object cmd, object data)
{
injectionBinder.Bind<ICommand> ().To (cmd);
if (data is IEvent)
{
injectionBinder.Bind<IEvent>().ToValue(data).ToInject(false);
}

ICommand command = injectionBinder.GetInstance<ICommand>() as ICommand;
try
{
if (command == null)
{
string msg = "A Command ";
if (data is IEvent)
{
IEvent evt = (IEvent) data;
msg += "tied to event " + evt.type;
}
msg += " could not be instantiated.\nThis might be caused by a null pointer during instantiation or failing to override Execute (generally you shouldn't have constructor code in Commands).";
throw new CommandException(msg, CommandExceptionType.BAD_CONSTRUCTOR);
}

command.data = data;
}
catch (Exception)
{
throw;
}
finally
{
if (data is IEvent)
{
injectionBinder.Unbind<IEvent>();
}
injectionBinder.Unbind<ICommand>();
}

return command;
}

override protected void disposeOfSequencedData(object data)
{
if (data is IPoolable)
{
(data as IPoolable).Release();
}
}
}
}

@@ -0,0 +1,205 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.command.impl.SignalCommandBinder;
*
* A Binder that triggers the instantiation of Commands using Signals.
*
* Commands are where the logic of your application belongs.
* These Commands typically focus on a single function, such as
* adding a View, requesting a service, reading from or saving to a model.
*
* The act of binding Signals to Commands means that code needn't know
* anything about a Signal recipient, or even how the Signal will be used.
* For example, a Mediator might dispatch a Signal that two View objects
* collided. A Command would then determine that the result of that Signal
* was to Destroy both objects, tell a ScoreKeeper model to change the
* score and request a message be sent to the server. Whether that
* example means one Command or three is up to your coding preference...
* SignalCommandBinder can trigger one Command or multiple Commands off the
* same Signal.
*
* Signals bind their parameters to Command injections by comparing types and do not understand
* named injections. Therefore, in order to Bind a Command's injections to a Signal,
* PARAMETERS/INJECTIONS MUST BE OF UNIQUE TYPES. It is not, therefore, possible to bind a
* Signal with two of the same type to a Command.
*
* Note that like CommandBinder, SignalCommandBinder features sequencing. By default,
* SignalCommandBinder fires all Commands in parallel. If your binding specifies `InSequence()`,
* Commands will run serially, with the option of suspending the chain at any time.
*
* Example bindings:
Bind(someSignal).To<SomeCommand>();
Bind<SomeSignalClass>().To<StartCommand>().Once(); //Destroy the binding immediately after a single use
Bind<SomeSignalClass>().To<FirstCommand>().To<SecondCommand>().To<ThirdGCommand>().InSequence(); //Bind a sequence
*
* See Command for details on asynchronous Commands and cancelling sequences.
*/

using System;
using System.Collections.Generic;
using System.Reflection;
using strange.extensions.command.api;
using strange.extensions.injector.api;
using strange.framework.api;
using strange.extensions.injector.impl;
using strange.extensions.signal.impl;
using strange.extensions.signal.api;

namespace strange.extensions.command.impl
{
public class SignalCommandBinder : CommandBinder
{
override public void ResolveBinding(IBinding binding, object key)
{
base.ResolveBinding(binding, key);

if (bindings.ContainsKey(key)) //If this key already exists, don't bind this again
{
IBaseSignal signal = (IBaseSignal)key;
signal.AddListener(ReactTo); //Do normal bits, then assign the commandlistener to be reactTo
}

}

override public void OnRemove()
{
foreach (object key in bindings.Keys)
{
IBaseSignal signal = (IBaseSignal)key;
if (signal != null)
{
signal.RemoveListener(ReactTo);
}
}
}

protected override ICommand invokeCommand(Type cmd, ICommandBinding binding, object data, int depth)
{
IBaseSignal signal = (IBaseSignal)binding.key;
ICommand command = createCommandForSignal(cmd, data, signal.GetTypes()); //Special signal-only command creation
command.sequenceId = depth;
trackCommand(command, binding);
executeCommand(command);
return command;
}

/// Create a Command and bind its injectable parameters to the Signal types
protected ICommand createCommandForSignal(Type cmd, object data, List<Type> signalTypes)
{
if (data != null)
{
object[] signalData = (object[])data;

//Iterate each signal type, in order.
//Iterate values and find a match
//If we cannot find a match, throw an error
HashSet<Type> injectedTypes = new HashSet<Type>();
List<object> values = new List<object>(signalData);

foreach (Type type in signalTypes)
{
if (!injectedTypes.Contains(type)) // Do not allow more than one injection of the same Type
{
bool foundValue = false;
foreach (object value in values)
{
if (value != null)
{
if (type.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo())) //IsAssignableFrom lets us test interfaces as well
{
injectionBinder.Bind(type).ToValue(value).ToInject(false);
injectedTypes.Add(type);
values.Remove(value);
foundValue = true;
break;
}
}
else //Do not allow null injections
{
throw new SignalException("SignalCommandBinder attempted to bind a null value from a signal to Command: " + cmd.GetType() + " to type: " + type, SignalExceptionType.COMMAND_NULL_INJECTION);
}
}
if (!foundValue)
{
throw new SignalException("Could not find an unused injectable value to inject in to Command: " + cmd.GetType() + " for Type: " + type, SignalExceptionType.COMMAND_VALUE_NOT_FOUND);
}
}
else
{
throw new SignalException("SignalCommandBinder: You have attempted to map more than one value of type: " + type +
" in Command: " + cmd.GetType() + ". Only the first value of a type will be injected. You may want to place your values in a VO, instead.",
SignalExceptionType.COMMAND_VALUE_CONFLICT);
}
}
}
ICommand command = getCommand(cmd);
command.data = data;

foreach (Type typeToRemove in signalTypes) //clean up these bindings
injectionBinder.Unbind(typeToRemove);
return command;
}

override public ICommandBinding Bind<T>()
{
IInjectionBinding binding = injectionBinder.GetBinding<T>();
if (binding == null) //If this isn't injected yet, inject a new one as a singleton
{
injectionBinder.Bind<T>().ToSingleton();
}

T signal = injectionBinder.GetInstance<T>();
return base.Bind(signal);
}

/// <summary>Unbind by Signal Type</summary>
/// <exception cref="InjectionException">If there is no binding for this type.</exception>
public override void Unbind<T>()
{
ICommandBinding binding = injectionBinder.GetBinding<T>() as ICommandBinding;
if (binding != null)
{
T signal = (T) injectionBinder.GetInstance<T>();
Unbind(signal, null);
}
}

/// <summary>Unbind by Signal Instance</summary>
/// <param name="key">Instance of IBaseSignal</param>
override public void Unbind(object key, object name)
{
if (bindings.ContainsKey(key))
{
IBaseSignal signal = (IBaseSignal)key;
signal.RemoveListener(ReactTo);
}
base.Unbind(key, name);
}

public override ICommandBinding GetBinding<T>()
{
//This should be a signal, see Bind<T> above
T signal = (T)injectionBinder.GetInstance<T>();
return base.GetBinding(signal) as ICommandBinding;
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/



using System;

namespace strange.extensions.context.api
{
public enum ContextEvent
{
/// Fires immediately on conclusion of Context bootstrapping. Map this to your first Command.
START
}
}

@@ -0,0 +1,29 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.context.api
{
public enum ContextExceptionType
{
/// MVCSContext requires a root ContextView
NO_CONTEXT_VIEW,
/// MVCSContext requires a mediationBinder
NO_MEDIATION_BINDER
}
}

@@ -0,0 +1,33 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.context.api
{
public enum ContextKeys
{
/// Marker for the named Injection of the Context
CONTEXT,
/// Marker for the named Injection of the ContextView
CONTEXT_VIEW,
/// Marker for the named Injection of the contextDispatcher
CONTEXT_DISPATCHER,
/// Marker for the named Injection of the crossContextDispatcher
CROSS_CONTEXT_DISPATCHER
}
}

@@ -0,0 +1,38 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* Flags to interrupt the Context startup process.
*/

using System;

namespace strange.extensions.context.api
{
[Flags]
public enum ContextStartupFlags
{
/// Context will map bindings and launch automatically (default).
AUTOMATIC = 0,
/// Context startup will halt after Core bindings are mapped, but before instantiation or any custom bindings.
/// If this flag is invoked, the developer must call context.Start()
MANUAL_MAPPING = 1,
/// Context startup will halt after all bindings are mapped, but before firing ContextEvent.START (or the analogous Signal).
/// If this flag is invoked, the developer must call context.Launch()
MANUAL_LAUNCH = 2,
}
}

@@ -0,0 +1,58 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.context.api.IContext
*
* A Context is the entry point to the binding framework.
*
* Implement this interface to create the binding context suitable for your application.
*
* In a typical Unity3D setup, an extension of MVCSContext should be instantiated from the ContextView.
*/

using System;
using strange.framework.api;
using strange.extensions.dispatcher.api;

namespace strange.extensions.context.api
{
public interface IContext : IBinder
{
/// Kicks off the internal Context binding/instantiation mechanisms
IContext Start();

/// Fires ContextEvent.START (or the equivalent Signal) to launch the application
void Launch();

/// Register a new context to this one
IContext AddContext(IContext context);

/// Remove a context from this one
IContext RemoveContext(IContext context);

/// Register a view with this context
void AddView(object view);

/// Remove a view from this context
void RemoveView(object view);

/// Get the ContextView
object GetContextView();

}
}

@@ -0,0 +1,41 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.context.api.IContextView
*
* The ContextView is the entry point to the application.
*
* In a standard MVCSContext setup for Unity3D, it is a MonoBehaviour
* attached to a GameObject at the very top of of your application.
* It's most important role is to instantiate and call `Start()` on the Context.
*/

using System;
using strange.extensions.mediation.api;
using Xamarin.Forms;

namespace strange.extensions.context.api
{
public interface IContextView
{
/// Get and set the Context
IContext context{get;set;}
Page MainPage { get; set; }

}
}

@@ -0,0 +1,57 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.context.api.ICrossContextCapable
*
* API for allowing Contexts to register across the Context border.
*
* Implement this interface to create a binding context that can communicate across Context boundaries.
*
* MVCSContext inherits CrossContext to obtain important capabilities, such as shared injections.
*
* @see strange.extensions.injector.api.IInjectionBinding
*/

using strange.extensions.dispatcher.api;
using strange.extensions.injector.api;

namespace strange.extensions.context.api
{
public interface ICrossContextCapable
{
/// Add cross-context functionality to a child context being added
void AssignCrossContext(ICrossContextCapable childContext);

/// Clean up cross-context functionality from a child context being removed
void RemoveCrossContext(ICrossContextCapable childContext);

/// Request a component from the context (might be useful in certain cross-context situations)
/// This is technically a deprecated methodology. Bind using CrossContext() instead.
object GetComponent<T>();

/// Request a component from the context (might be useful in certain cross-context situations)
/// This is technically a deprecated methodology. Bind using CrossContext() instead.
object GetComponent<T>(object name);

/// All cross-context capable contexts must implement an injectionBinder
ICrossContextInjectionBinder injectionBinder { get; set; }

/// Set and get the shared system bus for communicating across contexts
IDispatcher crossContextDispatcher { get; set; }

}
}
@@ -0,0 +1,173 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.context.impl.Context
*
* A Context is the entry point to the binding framework.
*
* Extend this class to create the binding context suitable
* for your application.
*
* In a typical Unity3D setup, extend MVCSContext and instantiate
* your extension from the ContextView.
*/

using strange.extensions.context.api;
using strange.framework.impl;

namespace strange.extensions.context.impl
{
public class Context : Binder, IContext
{
/// The top of the View hierarchy.
/// In MVCSContext, this is your top-level GameObject
public object contextView { get; set; }

/// In a multi-Context app, this represents the first Context to instantiate.
public static IContext firstContext;

/// If false, the `Launch()` method won't fire.
public bool autoStartup;

public Context()
{
}

public Context(object view, ContextStartupFlags flags)
{
//If firstContext was unloaded, the contextView will be null. Assign the new context as firstContext.
if (firstContext == null || firstContext.GetContextView() == null)
{
firstContext = this;
}
else
{
firstContext.AddContext(this);
}
SetContextView(view);
addCoreComponents();
this.autoStartup = (flags & ContextStartupFlags.MANUAL_LAUNCH) != ContextStartupFlags.MANUAL_LAUNCH;
if ((flags & ContextStartupFlags.MANUAL_MAPPING) != ContextStartupFlags.MANUAL_MAPPING)
{
Start();
}
}

public Context(object view) : this(view, ContextStartupFlags.AUTOMATIC) { }

public Context(object view, bool autoMapping) : this(view, (autoMapping) ? ContextStartupFlags.MANUAL_MAPPING : ContextStartupFlags.MANUAL_LAUNCH | ContextStartupFlags.MANUAL_MAPPING)
{
}

/// Override to add componentry. Or just extend MVCSContext.
virtual protected void addCoreComponents()
{
}

/// Override to instantiate componentry. Or just extend MVCSContext.
virtual protected void instantiateCoreComponents()
{
}

/// Set the object that represents the top of the Context hierarchy.
/// In MVCSContext, this would be a GameObject.
virtual public IContext SetContextView(object view)
{
contextView = view;
return this;
}

virtual public object GetContextView()
{
return contextView;
}

/// Call this from your Root to set everything in action.
virtual public IContext Start()
{
instantiateCoreComponents();
mapBindings();
postBindings();
if (autoStartup)
Launch();
return this;
}

/// The final method to fire after mappings.
/// If autoStartup is false, you need to call this manually.
virtual public void Launch()
{
}

/// Override to map project-specific bindings
virtual protected void mapBindings()
{
}

/// Override to do things after binding but before app launch
virtual protected void postBindings()
{
}

/// Add another Context to this one.
virtual public IContext AddContext(IContext context)
{
return this;
}

/// Remove a context from this one.
virtual public IContext RemoveContext(IContext context)
{
//If we're removing firstContext, set firstContext to null
if (context == firstContext)
{
firstContext = null;
}
else
{
context.OnRemove();
}
return this;
}

/// Retrieve a component from this Context by generic type
virtual public object GetComponent<T>()
{
return null;
}


/// Retrieve a component from this Context by generic type and name
virtual public object GetComponent<T>(object name)
{
return null;
}

/// Register a View with this Context
virtual public void AddView(object view)
{
//Override in subclasses
}

/// Remove a View from this Context
virtual public void RemoveView(object view)
{
//Override in subclasses
}
}
}

@@ -0,0 +1,45 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.context.impl.ContextException
*
* An exception raised by the Context system.
*
* @see strange.extensions.context.api.ContextExceptionType
*/

using System;
using strange.extensions.context.api;

namespace strange.extensions.context.impl
{
public class ContextException : Exception
{
public ContextExceptionType type{ get; set;}

public ContextException () : base()
{
}

/// Constructs a ContextException with a message and ContextExceptionType
public ContextException(string message, ContextExceptionType exceptionType) : base(message)
{
type = exceptionType;
}
}
}

@@ -0,0 +1,173 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.context.impl.CrossContext
*
* Provides the capabilities that allow a Context to communicate across
* the Context boundary. Specifically, CrossContext provides
* - A CrossContextInjectionBinder that allows injections to be shared cross-context
* - An EventDispatcher that allows messages to be sent between Contexts
* - Methods (the ICrossContextCapable API) for adding and removing the hooks between Contexts.
*/

using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.dispatcher.eventdispatcher.impl;
using strange.extensions.context.api;
using strange.extensions.dispatcher.api;
using strange.extensions.injector.api;
using strange.extensions.injector.impl;
using strange.framework.api;

namespace strange.extensions.context.impl
{
public class CrossContext : Context, ICrossContextCapable
{
private ICrossContextInjectionBinder _injectionBinder;
private IBinder _crossContextBridge;

/// A Binder that handles dependency injection binding and instantiation
public ICrossContextInjectionBinder injectionBinder
{
get { return _injectionBinder ?? (_injectionBinder = new CrossContextInjectionBinder()); }
set { _injectionBinder = value; }
}

/// A specific instance of EventDispatcher that communicates
/// across multiple contexts. An event sent across this
/// dispatcher will be re-dispatched by the various context-wide
/// dispatchers. So a dispatch to other contexts is simply
///
/// `crossContextDispatcher.Dispatch(MY_EVENT, payload)`;
///
/// Other contexts don't need to listen to the cross-context dispatcher
/// as such, just map the necessary event to your local context
/// dispatcher and you'll receive it.
protected IEventDispatcher _crossContextDispatcher;


public CrossContext() : base()
{}

public CrossContext(object view) : base(view)
{
}

public CrossContext(object view, ContextStartupFlags flags) : base(view, flags)
{
}

public CrossContext(object view, bool autoMapping) : base(view, autoMapping)
{
}

protected override void addCoreComponents()
{
base.addCoreComponents();
if (injectionBinder.CrossContextBinder == null) //Only null if it could not find a parent context / firstContext
{
injectionBinder.CrossContextBinder = new CrossContextInjectionBinder();
}

if (firstContext == this)
{
injectionBinder.Bind<IEventDispatcher>().To<EventDispatcher>().ToSingleton().ToName(ContextKeys.CROSS_CONTEXT_DISPATCHER).CrossContext();
injectionBinder.Bind<CrossContextBridge> ().ToSingleton ().CrossContext();
}

}

protected override void instantiateCoreComponents()
{
base.instantiateCoreComponents();

IInjectionBinding dispatcherBinding = injectionBinder.GetBinding<IEventDispatcher> (ContextKeys.CONTEXT_DISPATCHER);

if (dispatcherBinding != null) {
IEventDispatcher dispatcher = injectionBinder.GetInstance<IEventDispatcher> (ContextKeys.CONTEXT_DISPATCHER) as IEventDispatcher;

if (dispatcher != null) {
crossContextDispatcher = injectionBinder.GetInstance<IEventDispatcher> (ContextKeys.CROSS_CONTEXT_DISPATCHER) as IEventDispatcher;
(crossContextDispatcher as ITriggerProvider).AddTriggerable (dispatcher as ITriggerable);
(dispatcher as ITriggerProvider).AddTriggerable (crossContextBridge as ITriggerable);
}
}
}

override public IContext AddContext(IContext context)
{
base.AddContext(context);
if (context is ICrossContextCapable)
{
AssignCrossContext((ICrossContextCapable)context);
}
return this;
}

virtual public void AssignCrossContext(ICrossContextCapable childContext)
{
childContext.crossContextDispatcher = crossContextDispatcher;
childContext.injectionBinder.CrossContextBinder = injectionBinder.CrossContextBinder;
}

virtual public void RemoveCrossContext(ICrossContextCapable childContext)
{
if (childContext.crossContextDispatcher != null)
{
((childContext.crossContextDispatcher) as ITriggerProvider).RemoveTriggerable(childContext.GetComponent<IEventDispatcher>(ContextKeys.CONTEXT_DISPATCHER) as ITriggerable);
childContext.crossContextDispatcher = null;
}
}

override public IContext RemoveContext(IContext context)
{
if (context is ICrossContextCapable)
{
RemoveCrossContext((ICrossContextCapable)context);
}
return base.RemoveContext(context);
}

virtual public IDispatcher crossContextDispatcher
{
get
{
return _crossContextDispatcher;
}
set
{
_crossContextDispatcher = value as IEventDispatcher;
}
}

virtual public IBinder crossContextBridge
{
get
{
if (_crossContextBridge == null)
{
_crossContextBridge = injectionBinder.GetInstance<CrossContextBridge> () as IBinder;
}
return _crossContextBridge;
}
set
{
_crossContextDispatcher = value as IEventDispatcher;
}
}

}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @class strange.extensions.context.impl.CrossContextBridge
*
* A relay for events mapped across multiple Contexts.
*
* This simple class gates events fired by the local Context-wide EventDispatcher.
* Any event trigger mapped to this Binder will be relayed to the CrossContextDispatcher
* for consumption by others. This removes the necessity to ever inject the CrossContextDispatcher
* at an endpoint (e.g., a Command or Mediator).
*
* Because the Bridge itself is mapped cross-context (and
* therefore shared), it is up to the developer to decide where to make cross-Context the mappings.
*
* This "freedom" is also a potential pitfall; we recommend that you map all Cross-Context
* events in firstContext to avoid confusion.
*
* Example:
crossContextBridge.Bind(GameEvent.MISSILE_HIT);
* By doing this from any Context in your app, any Context Dispatcher that fires `GameEvent.MISSILE_HIT` will
* relay that Event to other Contexts.
*/

using System;
using strange.extensions.dispatcher.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.context.api;
using strange.framework.api;
using strange.framework.impl;
using System.Collections.Generic;

namespace strange.extensions.context.impl
{
public class CrossContextBridge : Binder, ITriggerable
{
[Inject(ContextKeys.CROSS_CONTEXT_DISPATCHER)]
public IEventDispatcher crossContextDispatcher{ get; set;}

/// Prevents the currently dispatching Event from cycling back on itself
protected HashSet<object> eventsInProgress = new HashSet<object>();

public CrossContextBridge ()
{
}

override public IBinding Bind(object key)
{
IBinding binding;
binding = GetRawBinding ();
binding.Bind(key);
resolver (binding);
return binding;
}

#region ITriggerable implementation

public bool Trigger<T> (object data)
{
return Trigger (typeof(T), data);
}

public bool Trigger (object key, object data)
{
IBinding binding = GetBinding (key, null);
if (binding != null && !eventsInProgress.Contains(key))
{
eventsInProgress.Add (key);
crossContextDispatcher.Dispatch (key, data);
eventsInProgress.Remove (key);
}
return true;
}

#endregion
}
}

@@ -0,0 +1 @@
/* * Copyright 2013 ThirdMotion, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *//** * @class strange.extensions.context.impl.MVCSContext * * The recommended Context for getting the most out of StrangeIoC. * * By extending this Context, you get the entire * all-singing/all-dancing version of Strange, as it was shipped from the * warehouse and ready for you to map your dependencies. * * As the name suggests, MVCSContext provides structure for * app development using the classic <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC (Model-View-Controller)</a> * design pattern, and adds 'S' (Service) for asynchronous calls outside * the application. Strange is highly modular, so you needn't use * MVCSContext if you don't want to (you can extend Context or CrossContext directly) * but MVCS is a highly proven design strategy and MVCSContext is by far the easiest * way to get familiar with what Strange has to offer. * * The parts: * <ul> * <li>contextView</li> * * The GameObject at the top of your display hierarchy. Attach a subclass of * ContextView to a GameObject, then instantiate a subclass of MVCSContext * to start the app. * * Example: public class MyProjectRoot : ContextView { void Awake() { context = new MyContext(this); //Extends MVCSContext } } * * The contextView is automatically injected into all Mediators * and available for injection into commands like so: [Inject(ContextKeys.CONTEXT_VIEW)] public GameObject contextView{get;set;} * It is strongly advised that the contextView NOT be injected into * Views, Models or Services. * * <li>injectionBinder</li> * * Maps dependencies to concrete classes or values. * * Examples: injectionBinder.Bind<ISpaceship>().To<TieFighter>(); //Injects a new TieFighter wherever an ISpaceship is requested injectionBinder.Bind<ISpaceship>().To<Starship>().ToName(Ships.ENTERPRISE); //Injects a Starship wherever an ISpaceship is requested with the Name qualifier Enterprise injectionBinder.Bind<ITool>().To<SonicScrewdriver>().ToSingleton(); //Injects SonicScrewdriver as a Singleton wherever an ITool is requested injectionBinder.Bind<IMoonbase>().ToValue(new Alpha()); //Injects the provided instance wherever IMoonbase is requested injectionBinder.Bind<ISpaceship>().Bind<ITimeShip>.To<Tardis>(); //Injects a new Tardis wherever EITHER ISpaceship or ITimeship is requested. * `injectionBinder` is automatically injected into all commands and may * be injected elsewhere with: [Inject] public IInjectionBinder injectionBinder{ get; set;} * <li>dispatcher</li> * * The event bus shared across the context. Informs listeners * and triggers commands. * * `dispatcher` is injected into all EventMediators, EventCommands * and EventSequenceCommands, and may be injected elsewhere with: [Inject(ContextKeys.CONTEXT_DISPATCHER)] public IEventDispatcher dispatcher{ get; set;} * For examples, see IEventDispatcher. Generally you don't map the dispatcher's * events to methods inside the Context. Rather, you map Commands and Sequences. * Read on! * * <li>crossContextDispatcher</li> * * A second event bus for sending events between contexts. It * should only be accessed from Commands or SequenceCommands, * into which it may be injected by declaring the dependency: [Inject(ContextKeys.CROSS_CONTEXT_DISPATCHER)] public IEventDispatcher dispatcher{ get; set;} * * <li>commandBinder</li> * * Maps events that result in the creation and execution of Commands. * Events from dispatcher can be used to trigger Commands. * * `commandBinder` is automatically injected into all Commands. * * Examples: commandBinder.Bind(GameEvent.MISSILE_HIT).To<MissileHitCommand>(); //MissileHitCommand fires whenever MISSILE_HIT is dispatched commandBinder.Bind(GameEvent.MISSILE_HIT).To<IncrementScoreCommand>().To<UpdateServerCommand>(); //Both Commands fire commandBinder.Bind(ContextEvent.START).To<StartCommand>().Once(); //StartCommand fires when START fires, then unmaps itself * * <li>sequencer</li> * * Maps events that result in the creation and execution of Sequences, * which are just like Commands, except they run sequentially, rather than * in parallel. * * 'sequencer' is automatically injected into all SequenceCommands. * * In the following example, `TestMissileHitCommand` runs logic to determine * whether the missile hit is valid. If it's not, it may call `BreakSeqeunce()`. * so neither of the other Commands will fire. sequencer.Bind(GameEvent.MISSILE_HIT).To<TestMissileHitCommand>().To<IncrementScoreCommand>().To<UpdateServerCommand>(); * * <li>mediationBinder</li> * * Maps Views to Mediators in order to insultate Views from direct * linkage to the application. * * MediationBinder isn't automatically injected anywhere. It is * possible, however, that you might want to change mediation bindings * at runtime. This might prove difficult as a practical matter, but * if you want to experiment, feel free to inject `mediationBinder` * into Commands or SequenceCommands like so: [Inject] IMediationBinder mediationBinder{get;set;} * * Example: mediationBinder.Bind<RobotView>().To<RobotMediator>(); * <ul> * * */using System.Diagnostics;using strange.extensions.implicitBind.api;//using strange.extensions.implicitBind.impl;using strange.extensions.command.api;using strange.extensions.command.impl;using strange.extensions.context.api;using strange.extensions.dispatcher.api;using strange.extensions.dispatcher.eventdispatcher.api;using strange.extensions.dispatcher.eventdispatcher.impl;using strange.extensions.injector.api;using strange.extensions.mediation.api;using strange.extensions.mediation.impl;using strange.extensions.sequencer.api;using strange.extensions.sequencer.impl;using strange.framework.api;using strange.framework.impl;using StrangeIOC.extensions.context.impl;namespace strange.extensions.context.impl{ public class XamarinContext : CrossContext { /// A Binder that maps Events to Commands public ICommandBinder commandBinder{get;set;} /// A Binder that serves as the Event bus for the Context public IEventDispatcher dispatcher{get;set;} /// A Binder that maps Views to Mediators public IMediationBinder mediationBinder{get;set;} //Interprets implicit bindings// public IImplicitBinder implicitBinder { get; set; } /// A Binder that maps Events to Sequences public ISequencer sequencer{get;set;} /// A list of Views Awake before the Context is fully set up. protected static ISemiBinding viewCache = new SemiBinding(); public XamarinContext(XamarinContextView view) : base(view) {} /// Map the relationships between the Binders. /// Although you can override this method, it is recommended /// that you provide all your application bindings in `mapBindings()`. protected override void addCoreComponents() { Debug.WriteLine("XamarinContext::addingCoreComponents"); base.addCoreComponents(); injectionBinder.Bind<IInstanceProvider>().Bind<IInjectionBinder>().ToValue(injectionBinder); injectionBinder.Bind<IContext>().ToValue(this).ToName(ContextKeys.CONTEXT); injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton(); //This binding is for local dispatchers injectionBinder.Bind<IEventDispatcher>().To<EventDispatcher>();// //This binding is for the common system bus injectionBinder.Bind<IEventDispatcher>().To<EventDispatcher>().ToSingleton().ToName(ContextKeys.CONTEXT_DISPATCHER); injectionBinder.Bind<IMediationBinder>().To<MediationBinder>().ToSingleton(); injectionBinder.Bind<ISequencer>().To<EventSequencer>().ToSingleton();// injectionBinder.Bind<IImplicitBinder>().To<ImplicitBinder>().ToSingleton(); injectionBinder.Bind<IContextView>().Bind<XamarinContextView>().ToValue(contextView);//.ToName(ContextKeys.CONTEXT_VIEW); } protected override void instantiateCoreComponents() { Debug.WriteLine("XamarinContext::instantiateCoreComponents"); base.instantiateCoreComponents(); commandBinder = injectionBinder.GetInstance<ICommandBinder>() as ICommandBinder; dispatcher = injectionBinder.GetInstance<IEventDispatcher>(ContextKeys.CONTEXT_DISPATCHER) as IEventDispatcher; mediationBinder = injectionBinder.GetInstance<IMediationBinder>() as IMediationBinder; sequencer = injectionBinder.GetInstance<ISequencer>() as ISequencer;// implicitBinder = injectionBinder.GetInstance<IImplicitBinder>() as IImplicitBinder; (dispatcher as ITriggerProvider).AddTriggerable(commandBinder as ITriggerable); (dispatcher as ITriggerProvider).AddTriggerable(sequencer as ITriggerable); } /// Fires ContextEvent.START /// Whatever Command/Sequence you want to happen first should /// be mapped to this event. public override void Launch() { dispatcher.Dispatch(ContextEvent.START); } /// Gets an instance of the provided generic type. /// Always bear in mind that doing this risks adding /// dependencies that must be cleaned up when Contexts /// are removed. override public object GetComponent<T>() { return GetComponent<T>(null); } /// Gets an instance of the provided generic type and name from the InjectionBinder /// Always bear in mind that doing this risks adding /// dependencies that must be cleaned up when Contexts /// are removed. override public object GetComponent<T>(object name) { IInjectionBinding binding = injectionBinder.GetBinding<T>(name); if (binding != null) { return injectionBinder.GetInstance<T>(name); } return null; } /// Clean up. Called by a ContextView in its OnDestroy method public override void OnRemove() { base.OnRemove(); commandBinder.OnRemove(); } }}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime;
using strange.extensions.context.api;
using strange.extensions.context.impl;
using strange.extensions.mediation.api;
using Xamarin.Forms;

namespace StrangeIOC.extensions.context.impl
{
public class XamarinContextView : Application, IContextView
{
public new Page MainPage
{
get { return base.MainPage; }
set
{
if (base.MainPage != null)
{
((XamarinContext)context).mediationBinder.Trigger(MediationEvent.DESTROYED, base.MainPage);
}
base.MainPage = value;
((XamarinContext)context).mediationBinder.Trigger(MediationEvent.AWAKE, value);
}
}

public bool requiresContext { get; set; }
public bool registeredWithContext { get; set; }
public bool autoRegisterWithContext { get; }
public IContext context { get; set; }
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.dispatcher.api
{
public enum DispatcherExceptionType
{
/// Injector Factory not found
NULL_FACTORY,

/// Callback must be a Delegate with zero or one argument
ILLEGAL_CALLBACK_HANDLER
}
}

@@ -0,0 +1,41 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.api.IDispatcher
*
* A Dispatcher sends notifiations to any registered listener.
* It represents the subject in a standard Observer pattern.
*
* In MVCSContext the dispatched notification is an IEvent.
*/

using System;

namespace strange.extensions.dispatcher.api
{
public interface IDispatcher
{
/// Send a notification of type eventType. No data.
/// In MVCSContext this dispatches an IEvent.
void Dispatch (object eventType);

/// Send a notification of type eventType and the provided data payload.
/// In MVCSContext this dispatches an IEvent.
void Dispatch (object eventType, object data);
}
}

@@ -0,0 +1,47 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.api.ITriggerProvider
*
* Interface for declaring a class capable of triggering an ITriggerable class.
*
* Objects implementing a TriggerProvider declare themselves able to
* provide triggering to any ITriggerable. The contract specifies that
* TriggerProvider will pass events on to the Triggerable class.
* This allows notifications, such as IEvents, to pass through
* the event bus and trigger other binders.
*
* @see strange.extensions.dispatcher.api.ITriggerable
*/

using System;

namespace strange.extensions.dispatcher.api
{
public interface ITriggerProvider
{
/// Register a Triggerable client with this provider.
void AddTriggerable(ITriggerable target);

/// Remove a previously registered Triggerable client from this provider.
void RemoveTriggerable(ITriggerable target);

/// Count of the current number of trigger clients.
int Triggerables{ get;}
}
}

@@ -0,0 +1,45 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.api.ITriggerable
*
* Interface for declaring a class capable of being triggered by a provided key and/or name.
*
* Objects implementing ITriggerable can register with objects implementing
* ITriggerProvider. The contract specifies that TriggerProvider will
* pass events on to the Triggerable class. This allows notifications,
* such as IEvents to pass through the event bus and trigger other binders.
*
* @see strange.extensions.dispatcher.api.ITriggerProvider
*/

using System;

namespace strange.extensions.dispatcher.api
{
public interface ITriggerable
{
/// Cause this ITriggerable to access any provided Key in its Binder by the provided generic and data.
/// <returns>false if the originator should abort dispatch</returns>
bool Trigger<T>(object data);

/// Cause this ITriggerable to access any provided Key in its Binder by the provided key and data.
/// <returns>false if the originator should abort dispatch</returns>
bool Trigger(object key, object data);
}
}

@@ -0,0 +1,31 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.dispatcher.eventdispatcher.api
{
public enum EventCallbackType
{
/// Indicates an EventCallback with no arguments
NO_ARGUMENTS,
/// Indicates an EventCallback with one argument
ONE_ARGUMENT,
/// Indicates no matching EventCallback could be found
NOT_FOUND
}
}

@@ -0,0 +1,33 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace strange.extensions.dispatcher.eventdispatcher.api
{
public enum EventDispatcherExceptionType
{
/// Indicates that an event was fired with null as the key.
EVENT_KEY_NULL,

/// Indicates that the type of Event in the call and the type of Event in the payload don't match.
EVENT_TYPE_MISMATCH,

/// When attempting to fire a callback, the callback was discovered to be casting illegally.
TARGET_INVOCATION
}
}

@@ -0,0 +1,39 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.eventdispatcher.api.IEvent
*
* The interface for an event sent by the EventDispatcher
*/

using System;

namespace strange.extensions.dispatcher.eventdispatcher.api
{
public interface IEvent
{
/// The Event key
object type{ get; set; }

/// The IEventDispatcher that sent the event
IEventDispatcher target{ get; set; }

/// An arbitrary data payload
object data{ get; set; }
}
}

@@ -0,0 +1,57 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.eventdispatcher.api.IEventBinding
*
* Binding interface for EventDispatcher.
*
* EventBindings technically allow any Key, but require either an
* EmptyCallback (no arguments) or an EventCallback (one argument).
*
* The IEvent only accepts strings as keys, so in the standard MVCSContext
* setup, your EventBinder keys should also be strings.
*
* @see strange.extensions.dispatcher.eventdispatcher.api.IEvent
*/

using System;
using strange.framework.api;

namespace strange.extensions.dispatcher.eventdispatcher.api
{
/// Delegate for adding a listener with a single argument
public delegate void EventCallback(IEvent payload);

/// Delegate for adding a listener with a no arguments
public delegate void EmptyCallback();

public interface IEventBinding : IBinding
{
/// Retrieve the type of the provided callback
EventCallbackType TypeForCallback (EventCallback callback);

/// Retrieve the type of the provided callback
EventCallbackType TypeForCallback (EmptyCallback callback);

new IEventBinding Bind (object key);
IEventBinding To (EventCallback callback);
IEventBinding To (EmptyCallback callback);


}
}

@@ -0,0 +1,72 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.eventdispatcher.api.IEventDispatcher
*
* Interface for allowing a client to register as an observer.
*
* EventDispatcher allows a client to register as an observer. Whenever the
* Dispatcher executes a `Dispatch()`, observers will be notified of any event
* (Key) for which they have registered.
*
* EventDispatcher dispatches IEvents.
*
* The EventDispatcher is the only Dispatcher currently released with Strange
* (though by separating EventDispatcher from Dispatcher I'm obviously
* signalling that I don't think it's the only possible one).
*
* @see strange.extensions.dispatcher.eventdispatcher.api.IEvent
*/

using System;
using strange.extensions.dispatcher.api;

namespace strange.extensions.dispatcher.eventdispatcher.api
{
public interface IEventDispatcher : IDispatcher
{
IEventBinding Bind(object key);

/// Add an observer with exactly one argument to this Dispatcher
void AddListener(object evt, EventCallback callback);

/// Add an observer with exactly no arguments to this Dispatcher
void AddListener(object evt, EmptyCallback callback);

/// Remove a previously registered observer with exactly one argument from this Dispatcher
void RemoveListener(object evt, EventCallback callback);

/// Remove a previously registered observer with exactly no arguments from this Dispatcher
void RemoveListener(object evt, EmptyCallback callback);

/// Returns true if the provided observer is already registered
bool HasListener(object evt, EventCallback callback);

/// Returns true if the provided observer is already registered
bool HasListener(object evt, EmptyCallback callback);

/// By passing true, an observer with exactly one argument will be added to this Dispatcher
void UpdateListener(bool toAdd, object evt, EventCallback callback);

/// By passing true, an observer with exactly no arguments will be added to this Dispatcher
void UpdateListener(bool toAdd, object evt, EmptyCallback callback);

/// Allow a previously retained event to be returned to its pool
void ReleaseEvent(IEvent evt);
}
}

@@ -0,0 +1,126 @@
/*
* Copyright 2013 ThirdMotion, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @interface strange.extensions.dispatcher.eventdispatcher.impl.EventBinding
*
* A Binding for the EventDispatcher.
*
* EventBindings technically allow any Key, but require either an
* EmptyCallback (no arguments) or an EventCallback (one argument).
*
* @see strange.extensions.dispatcher.eventdispatcher.api.IEvent
*/

using System;
using System.Collections.Generic;
using System.Reflection;
using strange.extensions.dispatcher.api;
using strange.extensions.dispatcher.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.framework.api;
using strange.framework.impl;

namespace strange.extensions.dispatcher.eventdispatcher.impl
{
public class EventBinding : Binding, IEventBinding
{
private Dictionary<Delegate, EventCallbackType> callbackTypes;

public EventBinding () : this(null)
{
}

public EventBinding (strange.framework.impl.Binder.BindingResolver resolver) : base(resolver)
{
keyConstraint = BindingConstraintType.ONE;
valueConstraint = BindingConstraintType.MANY;
callbackTypes = new Dictionary<Delegate, EventCallbackType> ();
}

public EventCallbackType TypeForCallback(EmptyCallback callback)
{
if (callbackTypes.ContainsKey (callback))
{
return callbackTypes [callback];
}
return EventCallbackType.NOT_FOUND;
}

public EventCallbackType TypeForCallback(EventCallback callback)
{
if (callbackTypes.ContainsKey (callback))
{
return callbackTypes [callback];
}
return EventCallbackType.NOT_FOUND;
}

new public IEventBinding Bind(object key)
{
return base.Bind (key) as IEventBinding;
}

public IEventBinding To(EventCallback value)
{
base.To (value);
storeMethodType(value as Delegate);
return this;
}

public IEventBinding To(EmptyCallback value)
{
base.To (value);
storeMethodType(value as Delegate);
return this;
}

new public IEventBinding To(object value)
{
base.To (value);
storeMethodType(value as Delegate);
return this;
}

override public void RemoveValue(object value)
{
base.RemoveValue (value);
callbackTypes.Remove (value as Delegate);
}

private void storeMethodType(Delegate value)
{
if (value == null)
{
throw new DispatcherException ("EventDispatcher can't map something that isn't a delegate'", DispatcherExceptionType.ILLEGAL_CALLBACK_HANDLER);
}
MethodInfo methodInfo = value.GetMethodInfo();
int argsLen = methodInfo.GetParameters ().Length;
switch(argsLen)
{
case 0:
callbackTypes[value] = EventCallbackType.NO_ARGUMENTS;
break;
case 1:
callbackTypes[value] = EventCallbackType.ONE_ARGUMENT;
break;
default:
throw new DispatcherException ("Event callbacks must have either one or no arguments", DispatcherExceptionType.ILLEGAL_CALLBACK_HANDLER);
}
}
}
}