Tutorial

Multiple Affixes

Adding New Affixes

Multiple affixes can be applied to a given method independently of each other. That is to say that repeated calls to addPrefix, addSuffix or addWrapper in the context of a given interceptee will simply add new affixes to that method, irrespective of any that it may possess already.

A new prefix that is added using addPrefix or addWrapper is added such that it is the last to execute in any existing set that is associated with the interceptee. Similarly, addition of a new suffix (added using addSuffix or addWrapper) causes that suffix to execute before any other suffixes that may be associated with the interceptee.

The diagram illustrates this point.

Diagram, showing the effect of repeated calls to addPrefix for the same interceptee.

Accordingly, Example 24 demonstrates the principle by applying a prefix and a suffix to myFunc, which is then invoked. A further prefix and suffix are then added, before myFunc is invoked a second time.

The result is that the original prefix executes first, followed by the new prefix, and then myFunc, after which the new suffix executes, followed finally by the original suffix.

Note that a given function can be attached to the same interceptee an unlimited number of times, thus allowing it to act as a number of prefixes, suffixes, or prefixes and suffixes (although why this might be useful is unclear).


 // Example 24

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }

 function suffixFunc_0 () { alert ("suffixFunc_0 executed"); }
 function suffixFunc_1 () { alert ("suffixFunc_1 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 AJS.addPrefix (this, "myFunc", prefixFunc_0);
 AJS.addSuffix (this, "myFunc", suffixFunc_0);

 myFunc ();

 AJS.addPrefix (this, "myFunc", prefixFunc_1);
 AJS.addSuffix (this, "myFunc", suffixFunc_1);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_0 executed
 myFunc executed
 suffixFunc_0 executed
 prefixFunc_0 executed
 prefixFunc_1 executed
 myFunc executed
 suffixFunc_1 executed
 suffixFunc_0 executed
            

Using addBefore/addAfter

The ability to add affixes arbitrarily and independently of other affixes is of great value because it allows a given interception setter to operate in ignorance of other code that sets interceptions on a given function.

However, when it is necessary to add a new affix to an existing set at a particular point in the calling order, the way that addPrefix etc operate is inappropriate.

To address this, use the addBefore and addAfter methods of the Affix type. addBefore applies the affix function, such that (on a call to the interceptee) it executes before the affix on which addBefore was called. Conversely, addAfter applies the affix function, such that it executes after the affix on which addAfter was called. The diagram illustrates these two points.

These methods create a new Affix object, insert it into the relevant set, take care of the book-keeping, and return a reference to the Affix. That object permits insertion of further prefixes or suffixes through the use of the addBefore and addAfter methods that it supports.

Diagram, showing the effect of calling the addBefore and addAfter methods of a given affix object.

Example 25 demonstrates the use of addBefore and addAfter, using the Affix object returned from a call to addPrefix.


 // Example 25

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }
 function prefixFunc_2 () { alert ("prefixFunc_2 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 var PrefixObj = AJS.addPrefix (this, "myFunc", prefixFunc_1);

 PrefixObj.addAfter  (prefixFunc_2);

 myFunc ();

 PrefixObj.addBefore (prefixFunc_0);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_1 executed
 prefixFunc_2 executed
 myFunc executed
 prefixFunc_0 executed
 prefixFunc_1 executed
 prefixFunc_2 executed
 myFunc executed
            

The next example demonstrates the application of a third prefix-function, through the addAfter method of the Affix object returned from a previous call to addAfter.


 // Example 26

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }
 function prefixFunc_2 () { alert ("prefixFunc_2 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 var PrefixObj = AJS.addPrefix (this, "myFunc", prefixFunc_0);

 PrefixObj = PrefixObj.addAfter (prefixFunc_1);

 PrefixObj.addAfter (prefixFunc_2);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_0 executed
 prefixFunc_1 executed
 prefixFunc_2 executed
 myFunc executed
            

Adding Affixes through Wrappers

Where client code has possession of a Wrapper object, adding new prefixes and suffixes operates in exactly the same way as described above. The only difference is that one must retrieve the reference to the relevant Affix object first, and Example 27 illustrates this.


 // Example 27

 function prefixFunc   () { alert ("prefixFunc executed");   }
 function suffixFunc_0 () { alert ("suffixFunc_0 executed"); }
 function suffixFunc_1 () { alert ("suffixFunc_1 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 var Wrapper = AJS.addWrapper (this, "myFunc", prefixFunc, null, Infinity, suffixFunc_0);

 myFunc ();

 Wrapper.getSuffix ().addAfter (suffixFunc_1);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc executed
 myFunc executed
 suffixFunc_0 executed
 prefixFunc executed
 myFunc executed
 suffixFunc_0 executed
 suffixFunc_1 executed
            

Using Interceptee Methods

AspectJS uses JavaScript's 'functions-as-data-objects' feature internally, and by dint of this, the initial application of a prefix or suffix has the effect of adding nine methods to the interceptee function-object (or, more correctly, to the proxy function that the AJS object inserts between the caller and the interceptee itself).

Three of these have the same names as the addPrefix etc methods that the AJS object supports, and operate in exactly the same way as those counterparts. This difference (and advantage) is that using them allows client-code to add new affixes to a method directly, without knowing which object owns the method.

Given this, their signatures lack the first two arguments of their AJS equivalents. This means that the interceptee implementations of addPrefix and addSuffix have identical signatures to the addBefore and addAfter methods of the Affix type.

Note that, as with calling AJS.addPrefix etc, the new affix is applied so that it is 'nearer' to the interceptee (in terms of execution order) than existing affixes. In other words, adding a new prefix using an interceptee-method means that it will execute after any of its siblings, and before the interceptee executes. Conversely, adding a new suffix through an interceptee method means it will execute after the interceptee does, but before any of its siblings.

Example 28 demonstrates these points.


 // Example 28

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }

 function suffixFunc_0 () { alert ("suffixFunc_0 executed"); }
 function suffixFunc_1 () { alert ("suffixFunc_1 executed"); }

 function myFunc       () { alert ("myFunc executed"); }

 AJS.addWrapper   (this, "myFunc", prefixFunc_0, null, Infinity, suffixFunc_0);

 myFunc.addPrefix (prefixFunc_1);
 myFunc.addSuffix (suffixFunc_1);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_0 executed
 prefixFunc_1 executed
 myFunc executed
 suffixFunc_1 executed
 suffixFunc_0 executed
            

Interceptees are also augmented with methods that allow client code to determine how many prefixes and suffixes it possesses, and give access to their first and last prefixes and suffixes.

Example 29 demonstrates this.

See the API documentation for the full range of methods that the AJS object adds to a given interceptee.


 // Example 29

 function prefixFunc_0 () { }
 function prefixFunc_1 () { }

 function suffixFunc   () { }

 function myFunc       () { }

 AJS.addPrefix    (this, "myFunc", prefixFunc_0);

 myFunc.addPrefix (prefixFunc_1);
 myFunc.addSuffix (suffixFunc);

 alert (myFunc.getPfixTot ());
 alert (myFunc.getSfixTot ());

 --------------------------------------

 Output:
 2
 1
            

Multiple Prefix Function-Signatures

Where an interceptee has multiple prefixes or suffixes those functions receive more parameters when executed than do single prefixes or suffixes.

In the case of prefixes, these are passed the same two arguments that single prefixes receive, but they also receive the return value of the previous prefix as a third argument. The diagram illustrates this principle.

Diagram, showing the arguments that are passed to multiple prefix-functions.

This permits prefix 'decoration' (in the Design Patterns sense), thereby allowing prefix functionality to be built up in a layered or additive fashion, and Example 30 demonstrates this by means of concatenating strings.

Note that if a prefix has no previous sibling, or the previous prefix returns nothing, then the value for the PrevPRtn argument is 'undefined' (note: not 'not defined'). By definition, it is always undefined for single prefixes, as it is with the first prefix in a set of such functions.


 // Example 30

 function prefixFunc_0 (IArgs, Arg, PrevPRtn) { return            "The rain ";  }
 function prefixFunc_1 (IArgs, Arg, PrevPRtn) { return PrevPRtn + "in Spain ";  }
 function prefixFunc_2 (IArgs, Arg, PrevPRtn) { return PrevPRtn + "falls ";     }
 function prefixFunc_3 (IArgs, Arg, PrevPRtn) { return PrevPRtn + "mainly in "; }
 function prefixFunc_4 (IArgs, Arg, PrevPRtn) { return PrevPRtn + "the plain";  }
 function prefixFunc_5 (IArgs, Arg, PrevPRtn) { alert (PrevPRtn);               }

 function myFunc       ()                     { alert ("myFunc executed");      }


 var PrefixObj = AJS.addPrefix (this, "myFunc", prefixFunc_5);

 PrefixObj.addBefore (prefixFunc_0);
 PrefixObj.addBefore (prefixFunc_1);
 PrefixObj.addBefore (prefixFunc_2);
 PrefixObj.addBefore (prefixFunc_3);
 PrefixObj.addBefore (prefixFunc_4);

 myFunc ();

 --------------------------------------

 Output:

 The rain in Spain falls mainly in the plain
 myFunc executed
            

Multiple Suffix Function-Signatures

Multiple suffixes are passed the interceptee's arguments array and any static value provided by the intercept setter (as they are when they act singly). They also receive the return value of the previous suffix as the third argument. (The fourth is whatever the interceptee returned).

The diagram illustrates the principle.

Diagram, showing the arguments that are passed to multiple suffix-functions.

As with prefixes, this allows decoration of suffix functionality, and Example 31 demonstrates this, also by means of the concatenation of strings.

As is also the case with prefixes, suffixes that have no previous sibling receive a value of 'undefined' for the PrevSResult argument.

Do note that the design of AspectJS requires the previous suffix's return value to be the third in suffix function-signatures, even though this may be inconvenient when you are interested only in examining the value returned from the interceptee. This is because the first three arguments of a suffix function must be the same as the first three in prefix functions, otherwise it would be impossible to use the same function as both a prefix and a suffix.

Given this, and given that only suffixes can receive the interceptee's return value, that value must form the fourth argument in suffix-function signatures. Moreover, given that an affix may be the only one attached to its interceptee, it follows that the interceptee's arguments array and the interception-setter's static argument must form the first two parameters in an affix-function signature.


 // Example 31

 // Note: IArgs, Arg, PrevSRtn and IRtn denote 'Interceptee Arguments',
 // the 'static affix-argument', 'Previous Suffix Return" and '
 // Interceptee Return' respectively

 function suffixFunc_0 (IArgs, Arg, PrevSRtn, IRtn) { return IRtn     + "sticks, "; }

 function suffixFunc_1 (IArgs, Arg, PrevSRtn, IRtn) { return PrevSRtn
                                                           + IRtn     + " wax, ";   }

 function suffixFunc_2 (IArgs, Arg, PrevSRtn, IRtn) { return PrevSRtn
                                                           + IRtn     + "light ";   }

 function suffixFunc_3 (IArgs, Arg, PrevSRtn, IRtn) { alert (PrevSRtn + "all relate to "
                                                          +  IRtn     + "s");       }

 function myFunc   ()
    {
    alert ("Returning 'Candle'");
    return "Candle";
    }


 var SuffixObj = AJS.addSuffix (this, "myFunc", suffixFunc_3);

 SuffixObj.addBefore (suffixFunc_0);
 SuffixObj.addBefore (suffixFunc_1);
 SuffixObj.addBefore (suffixFunc_2);

 myFunc ();

 --------------------------------------

 Output:

 Returning 'Candle'
 Candlesticks, Candle wax, Candlelight all relate to Candles.
            

Iterating across Affix Sets

Affix objects provide two methods called getNext and getPrev, which support iteration through a set of prefixes or suffixes, either 'back' in the calling order to the first in the set, or 'forward' to the last.

Note that calling getPrev on the first affix in a set, or calling getNext on the last will return the object on which those methods are called. This means it is impossible to navigate beyond either end of a set of affixes.

In Example 32, four prefixes are attached to myFunc, which is then invoked. By calling getPrev on the Affix object returned from the last call to addAfter, the affix that was inserted on that call is retrieved, and repetition of this principle allows iteration back up the collection of prefixes. Comparison of the reference returned from the call to getPrev with the reference to the object on which it is called enables detection of the end of the set.


 // Example 32

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }
 function prefixFunc_2 () { alert ("prefixFunc_2 executed"); }
 function prefixFunc_3 () { alert ("prefixFunc_3 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 var PrefixObj = AJS.addPrefix (this, "myFunc", prefixFunc_0);

 PrefixObj = PrefixObj.addAfter (prefixFunc_1);
 PrefixObj = PrefixObj.addAfter (prefixFunc_2);
 PrefixObj = PrefixObj.addAfter (prefixFunc_3);

 myFunc ();

 while (PrefixObj.getPrev () !== PrefixObj)
    {
    PrefixObj = PrefixObj.getPrev ();
    }

 PrefixObj.remove ();

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_0 executed
 prefixFunc_1 executed
 prefixFunc_2 executed
 prefixFunc_3 executed
 myFunc executed
 prefixFunc_1 executed
 prefixFunc_2 executed
 prefixFunc_3 executed
 myFunc executed
            

Adding New Affix-Properties

Note that because JavaScript allows the addition of new properties to an object dynamically, it is therefore possible to assign attributes and other methods to a given Affix object. This allows tagging of prefixes and suffixes, such that they can be identified by other parts of a system, and is therefore of use in iterating through sets of affixes.

The next example illustrates this.


 // Example 33

 function prefixFunc () { }
 function myFunc     () { }

 var PrefixObj = AJS.addPrefix (this, "myFunc", prefixFunc);

 PrefixObj.ID = "Prefix 5";

 PrefixObj.addAfter (prefixFunc).ID = "Prefix 1";
 PrefixObj.addAfter (prefixFunc).ID = "Prefix 2";
 PrefixObj.addAfter (prefixFunc).ID = "Prefix 3";
 PrefixObj.addAfter (prefixFunc).ID = "Prefix 4";

 var OldObj = null;

 while (OldObj !== PrefixObj)
   {
   alert ("Affix ID = " + PrefixObj.ID);

   OldObj    = PrefixObj;
   PrefixObj = PrefixObj.getNext ();

   }

 --------------------------------------

 Output:

 Affix ID = Prefix 5
 Affix ID = Prefix 4
 Affix ID = Prefix 3
 Affix ID = Prefix 2
 Affix ID = Prefix 1
            

Promoting and Demoting Affixes

The execution order of affixes can be changed by means of the promote and demote methods of the Affix type. They can also be set collectively using the equivalent methods that Wrapper objects support.

To promote an affix means to move it closer, temporally, to the interceptee in the execution order. Therefore, to promote prefix A over prefix B means to cause A to execute after B (but still before the interceptee).

Similarly, to promote suffix A over suffix B will cause suffix A to execute before suffix B (but still after the interceptee).

The diagram illustrates these concepts.

Diagram, showing the effect of promoting and demoting affixes.

In Example 34, a wrapper is applied to myFunc, which is then invoked. A second wrapper is then applied such that, on a second call to myFunc, its prefix and suffix execute after and before (respectively) the first wrapper's affixes. The second wrapper's prefix and suffix are then both demoted, meaning that a final call to myFunc causes those affixes to execute before and after the first wrapper's affixes.

Note that, as with affix removal, it is impossible to promote or demote an affix whilst the interception mechanism is executing it and its siblings, although a prefix may promote/demote a suffix that is attached to the same interceptee, and vice versa. Were this not the case, it would be possible to confuse the interception mechanism with regard to affix execution order.


 // Example 34

 function prefixFunc_0 () { alert ("prefixFunc_0 executed"); }
 function prefixFunc_1 () { alert ("prefixFunc_1 executed"); }

 function suffixFunc_0 () { alert ("suffixFunc_0 executed"); }
 function suffixFunc_1 () { alert ("suffixFunc_1 executed"); }

 function myFunc       () { alert ("myFunc executed");       }


 AJS.addWrapper (this, "myFunc", prefixFunc_0, null, Infinity, suffixFunc_0);

 myFunc ();

 var WrapperObj = AJS.addWrapper (this, "myFunc", prefixFunc_1, null, Infinity, suffixFunc_1);

 myFunc ();

 WrapperObj.demote ();

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc_0 executed
 myFunc executed
 suffixFunc_0 executed

 prefixFunc_0 executed
 prefixFunc_1 executed
 myFunc executed
 suffixFunc_1 executed
 suffixFunc_0 executed

 prefixFunc_1 executed
 prefixFunc_0 executed
 myFunc executed
 suffixFunc_0 executed
 suffixFunc_1 executed
            

Go forward to Part 5 of this tutorial.
Go back to Part 3 of this tutorial.