/**
 * This class provides aspect-oriented programming services for objects and classes in
 * JavaScript. For a description of aspect-oriented programming, see the 
 * <a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming">entry</a> in the 
 * Wikipedia. 
 * <br><br>
 * <a href="mailto:ehudsons@andrew.cmu.edu">Ellen Hudson-Snyder</a>, 
 * <a href="mailto:evedar@andrew.cmu.edu">Elvin Vedar</a>,
 * <a href="mailto:cbalz@andrew.cmu.edu">Christopher M. Balz</a>.
 * <br><br>CVS Version Info:<br>
 *  $Id: Aspect.js,v 1.5 2005/11/27 16:31:28 cbalz Exp $     
 * <br><br>
 * @class-prop <code>RXP_INSERT_BEFORE</code> A regular expression for inserting code into 
 *                                            a method body so that it executes before the former
 *                                             body of the method.
 * @class-prop <code>RXP_FUNCTION</code>  A very simple regular expression for matching the word, 'function'.
 * @class-prop <code>RXP_GET_FUNC_ARGS</code>  A non-greedy regular expression that will obtain the arguments 
 *                                             of a method.
 * @class-prop <code>RXP_INSERT_AFTER</code> A regular expression for inserting code so that it  
 *                                           exectues after the former body of a method.
 * @class-prop <code>RXP_INSERT_AT_TOP_OF_FIRST_BLOCK_IN_FUNCTION</code>  A regular expression for inserting 
 *                                                                        code just before the first lexical 
 *                                                                        block in the function or method.  
 * @object-prop <code>arrPointCuts</code>  <code>Array</code>  An array of specified point-cuts for a particular 
 *                                                             aspect.
 * @object-prop <code>hshAllowedWeaves</code>  <code>Object</code>  A hash specifying the allowed weave actions.   
 *                                                                  This is used for error checking.
 */

Aspect.RXP_INSERT_BEFORE = /function\s([a-zA-Z0-9]+)/; // match[1]
Aspect.RXP_FUNCTION = /function/;
Aspect.RXP_GET_FUNC_ARGS = /\((?:(?!\().)*\)/; // Only one match.
Aspect.RXP_INSERT_AFTER = /(return[^a-zA-Z0-9]+?[a-zA-Z0-9]+?\s*?;?)?\s*\}\s*;?\s*$/; 
Aspect.RXP_INSERT_AT_TOP_OF_FIRST_BLOCK_IN_FUNCTION = /^\s*function\s*([\w\W]*)\s*\(*\)\s*\{\s*/; 
Aspect.chopFunctionTop = Aspect_chopFunctionTop;


/**
 * Create an Aspect, the central element
 * of a software engineering paradigm, Aspect Oriented Programming, that complements object-oriented
 * software engineering.
 */
function Aspect() {
    // Methods:
    this.superC                    = Aspect_superConstructor;
    this.definePointCut            = Aspect_definePointCut;
    this.makeInterTypeDeclarations = Aspect_makeInterTypeDeclarations;
    this.insertBefore              = Aspect_insertBefore;
    this.insertIntoSwitchStmt      = Aspect_insertIntoSwitchStmt;
    this.insertAfter               = Aspect_insertAfter;                  
    this.weave                     = Aspect_weave;
    this.priCreateMethod           = Aspect_priCreateMethod;
}


/**
 * This method bestows unique object properties upon subclasses.
 */ 
function Aspect_superConstructor() {
    this.arrPointCuts      = [];    
    this.hshAllowedWeaves  = [];
    this.hshAllowedWeaves[ "makeInterTypeDeclarations" ] = this.makeInterTypeDeclarations;
    this.hshAllowedWeaves[ "insertBefore"              ] = this.insertBefore;
    this.hshAllowedWeaves[ "insertIntoSwitchStmt"      ] = this.insertIntoSwitchStmt;
    this.hshAllowedWeaves[ "insertAfter"               ] = this.insertAfter;    
}


/**
 * This method removes a function or method declaration and its 
 * parameters section, through its first opening brace. 
 * @param pFunction <code>function</code>  The function to operate upon.
 * @return <code>Array</code>  An array consisting of three strings:
 *                             the function name, if any; the function 
 *                             arguments (including the parentheses), and the non-top part 
 *                             of the function.
 */
function Aspect_chopFunctionTop( pFunction ) {
    // First, parse function beginning (to opening function curly brance):
    var strTemp, strFunNameIfAny, strFunArgsWithParens;

    strTemp = pFunction.toString();
    strFunNameIfAny = strTemp.match(Aspect.RXP_INSERT_BEFORE)[1];
    if (strFunNameIfAny == null) {
        strFunNameIfAny = "";
    }
    strFunArgsWithParens = strTemp.match(Aspect.RXP_GET_FUNC_ARGS);
    strTemp = strTemp.replace(Aspect.RXP_FUNCTION, "");
    strTemp = strTemp.replace(strFunNameIfAny, "");
    strTemp = strTemp.replace(strFunArgsWithParens, "");
    strTemp = strTemp.replace(/{/, "");
                    
    return [ strFunNameIfAny, strFunArgsWithParens, strTemp ];
}


/**
 * This method defines a place to make a change in an object at init time.
 * @param pObjObject <code>Object</code> The object to be woven into.
 * @param pStrWeaveAction <code>string</code> The weave action to perform.  Must be one of:
 * <ul>
 * <li>"makeInterTypeDeclarations"</li>
 * <li>"insertBefore"</li>
 * <li>"insertAfter"</li>
 * <li>"insertIntoSwitchStmt"</li>
 * </ul>
 * @param pAnyYarn <code></code>
 */
function Aspect_definePointCut(pObjObject, pStrWeaveAction, pObjYarn) {
    if (typeof this.hshAllowedWeaves[ pStrWeaveAction ] == "undefined") {
        throw "WARNING: Unsupported weave action in Aspect's 'definePointCut' method.";
    }

    this.arrPointCuts[ this.arrPointCuts.length ] = { objObject : pObjObject, 
                                                      funWeave  : this[ pStrWeaveAction ], 
                                                      objYarn   : pObjYarn };                                              
}


/**
 * Weaves the aspects into the target objects.
 */
function Aspect_weave() {
    var intL = this.arrPointCuts.length, intI = 0, objPointCut = null;
    
    for (; intI < intL; intI++) {
        objPointCut = this.arrPointCuts[intI];
        objPointCut.funWeave.call(this, objPointCut.objObject, objPointCut.objYarn);
    }
}


/**
 * Weaves an inter-type declaration into the target objects.<br>
 * <b>Warning:</b>If passing in a property or properties of <code>pObjYarn</code> that are
 * of type <code>object</code> (i.e., a value that is not 
 * a <code>string</code>, <code>number</code>, <code>function</code>, or <code>boolean</code>) 
 * it/they will be shared (no cloning is done here).
 * @pObjObject <code>Object</code> The target object.
 * @pAnyYarn <code>Object</code> A set of name-value pairs to be added to the target object.
 */
function Aspect_makeInterTypeDeclarations(pObjObject, pObjYarn) {
    var anyI = null;

    if (typeof pObjObject != "object") {
        throw "ERROR in Aspect's 'makeInterTypeDeclarations' method: operating on non-object type.";
    }

    for (anyI in pObjYarn) {
        pObjObject[anyI] = pObjYarn[anyI];
    }
}


/**
 * Weaves method code into a target method, so that the code executes before the original code 
 * of the target method.   
 * @pObjObject <code>Object</code> The target object.
 * @pObjYarn <code>MethodYarn</code> An object with the method data needed to perform the weave.
 */
function Aspect_insertBefore(pObjObject, pObjYarn) {
    var funWoven, strFunWoven, strTemp, arrPieces, strFunNameIfAny, strFunArgsWithParens, 
        objTargetObject = pObjObject, 
        strTargetFunctionName = pObjYarn.strTargetFunctionName, funTarget = pObjYarn.funTarget, 
        strFunYarn = pObjYarn.strFunYarn;

    if ( typeof funTarget != "function" ) {
        throw "ERROR in Aspect's 'insertBefore' method: operating on non-function type.";
    }

    arrPieces = Aspect.chopFunctionTop( funTarget );
    strFunNameIfAny = arrPieces[0];
    strFunArgsWithParens = arrPieces[1];
    strTemp = arrPieces[2];
    strFunWoven = "function " + strFunNameIfAny + " " + strFunArgsWithParens + " { " + strFunYarn + strTemp;
    
    this.priCreateMethod(objTargetObject, strTargetFunctionName, strFunWoven);
}


/**
 * Weaves <code>case</code> statement code into a switch statement, so that the code 
 * executes first in the <code>switch</code> statement.  Currently, this has only been tested
 * on <code>switch</code> statements where the <code>case</code> is inserted
 * at the top of the target <code>switch</code> statement.
 * @pObjObject <code>Object</code> The target object.
 * @pObjYarn <code>MethodYarn</code> An object with the method data needed to perform the weave.
 */
function Aspect_insertIntoSwitchStmt( pObjTargetObject, pObjYarn ) {
    var arrSplatA, arrSplatB, funWoven, strFunWoven, 
        strTargetFunctionName = pObjYarn.strTargetFunctionName, 
        funTarget = pObjYarn.funTarget, 
        strFunYarn = pObjYarn.strFunYarn,
        strCase = pObjYarn.strCase, strSwitch = pObjYarn.strSwitch;
    
    // Insert-after functionality:
    // arrSplatA = funTarget.toString().split(pObjYarn.rxpCase);
    // arrSplatB = arrSplatA[1].split(pObjYarn.rxpSwitch);
    // arrSplatB[arrSplatB.length-1] = arrSplatB[arrSplatB.length-1].replace(strSwitch, ""); // Opera browser compatibility
    // alert(arrSplatB.join(" ***************** ")); // debugging
    // strFunWoven = arrSplatA[0] + strCase + "\n" + strSwitch + "\n" + strFunYarn + arrSplatB[1];

    // Insert-before functionaity (at head of 'switch' statement):
    arrSplutA = funTarget.toString().split(pObjYarn.rxpSwitch);
    strRemainder = funTarget.toString().replace( (arrSplutA[0] + funTarget.toString().match(pObjYarn.rxpSwitch)) ).replace("undefined", "");
    strFunWoven = arrSplutA[0] + " \n " + strSwitch + " \n " + strFunYarn + "\n";
    strFunWoven += strRemainder;
    this.priCreateMethod(pObjTargetObject, strTargetFunctionName, strFunWoven);
}


/**
 * Weaves method code into a target method, so that the code executes after the original code 
 * of the target method.   
 * @pObjObject <code>Object</code> The target object.
 * @pObjYarn <code>MethodYarn</code> An object with the method data needed to perform the weave.
 */
function Aspect_insertAfter(pObjObject, pObjYarn) {
    var funWoven, strFunWoven, strTemp, 
        objTargetObject = pObjObject, 
        strTargetFunctionName = pObjYarn.strTargetFunctionName, funTarget = pObjYarn.funTarget, 
        strFunYarn = pObjYarn.strFunYarn, intCurlyBraceLevels = pObjYarn.intCurlyBraceLevels, intI, strCurlyBraces;

    if ( typeof funTarget != "function" ) {
        throw "ERROR in Aspect's 'insertAfter' method: operating on non-function type.";
    }
    
    strTemp = funTarget.toString();
    intI = 0;    
    for (; intI < intCurlyBraceLevels; intI++) {
        strTemp = strTemp.replace(Aspect.RXP_INSERT_AFTER, "");
    }    
    strCurlyBraces = "";
    intI = 0;
    for (; intI < intCurlyBraceLevels; intI++) {
        strCurlyBraces += " } ";
    }
    strFunWoven = strTemp + strFunYarn + strCurlyBraces;    

    this.priCreateMethod(objTargetObject, strTargetFunctionName, strFunWoven);
}


/**
 * This method performs the actual binding of a string representing a function 
 * to a specified target object, so that the string becomes a method of the specified
 * target object.
 * @param pObjTargetObject <code>object</code> The object to receive a new method, as an aspect of itself.
 * @param strTargetFunctionName <code>string</code> The name to be given to the new method on the target object.
 * @param strFunWoven <code>string</code> A string representing a literal function. 
 */
function Aspect_priCreateMethod(pObjTargetObject, strTargetFunctionName, strFunWoven) {
    // alert( "woven: \n " + strFunWoven );
   funWoven = eval("(" + strFunWoven + ");");
    if (typeof funWoven == "undefined") {
        funWoven = new Function(" return " + strFunWoven)(); // For IE 6 and under.
    }
    pObjTargetObject[ strTargetFunctionName ] = funWoven;
}

