Tuesday, March 8, 2011

Caingorm as a plugin

Context: When developing a large app, we often want to make modules, or even fully self contained subsections of the app. The implemention of  classic cairngorm (< version 3.0) doesn't really allow for that.
I wanted to be able to create a userPreference package, a product package, and order package, etc that I could then easily merge together into the main app.

so this is what I want.

package examples.cairngorm
{
 import com.squaredi.frameworks.cairngorm.FrontControllerPlugin;

 public class MainFC extends FrontControllerPlugin
 {
  public function MainFC()
  {
   super("main");
   addController(new PlugInFC());
  }
 }
}
package examples.cairngorm
{
 import com.squaredi.frameworks.cairngorm.FrontControllerPlugin;
 
 public class PlugInFC extends FrontControllerPlugin
 {
  public function PlugInFC(id:String=null)
  {
   super("plugin");
   addCommand(MyCoolThingEvent.EVENT, MyCoolThingCommand);
  }
 }
}

So here is the class that I created to do that.

FrontControllerPlugIn.as
package com.squaredi.frameworks.cairngorm
{
 import com.adobe.cairngorm.CairngormError;
 import com.adobe.cairngorm.CairngormMessageCodes;
 import com.adobe.cairngorm.commands.ICommand;
 import com.adobe.cairngorm.control.CairngormEvent;
 import com.adobe.cairngorm.control.CairngormEventDispatcher;
 import com.adobe.cairngorm.control.FrontController;
 
 import flash.events.EventDispatcher;
 import flash.utils.Dictionary;
 
 import mx.logging.ILogger;
 import mx.logging.Log;
 import mx.utils.ObjectUtil;

 /**
  * This file is not likely to be edited. Please extend it for the various projects
  * 

  * It also has the ability to add other Controllers into itself to make a plugin type architecture via the addController method
  * 
  * As a plugin type architecture, there are use cases when a developer of a subcomponent or subproject might have a default implementation for and event 
  * (like show an alert for isolation testing), and know that the real implementation needs to be different. 
  * 
  * They should add the command via the addCommandToOverride method
  * 
  * */
 public class FrontControllerPlugin extends FrontController
 {
  protected static var logger:ILogger = Log.getLogger("com.squaredi.frameworks.cairngorm.FrontControllerPlugin")
  private var pluginControllers:Array /*FrontControllerPlugin*/ = new Array();
  
  private var cmdToPluginMap:Dictionary = new Dictionary(true);
  
  protected var overrideCommands:Dictionary = new Dictionary(true);
  
  private var id:String
  
  public function FrontControllerPlugin(id:String = null)
  {
   super();
   this.id = id;
   if (Log.isInfo())
   {
    logger.info("FrontControllerPlugin constructor: " + this.toString());
   }
  }
  
  
  
  /**
   * Adds all of the event command pairs already assocated with another frontController
   * This will throw an error if there are duplicated event names.
   * The expection to this is that if the duplicated event name is intended to be override
   * 
   * @param p
   * 
   */
  public function addController(p:FrontControllerPlugin):void
  {
   /*
   1) Recurse through all subcontrollers
   2) Add only new controllers
   3) Registger commands and deregister from added controllers
   */
   
   //Do subcontrollers
   if (p.pluginControllers.length >0)
   {
    //Register sub controllers first
    var len:int = pluginControllers.length;
    for (var i:int = 0; i < len; i++)
    {
     addController(p.pluginControllers[i]);
    }
    
   }
   
   //Find new Controllers
   if (findPlugin(p) < 0)
   {
    //Have not registered the controller yet
    pluginControllers.push(p);
    
    //Copy Override Items
    for (var clz:String in p.overrideCommands)
    {
     overrideCommands[clz] = p.overrideCommands[clz]; 
    }
    
    //Copy the commands in and remove them from the plugin
    var localcommands:Dictionary = p.commands;
    for (var cmd:String in localcommands)
    {
     //Make sure not to add commands twice either
     cmdToPluginMap[cmd] = p //Make sure to map the command before it gets added
     addCommand(cmd,localcommands[cmd],true);
     p.removeCommand(cmd);
    }
   }
   else
   {
    if (Log.isInfo()){logger.info(p.toString() + " has already been added");}
   }
  }
  
  protected function findPlugin(p:FrontControllerPlugin):int
  {
   var rtn:int = -1;
   var len:int = pluginControllers.length;
   for (var i:int = 0; i < len; i++)
   {
    if (pluginControllers[i].toString() == p.toString())
    {
     rtn = i;
     break;
    }
   }
   return rtn;
  }
  
  /**
   * 
   * Overrides the super class to allow for an error message that includes which plugin the conflict already exists in
   * 
   * @param commandName
   * @param commandRef
   * @param useWeakReference
   * 
   */
  public override function addCommand(commandName:String, commandRef:Class, useWeakReference:Boolean=true):void
  {
   //Add usefull infomation for the alert
   var pluginRef:FrontControllerPlugin = cmdToPluginMap[commandName];
   var pluginName:String = this.toString();
   if (pluginRef != null) { 
    pluginName = pluginRef.toString();
    cmdToPluginMap[commandName] = this;
   }
   
   var commandReference:Class = commands[ commandName ]
   if( commandReference != null)
   {
    //Verify that the command is not in the override list
    var overrideCommandReference:Class = overrideCommands[ commandName ]
    if (overrideCommandReference == null)
    {  
     throw new Error( "Command already registered for " + commandName +" in " + pluginName);
    }
    else
    {
     //We are indeed overriding a command
     if (Log.isInfo()) { logger.info(this.toString + " overriding a command")};
     
     //lets remove the old reference and add the new
     overrideCommands[ commandName ] = null;
     delete overrideCommands[ commandName ];
     
     cmdToPluginMap[commandName] = this;
     
    }
   }
   commands[ commandName ] = commandRef;
   CairngormEventDispatcher.getInstance().addEventListener( commandName, executeCommand, false, 0, useWeakReference );
  }
  
  /**
   * * As a plugin type architecture, there are use cases when a developer of a subcomponent or subproject might have a default implementation for and event 
   * (like show an alert for isolation testing), and know that the real implementation needs to be different. 
   * 
   * Adding command via this method will throw an error if this frontController gets added to another one AND the command is not redefined in the new one
   * 
   * */
  public function addCommandToOverride(commandName:String, commandRef:Class, useWeakReference:Boolean=true):void
  {
   var commandReference:Class = overrideCommands[ commandName ]
   if( commandReference != null)
   {
    var pluginRef:FrontControllerPlugin = cmdToPluginMap[commandName];
    var pluginName:String = this.toString();
    if (pluginRef != null) { 
     pluginName = pluginRef.toString();
     cmdToPluginMap[commandName] = this;
    }
    
    throw new Error( "Command already registered for override " + commandName +" in " + pluginName);
   }
   
   overrideCommands[ commandName ] = commandRef;
   
   addCommand(commandName, commandRef, useWeakReference);
   
  }
  
  protected function checkForOverrideCommands():void
  {
   //Copy Override Items
   var needsToBeOverridden:Array = new Array();
   for  (var clz:String in overrideCommands)
   {
    needsToBeOverridden.push(clz);
   }
   
   if (needsToBeOverridden.length >0)
   {
    throw new Error (this.toString() + " needs to override the following events: " + needsToBeOverridden.toString());
   }
   
  }
  
  internal function forceCheckForOverrideCommands():void
  {
   checkForOverrideCommands();
  }
  /**
   * 
   * Creates the command and temporarily adds it to the context so that we get dependancy injection
   * 
   * 
   */
  override protected function executeCommand( event : CairngormEvent ) : void
  {
   var commandToInitialise : Class = getCommand( event.type );
   var commandToExecute : ICommand = new commandToInitialise();
   
   if (Log.isInfo()) {logger.info("EVENT:" + event.type);}
   commandToExecute.execute( event );
   
  }
  
  internal function getCommandForTesting(commandName:String):Class
  {
   try
   {
    var cmd:Class = getCommand(commandName)
   }
   catch (e:Error)
   {
    //Ignore Cairngorm Errors for testing
   }
   return cmd;
  }
  
  /**
   * 
   * Returns the className for debugging purposes
   * 
   * @return 
   * 
   */
  public function toString():String
  {
   var classInfo:Object = ObjectUtil.getClassInfo(this);
   return classInfo.name + " (" + id + ")"
  }
  
  
 }
}

No comments:

Post a Comment