using System;
using System.Collections.Generic;
namespace Fadd.Commands
{
/// <summary>
/// <para>
/// This dispatcher is used to be able to specify which commands that can be invoked.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// A typical usage would be if you have a plugin system and you do not want to allow
/// all plugins to be able to execute all commands. Then you create a proxy dispatcher
/// and expose it to the plugins, instead of the core dispatcher which contains all command
/// mappings. In this way, your core system it still protected while plugins can access
/// a subset of it.
/// </para>
/// </remarks>
public class ProxyDispatcher : ICommandDispatcher
{
private static readonly List<Type> EmptyList = new List<Type>();
private readonly ICommandDispatcher _dispatcher;
private readonly List<Type> _executeCommands;
private readonly List<Type> _handledCommands;
private readonly ICommandDispatcher _parent;
/// <summary>
/// Occurs when an unhandled exception have been caught.
/// </summary>
public event UnhandledExceptionDelegate UnhandledExceptionThrown;
/// <summary>
/// This event is invoked when someone tries to add a handler for a command.
/// </summary>
public event ProxyHandler AddRequested;
/// <summary>
/// This event is invoked when someone tries to invoke a command.
/// </summary>
public event ProxyHandler InvokeRequested;
/// <summary>
/// Initializes a new instance of the <see cref="ProxyDispatcher"/> class.
/// </summary>
/// <param name="parent">Parent dispatcher (that is used by the rest of the system).</param>
/// <param name="executeCommands">Commands that may be invoked.</param>
/// <param name="handledCommands">Commands that may be handled.</param>
public ProxyDispatcher(ICommandDispatcher parent, List<Type> executeCommands, List<Type> handledCommands)
{
if (parent == null)
throw new ArgumentNullException("parent");
if (executeCommands == null)
throw new ArgumentNullException("executeCommands");
if (handledCommands == null)
throw new ArgumentNullException("handledCommands");
_parent = parent;
_dispatcher = new CommandManager(null, false);
_executeCommands = executeCommands;
_handledCommands = handledCommands;
}
/// <summary>
/// Initializes a new instance of the <see cref="ProxyDispatcher"/> class.
/// </summary>
/// <param name="parent">Parent dispatcher (that is used by the rest of the system).</param>
/// <remarks>No commands are allowed unless you use the <see cref="InvokeRequested"/> event.</remarks>
public ProxyDispatcher(ICommandDispatcher parent)
: this(parent, EmptyList, EmptyList)
{
}
#region ICommandDispatcher Members
/// <summary>
/// Add a command handler.
/// </summary>
/// <param name="type">Must be a class, an attribute.</param>
/// <param name="handler">handler handling the command</param>
/// <exception cref="ArgumentException">If handler have been added to that type already.</exception>
/// <exception cref="InvalidOperationException">The specified command may not be handled.</exception>
/// <example>
/// Program.Commands.Add(typeof(MyCommand), OnMyCommand);
/// </example>
public void Add(Type type, CommandHandler handler)
{
if (!typeof (Command).IsAssignableFrom(type))
throw new ArgumentException("Only commands may be added in this implementation.");
if (AddRequested != null)
AddRequested(this, new ProxyHandlerEventArgs(type));
else if (!_handledCommands.Contains(type))
throw new InvalidOperationException("You may not handle that command.");
bool exists = _dispatcher.Contains(type);
// Add the handler
_dispatcher.Add(type, handler);
// Add it to our real system wide dispatcher if it has not been added.
if (!exists)
_parent.Add(type, OnCommand);
}
/// <summary>
/// Remove a command handler.
/// </summary>
/// <param name="type">type to remove</param>
/// <param name="handler">delegated that was mapped to the type.</param>
public void Remove(Type type, CommandHandler handler)
{
_dispatcher.Remove(type, handler);
// Unhook this proxy if no handlers are left.
if (!_dispatcher.Contains(type))
_parent.Remove(type, OnCommand);
}
/// <summary>
/// Invoke a command.
/// </summary>
/// <param name="source">object that is invoking the command.</param>
/// <param name="command">command to invoke.</param>
/// <param name="ignoreMe">Handled that should not be invoked.</param>
/// <returns>true if command was handled.</returns>
public bool Invoke(object source, Command command, CommandHandler ignoreMe)
{
Type type = command.GetType();
if (InvokeRequested != null)
InvokeRequested(this, new ProxyHandlerEventArgs(type));
else if (!_executeCommands.Contains(type))
throw new InvalidOperationException("Unauthorized attempt to invoke " + command.GetType().FullName + ".");
// we will be calling ourself too since we've hooked up all requested commands
return _parent.Invoke(source ?? this, command, ignoreMe);
}
/// <summary>
/// Invoke a command.
/// </summary>
/// <param name="command">command to invoke.</param>
/// <returns>true if command was handled.</returns>
/// <exception cref="InvalidOperationException">If you may not invoke the specified command.</exception>
public bool Invoke(Command command)
{
return Invoke(command, null);
}
/// <summary>
/// Invoke a command.
/// </summary>
/// <param name="command">command to invoke.</param>
/// <param name="ignoreMe">Handled that should not be invoked.</param>
/// <returns>true if command was handled.</returns>
public bool Invoke(Command command, CommandHandler ignoreMe)
{
return Invoke(null, command, ignoreMe);
}
/// <summary>
/// Invoke a command.
/// </summary>
/// <param name="source">object that is invoking the command.</param>
/// <param name="command">command to invoke.</param>
/// <returns>true if command was handled.</returns>
public bool Invoke(object source, Command command)
{
return Invoke(source, command, null);
}
/// <summary>
/// Invoke a command asynchronously
/// </summary>
/// <param name="source">object that is invoking the command.</param>
/// <param name="command">Command to invoke</param>
/// <param name="callback">Callback that is invoked then the command completes.</param>
/// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
/// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
/// <exception cref="InvalidOperationException">If you may not invoke the specified command.</exception>
public IAsyncResult BeginInvoke(object source, Command command, AsyncCallback callback, object state)
{
return BeginInvoke(source, command, null, callback, state);
}
/// <summary>
/// Invoke a command asynchronously
/// </summary>
/// <param name="command">Command to invoke</param>
/// <param name="source">object that is invoking the command.</param>
/// <param name="ignoreMe">Handler that should not receive the command.</param>
/// <param name="callback">Callback that is invoked then the command completes.</param>
/// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
/// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
public IAsyncResult BeginInvoke(object source, Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
{
Type type = command.GetType();
if (InvokeRequested != null)
InvokeRequested(this, new ProxyHandlerEventArgs(type));
else if (!_executeCommands.Contains(type))
throw new InvalidOperationException("Unauthorized attempt to invoke " + command.GetType().FullName + ".");
return _parent.BeginInvoke(source, command, ignoreMe, callback, state);
}
/// <summary>
/// Invoke a command asynchronously
/// </summary>
/// <param name="command">Command to invoke</param>
/// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
/// <param name="callback">Callback that is invoked then the command completes.</param>
/// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
public IAsyncResult BeginInvoke(Command command, AsyncCallback callback, object state)
{
return BeginInvoke(null, command, null, callback, state);
}
/// <summary>
/// Invoke a command asynchronously
/// </summary>
/// <param name="command">Command to invoke</param>
/// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
/// <param name="ignoreMe">Handler that should not receive the command.</param>
/// <param name="callback">Callback that is invoked then the command completes.</param>
/// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
public IAsyncResult BeginInvoke(Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
{
return BeginInvoke(null, command, ignoreMe, callback, state);
}
/// <summary>
/// Invoke this method when the command is complete, or if you want to wait
/// on the command.
/// </summary>
/// <param name="res"></param>
/// <returns></returns>
public Command EndInvoke(IAsyncResult res)
{
return _parent.EndInvoke(res);
}
/// <summary>
/// Tells us if we have a handler for the specified type.
/// </summary>
/// <param name="type">Type to check</param>
/// <returns>True if a handler have been registered otherwise false.</returns>
public bool Contains(Type type)
{
return _dispatcher.Contains(type) || _parent.Contains(type);
}
/// <summary>
/// Handler for unhandled commands.
/// </summary>
/// <remarks>returning true will make the command look like it was handled</remarks>
public event CommandHandler Unhandled
{
add { _dispatcher.Unhandled += value; }
remove { _dispatcher.Unhandled -= value; }
}
#endregion
private bool OnCommand(object source, CommandEventArgs args)
{
try
{
return _dispatcher.Invoke(source, args.Command);
}
catch (Exception err)
{
if (UnhandledExceptionThrown == null)
throw;
UnhandledExceptionThrown(this, new UnhandledExceptionEventArgs(err));
return false;
}
}
}
}