| @@ -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,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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|