AJS Object User-Guide

Adding Multiple Affixes

Multiple affixes can be applied to a given method independently of each other. Indeed, as pointed out in the product overview, this is one of the principal benefits that AspectJS offers over implementing method-call interception yourself, and it is this feature that allows AJS_Validator, AJS_Logger and AJS_ODL to operate in concert, but independently and in complete ignorance of each other.

To add additional prefixes, suffixes or wrappers to an existing interceptee, simply call addPrefix, addSuffix or add addWrapper again, passing the method-owner, the inteceptee name and (an) affix function(s), as you did before.

If the interceptee has an existing prefix then a new prefix will be added such that it is the last to execute before the interceptee executes. Similarly, addition of a suffix where there are one or more existing suffixes will cause that suffix to execute before those other suffixes. Calling addWrapper on an existing interceptee operates along exactly the same lines.

The diagram illustrates this point, and Example 27 demonstrates it by applying a prefix and a suffix to method_A, which the code then invokes. A further prefix and suffix are then added, before method_A is invoked a second time.

Contents Adding Multiple Affixes
Using addBefore/addAfter
Adding Affixes through WrapperCtrls
Using Interceptee Methods
Multiple Prefix-Function Signatures
Multiple Suffix-Function Signatures
Iterating across Affix Sets
Execution Cardinality
Promoting and Demoting Affixes
Combining Auto-Addition/-Removal
Diagram showing the effect of repeated calls to addPrefix for the same interceptee.

The result is that the original prefix executes first, followed by the new prefix, and then method_A, 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. Why this might be useful is unclear, but you may have some fiendishly-inventive scheme in mind for which it is just the ticket.


 // -- Example 27 -------------------------------------------------------------

 function prefixfunc_0 () { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 () { console.log ("prefixfunc_1 executed"); }

 function suffixfunc_0 () { console.log ("suffixfunc_0 executed"); }
 function suffixfunc_1 () { console.log ("suffixfunc_1 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed");    }
    };

 AJS  .addPrefix (MyObj, "method_A", prefixfunc_0);
 AJS  .addSuffix (MyObj, "method_A", suffixfunc_0);

 MyObj.method_A  ();

 AJS  .addPrefix (MyObj, "method_A", prefixfunc_1);
 AJS  .addSuffix (MyObj, "method_A", suffixfunc_1);

 MyObj.method_A ();

 -- Output --------------------------------------------------------------------

 prefixfunc_0 executed
 method_A executed
 suffixfunc_0 executed
 prefixfunc_0 executed
 prefixfunc_1 executed
 method_A executed
 suffixfunc_1 executed
 suffixfunc_0 executed
      

Given this, and given the execution-order rules explored above, it is possible for a self-inserting prefix to cause infinite recursion. This is because each new instance of the prefix will always be inserted 'ahead' of the one that is executing currently, as the diagram above illustrates, and so there will always be one more to execute. This means that the thread of execution will never reach the interceptee – a situation that is akin to a train that lays its own track ahead of itself continuously (perhaps the road to hell is paved not with good intentions but with self-inserting prefixes).

Example 28 demonstrates this, putting a limit on the recursion depth.

Note that the situation above cannot arise with suffixes because a call from a suffix to AJS.addSuffix (in respect of the same interceptee) will always insert the new suffix 'above' the current one in the execution chain; although this is a way to grow a suffix chain automatically. Note also that certain considerations apply in respect of affixes that combine self-addition and -removal, which are covered in the Combining Auto-Addition/-Removal section below.

Note too that you can avoid the unwanted recursion of a self-inserting prefix if the prefix adds itself, and then promotes itself 'beyond' its new instance in the execution order (see below). Unwanted recursion can also be avoided if the prefix uses the addBefore method of its AffixCtrl object, which is covered in the next section. You can also prevent it by making the self-adding prefix suspend itself, as this will prevent it from executing again.


 // -- Example 28 -------------------------------------------------------------

  var MyObj =
    {
    method_A : function () { console.log ("method_A executed");    }
    };

 var Count = 0;

 function prefix ()
    {
    console.log ("Prefix executed. Count: " + Count++);

    if (Count < 5) { AJS.addPrefix (MyObj, "method_A", prefix); }

    }

 AJS  .addPrefix (MyObj, "method_A", prefix);
 MyObj.method_A  ();

 -- Output --------------------------------------------------------------------

 Prefix executed. Count: 0
 Prefix executed. Count: 1
 Prefix executed. Count: 2
 Prefix executed. Count: 3
 Prefix executed. Count: 4
 method_A executed
      

Using addBefore/addAfter

As pointed out above and elsewhere, 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 method. However, when it is necessary to add a new affix to an existing set at a particular point in the execution order, the default cardinality that addPrefix etc. confer on new affixes may not be what you want.

To address this, use the addBefore and addAfter methods of the AffixCtrl object that was returned when another affix was added previously (an alternative is to use a given AffixCtrl-object's promote and demote methods). 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 insert at the correct point the affix function that you provide, take care of the book-keeping, and return a reference to an AffixCtrl object. 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 29 demonstrates the use of addBefore and addAfter, using the AffixCtrl object returned from a call to addPrefix.


 // -- Example 29 -------------------------------------------------------------

 function prefixfunc_0 () { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 () { console.log ("prefixfunc_1 executed"); }
 function prefixfunc_2 () { console.log ("prefixfunc_2 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed");    }
    };

 var PrefixObj = AJS.addPrefix (MyObj, "method_A", prefixfunc_1);

 PrefixObj.addAfter  (prefixfunc_2);

 MyObj    .method_A  ();

 PrefixObj.addBefore (prefixfunc_0);

 MyObj    .method_A  ();

 -- Output --------------------------------------------------------------------

 prefixfunc_1 executed
 prefixfunc_2 executed
 method_A executed
 prefixfunc_0 executed
 prefixfunc_1 executed
 prefixfunc_2 executed
 method_A executed
      

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


 // -- Example 30 -------------------------------------------------------------

 function prefixfunc_0 () { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 () { console.log ("prefixfunc_1 executed"); }
 function prefixfunc_2 () { console.log ("prefixfunc_2 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed");    }
    };

 var PrefixObj = AJS.addPrefix (MyObj, "method_A", prefixfunc_0);

 PrefixObj = PrefixObj.addAfter (prefixfunc_1);

 PrefixObj.addAfter (prefixfunc_2);

 MyObj    .method_A ();

 -- Output --------------------------------------------------------------------

 prefixfunc_0 executed
 prefixfunc_1 executed
 prefixfunc_2 executed
 method_A executed
      

Adding Affixes through WrapperCtrls

Where client code has possession of a WrapperCtrl 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 AffixCtrl object first, and Example 31 illustrates this.


 // -- Example 31 -------------------------------------------------------------

 function prefixFunc   ()  { console.log ("prefixFunc executed");   }
 function suffixfunc_0 ()  { console.log ("suffixfunc_0 executed"); }
 function suffixfunc_1 ()  { console.log ("suffixfunc_1 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed");     }
    };

 var WrapperCtrl = AJS.addWrapper (MyObj, "method_A", prefixFunc, suffixfunc_0);

 MyObj      .method_A      ();

 WrapperCtrl.getSuffixCtrl ().addAfter (suffixfunc_1);

 MyObj      .method_A      ();

 -- Output --------------------------------------------------------------------

 prefixFunc executed
 method_A executed
 suffixfunc_0 executed
 prefixFunc executed
 method_A executed
 suffixfunc_0 executed
 suffixfunc_1 executed
      

Using Interceptee Methods

As the Introduction section explains, attaching an initial affix-function to a method causes the AJS object to replace the method owner's reference to the method's function-body with a proxy function that forwards calls to the various affixes and the method itself.

The AJS object equips this function with nine methods (remember that functions can have properties of their own), through which you can attach additional affix functions to the method in question, as the diagram illustrates, and this feature holds two advantages.

First, it allows you to add affixes to a method that is already in an intercepted state without needing to know the method owner (you can affirm a method's intercepted state by using the 'in' operator to determine if the method in question possesses, say, the addPrefix method). In line with this, three of the methods have the same names as the addPrefix etc. methods that the AJS object supports, and they operate in exactly the same way as their counterparts.

Moreover, their signatures lack the first two arguments of their AJS equivalents (such arguments are not needed, given that the method is already in an intercepted state), and this means that the interceptee implementations of addPrefix and addSuffix have identical signatures to the addBefore and addAfter methods of the AffixCtrl type.

Note that, as with calling AJS.addPrefix, 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 32 demonstrates these points.

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

The second advantage that the interceptee methods confer is that they absolve the AJS object from having to maintain a list of methods to which interceptions have been applied (which would be a rather inelegant approach). If it did operate in that way, it would be possible for client code to apply interceptions, and then delete the objects or methods in question, operations that the AJS object would be unable to detect. This would render it unable to remove the method(s) in question from the list, and so, were this process of application and deletion to continue unbounded, the list would grow inexorably, incurring an ever-greater degree of 'storage redundancy' (known in some circles as a 'Space Leak', as opposed to a memory leak).

In extreme situations, this would consume sufficient storage eventually to cause serious problems for the run-time itself. The design of the AJS object, however, does away with such concerns (because it uses the interceptee methods itself, which is why they exist), and so it is possible to add and remove affixes until you are blue in the face, such that, when a former interceptee is left with no affixes at all, the storage picture will be as if the AJS object had never been a factor in the run-time equation.

Note that, if you are running on an ES3-compliant-only run-time (i.e. you are operating within some kind of legacy scenario), and you apply a for..in loop to the properties of an intercepted method, the loop will enumerate the interceptee methods, as well as any others that your code may have added to the method, which may not be what you want. Moreover, you can also delete the interceptee methods when running on an ES3-only run-time, and you must not do this, as it will render the application unable to add new affixes to that intercepted-method subsequently.

However, on an ES5-compliant run-time, the AJS object adds the interceptee methods to the interceptee using the defineProperties method of the Object constructor, marking them as non-enumerable and non-configurable. This prevents for..in loops from picking-up those methods, and renders the application unable to delete them, although they are still there for you to use even though they are invisible.

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

Diagram showing the special methods that the AJS object adds to the proxy function.

 // -- Example 32 -------------------------------------------------------------

 function prefixfunc_0 ()  { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 ()  { console.log ("prefixfunc_1 executed"); }

 function suffixfunc_0 ()  { console.log ("suffixfunc_0 executed"); }
 function suffixfunc_1 ()  { console.log ("suffixfunc_1 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed");     }
    };

 AJS           .addWrapper (MyObj, "method_A", prefixfunc_0, suffixfunc_0);

 MyObj.method_A.addPrefix  (prefixfunc_1);
 MyObj.method_A.addSuffix  (suffixfunc_1);

 MyObj.         method_A   ();

 -- Output --------------------------------------------------------------------

 prefixfunc_0 executed
 prefixfunc_1 executed
 method_A executed
 suffixfunc_1 executed
 suffixfunc_0 executed
      

 // -- Example 33 -------------------------------------------------------------

 function prefixfunc_0  () { }
 function prefixfunc_1  () { }

 function suffixFunc    () { }

 var MyObj =
    {
    method_A : function () { }
    };

 AJS           .addPrefix (MyObj, "method_A", prefixfunc_0);

 MyObj.method_A.addPrefix (prefixfunc_1);
 MyObj.method_A.addSuffix (suffixFunc);

 console       .log       (MyObj.method_A.getNumPfixes ());
 console       .log       (MyObj.method_A.getNumSfixes ());

 -- Output --------------------------------------------------------------------
 2
 1
      

Multiple Prefix-Function Signatures

Where multiple prefixes are attached to a given interceptee, each prefix that sits between the first perfix (i.e. the one that is farthest from the interceptee in the calling order) and the interceptee receives a second argument in addition to the interceptee's arguments array. This extra argument is the return value of the prefix that executed before (for in a given call to the interceptee). It follows that if a given prefix is the sole or first prefix in the set, the value of that argument is undefined.

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), which is to say that you can build-up prefix functionality in a type-invariant, additive fashion, and Example 34 demonstrates this by means of concatenating strings. This feature too may be only a curiosity, but it is a natural consequence of the AJS object's implementation, and may be of use to the more avant garde developer.


 // -- Example 34 -------------------------------------------------------------

 function prefixfunc_0 (IArgs, PrevPRtn) { return            "The rain ";  }
 function prefixfunc_1 (IArgs, PrevPRtn) { return PrevPRtn + "in Spain ";  }
 function prefixfunc_2 (IArgs, PrevPRtn) { return PrevPRtn + "falls ";     }
 function prefixfunc_3 (IArgs, PrevPRtn) { return PrevPRtn + "mainly in "; }
 function prefixfunc_4 (IArgs, PrevPRtn) { return PrevPRtn + "the plain";  }
 function prefixfunc_5 (IArgs, PrevPRtn) { console.log (PrevPRtn);         }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 var PrefixObj = AJS.addPrefix (MyObj, "method_A", prefixfunc_5);

 PrefixObj.addBefore (prefixfunc_0);
 PrefixObj.addBefore (prefixfunc_1);
 PrefixObj.addBefore (prefixfunc_2);
 PrefixObj.addBefore (prefixfunc_3);
 PrefixObj.addBefore (prefixfunc_4);

 MyObj.method_A ();

 -- Output --------------------------------------------------------------------

 The rain in Spain falls mainly in the plain
 method_A executed
      

Multiple Suffix-Function Signatures

Where an interceptee has multiple suffix-functions, the first suffix to execute receives its return value (as its second argument – the first being the interceptee's arguments array, as with prefixes). If that suffix returns undefined, the AJS object passes the interceptee's return value to the next suffix in the chain. However, if it returns anything other than undefined, the next suffix in the chain receives that value instead.

The equivalent principle applies to the next suffix along. If it returns undefined, the AJS object passes the return value from the previous suffix (or the interceptee's return value) to the third suffix in the chain.

This process occurs for each suffix until the last one, where any non-undefined value it returns is returned to the interceptee's caller, otherwise the value it received is returned instead. The diagram illustrates these ideas.

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

As with prefixes, this allows decoration of suffix functionality. Moreover, to elaborate on the discussion in the section on Interceptee Return-Values and Suffixes, it allows a 'progressive' or 'incremental' form of that concept, where a set of suffixes act as, say, filters on the object or value that an interceptee returns.

Similarly, and to return to the discussion on argument/return-value mutation/injection, it allows return values to be mutated in an incremental or multi-stage fashion, or even for a set of suffixes to mutate their interceptee's return value, only for the final suffix in the chain to veto all such activity by substituting simply an unrelated value.

Example 35 demonstrates (in, admittedly, a rather contrived fashion) such progressive return-value mutation.

Note that, if you suspend a suffix that takes part in such chained return-value mutation/injection, the value returned (if defined) from the previous suffix (if one exists) is passed to the one after the suspended suffix (if one exists). In short, the value that is passed along the chain 'skips over' any suspended suffixes.


 // -- Example 35 -------------------------------------------------------------

 // Note: IArgs and IRtn denote 'Interceptee Arguments',
 // and the 'Interceptee Return' respectively.

 var MyObj =
    {
    method_A : function ()
       {
       console.log ("method_A ");
       return 42;
       }

    };

 AJS    .addSuffix (MyObj, "method_A", function (IArgs, IRtn) { console.log ("Suffix C: " + IRtn); return ++IRtn; });
 AJS    .addSuffix (MyObj, "method_A", function (IArgs, IRtn) { console.log ("Suffix B: " + IRtn);                });
 AJS    .addSuffix (MyObj, "method_A", function (IArgs, IRtn) { console.log ("Suffix A: " + IRtn); return ++IRtn; });

 console.log       (MyObj.method_A ());

 -- Output --------------------------------------------------------------------

 method_A
 Suffix A: 42
 Suffix B: 43
 Suffix C: 43
 44
      

Iterating across Affix Sets

AffixCtrl 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 36, four prefixes are attached to method_A, which is then invoked. By calling getPrev on the AffixCtrl 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 36 -------------------------------------------------------------

 function prefixfunc_0 () { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 () { console.log ("prefixfunc_1 executed"); }
 function prefixfunc_2 () { console.log ("prefixfunc_2 executed"); }
 function prefixfunc_3 () { console.log ("prefixfunc_3 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 var PrefixObj = AJS.addPrefix (MyObj, "method_A", prefixfunc_0);

 PrefixObj = PrefixObj.addAfter (prefixfunc_1);
 PrefixObj = PrefixObj.addAfter (prefixfunc_2);
 PrefixObj = PrefixObj.addAfter (prefixfunc_3);

 MyObj.method_A ();

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

 PrefixObj.remove   ();

 MyObj    .method_A ();

 -- Output --------------------------------------------------------------------

 prefixfunc_0 executed
 prefixfunc_1 executed
 prefixfunc_2 executed
 prefixfunc_3 executed
 method_A executed
 prefixfunc_1 executed
 prefixfunc_2 executed
 prefixfunc_3 executed
 method_A executed
      

Execution Cardinality

Given that multiple affix functions execute in a well-defined order, it is possible to determine an affix function's cardinality (i.e. its position) in the execution sequence, and this is the purpose of the getCardinality method that AffixCtrl objects support. If a given affix is first in the execution sequence, calling this method on the corresponding AffixCtrl object will return zero, and if it is second, the method will return 1 and so on.

In the case of suffix functions, where a method has three suffixes, then calling getCardinality on the one that is nearest to the interceptee in the execution order will return 2, whereas calling that method for the suffix that is farthest (i.e. the last in the execution order) will return zero. getCardinality can therefore be seen as a method that indicates the distance, in terms of function calls, from the point of invocation of a given method. As each prefix in a chain executes, it takes the execution thread one step further from the point-of-call to the intercepted method, and as each suffix in a chain executes, it takes the execution thread back one step closer to the point-of-call.

Example 37 demonstrates these points.

This is of great value in AJS_Validator's implementation because many of that component's ancillary methods accept a numeric 'StackOffset' argument by which one may stipulate the 'distance' in terms of sequential function-calls from the point in the application where a given function-call has occurred. This enables AJS_Validator and any Pre-/Post-Validators you generate to indicate the correct location at which trouble has manifested (rather than the point in the validation code where it has been detected).

However, and without recourse to getCardinality, each prefix or suffix that lay before (in the execution order) a validation prefix or suffix respectively in a given affix set would throw any stack-offset value off by one, the upshot being that exception notifications would indicate locations within the AJS_Validator or AJS objects, instead of the point within the application where the trouble lay. Happily, getCardinality forestalls such difficulties elegantly, and you should see the documentation on stack offsets in the context of AJS_Validator Pre-/Post-Vadlidators for further discussion of this and related issues.


 // -- Example 37 -------------------------------------------------------------

 function prefixfunc_0 () { }
 function prefixfunc_1 () { }
 function prefixfunc_2 () { }

 function suffixfunc_0 () { }
 function suffixfunc_1 () { }
 function suffixfunc_3 () { }

 var MyObj =
    {
    method_A : function ()
       {
       // ...
       }

    };

 var PrefixCtrl_0 = AJS.addPrefix (MyObj, "method_A", prefixfunc_0);
 var PrefixCtrl_1 = AJS.addPrefix (MyObj, "method_A", prefixfunc_1);
 var PrefixCtrl_2 = AJS.addPrefix (MyObj, "method_A", prefixfunc_2);

 var SuffixCtrl_0 = AJS.addSuffix (MyObj, "method_A", suffixfunc_0);
 var SuffixCtrl_1 = AJS.addSuffix (MyObj, "method_A", suffixfunc_1);
 var SuffixCtrl_2 = AJS.addSuffix (MyObj, "method_A", suffixfunc_2);

 console.log (PrefixCtrl_0.getCardinality ());
 console.log (PrefixCtrl_1.getCardinality ());
 console.log (PrefixCtrl_2.getCardinality ());

 console.log (SuffixCtrl_0.getCardinality ());
 console.log (SuffixCtrl_1.getCardinality ());
 console.log (SuffixCtrl_2.getCardinality ());

 -- Output --------------------------------------------------------------------

 0
 1
 2
 2
 0
 1
      

Promoting and Demoting Affixes

The execution order of affixes (i.e. their cardinality) can be changed by means of the promote and demote methods of the AffixCtrl type. They can also be set collectively using the equivalent methods that WrapperCtrl 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.

To demonstrate this, Example 38 shows the application of a wrapper to method_A, which is then invoked. A second wrapper is then applied such that, on a second call to method_A, 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 method_A causes those affixes to execute before and after the first wrapper's affixes.

Note that the promote and demote methods return a boolean-literal indicating the success (true) or failure (false) of the call. That is, if you try to demote an affix that is already at the 'far end' of the set of which it is a member, the affix cannot be demoted any further, and so the demote call will return false. The converse holds for a call to the promote method.

This means that to ensure that an affix makes it to the end of an affix set (i.e. as far away temporally as possible from the interceptee) then all you need to do is call demote repeatedly until it returns false. For example:

   while (MyAffix.demote ());

It follows that the converse holds when you want to promote an affix so that it is as close as possible temporally to the interceptee (i.e. keep calling promote until it returns false).

Note too that it is possible for an affix function to promote or demote itself (just as affixes can detach themselves from their interceptees). This means that if an interceptee has three prefixes called, for example, A, B and C, and affix B promotes itself, then B will execute a second time, after affix C has executed (after which, if they are prefixes, the interceptee executes, as you would expect). To clarify, the execution sequence is:

   A
   B
   C
   B     

Conversely, if B demotes itself, then affix A will execute twice. That is, the execution order will be:

   A
   B
   A
   C     

This means that a really good way to gum-up the works is to attach two prefixes or two suffixes to a method, and engineer things such that they both promote themselves when they execute (or demote, it works the same either way). If they promote, A will execute, followed by B, after which A will execute again, followed by B, and so on forever (which is a similar situation to the endless recursion issue that self-adding prefixes raise).

Finally in this section, note that getCardinality will always operate correctly irrespective of any promotions or demotions you apply to a given affix function.


 // -- Example 38 -------------------------------------------------------------

 function prefixfunc_0 () { console.log ("prefixfunc_0 executed"); }
 function prefixfunc_1 () { console.log ("prefixfunc_1 executed"); }

 function suffixfunc_0 () { console.log ("suffixfunc_0 executed"); }
 function suffixfunc_1 () { console.log ("suffixfunc_1 executed"); }

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 AJS           .addWrapper (MyObj, "method_A", prefixfunc_0, suffixfunc_0);

 MyObj         .method_A   ();

 var WrapperCtrlObj = AJS.addWrapper (MyObj, "method_A", prefixfunc_1, suffixfunc_1);

 MyObj         .method_A   ();

 WrapperCtrlObj.demote     ();

 MyObj         .method_A   ();

 -- Output --------------------------------------------------------------------

 prefixfunc_0 executed
 method_A executed
 suffixfunc_0 executed

 prefixfunc_0 executed
 prefixfunc_1 executed
 method_A executed
 suffixfunc_1 executed
 suffixfunc_0 executed

 prefixfunc_1 executed
 prefixfunc_0 executed
 method_A executed
 suffixfunc_0 executed
 suffixfunc_1 executed
      

Combining Auto-Addition/-Removal

As pointed out in the section on removing affix functions from interceptees, it is possible for an affix to remove itself. Moreover, the Adding Multiple Affixes section points out that an affix function can also add new affixes to a given method, including new instances of itself. This lends the potential for, say, a prefix to remove itself from its interceptee, and to then add a fresh prefix (even itself) to the same interceptee.

It seems unlikely that you would ever want to do this, but you must exercise a little care if you do because, in one specific case, the result will not be what you might expect. Note here too that the issues covered below pertain only to the use of the addPrefix or addSuffix methods of the AJS object or of an existing interceptee, they do not apply to the add-Before/-After methods of AffixCtrl objects because AffixCtrls exhibit null behaviour after their corresponding affix functions have been removed.

In the general case, where an interceptee has two or more prefixes, or a prefix and a suffix, and (one of) the prefix(es) removes itself before adding another prefix, then the new prefix will execute subsequently for the same call to the interceptee.

That is, if prefix A removes itself, and then adds prefix B, prefix B will execute after A immediately, or after any intermediate prefixes have executed. This concurs with the insertion rules covered in the Adding Multiple Affixes section above, and so is what you would expect, and the diagram illustrates the situation, while Example 39 demonstrates it in code.

Diagram showing the execution order when a prefix removes itself before adding a new prefix.

 // -- Example 39 -------------------------------------------------------------

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 function prefix_B () { console  .log    ("prefix_B executed"); }
 function prefix_A ()
    {
    console  .log    ("prefix_A executed");
    AffixCtrl.remove ();

    AffixCtrl = AJS.addPrefix (MyObj, "method_A", prefix_B);

    }                                                           // Prefix_A returns here, at which point
                                                                // the newly-added prefix_B will execute.
 var AffixCtrl = AJS.addPrefix (MyObj, "method_A", prefix_A);

 AJS  .addSuffix (MyObj, "method_A", function () { console.log ("Suffix executed"); });

 MyObj.method_A  ();

 -- Output --------------------------------------------------------------------

 prefix_A executed
 prefix_B executed
 method_A executed
 Suffix executed
      

Moreover, this principle holds in a recursive context. If a prefix removes itself, then adds a new prefix (perhaps itself), and then calls the interceptee, the thread of execution will re-enter that prefix (immediately or after any others have executed) because it now forms a part of the execution chain. This is an elaboration on the self-inserting prefix issue considered above, as it folds self removal into the mix. Given this, and assuming that the recursion stops at at that point, the newly-added prefix will return, at which point the interceptee will execute, followed by any suffixes, after which the interceptee will execute (followed by its suffixes). This concurs with the execution pattern explained in the Interception Candidates section of this guide.

This, after expending considerable mental-effort in thinking about the issue is what one would expect, and Example 40 demonstrates the dastardly little mind-twister in code (which recurses twice rather than once in order to spice things up a little).


 // -- Example 40 -------------------------------------------------------------

 var Count = 0;
 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 function prefix ()
    {
    if (Count < 2)                                              // We use a counter to stop the recursion.
       {
       console  .log    ("prefix executed. Count: " + Count++);
       AffixCtrl.remove ();

       AffixCtrl = AJS.addPrefix (MyObj, "method_A", prefix);

       MyObj.method_A ();                                       // Remember that each call here sees the prior
                                                                // execution of prefix, and so prefix will
       }                                                        // execute a total of three times.

    }

 var AffixCtrl = AJS.addPrefix (MyObj, "method_A", prefix);

 AJS  .addSuffix (MyObj, "method_A", function () { console.log ("Suffix executed"); });

 MyObj.method_A  ();

 -- Output --------------------------------------------------------------------

 prefix executed. Count: 0
 prefix executed. Count: 1
 method_A executed
 Suffix executed
 method_A executed
 Suffix executed
 method_A executed
 Suffix executed
      

However, the special case arises where an interceptee has just a single prefix (and no suffixes). If that prefix removes itself before adding a new prefix to the same interceptee, then that new prefix will not execute until the interceptee is called again. In other words, this will not incur the infinite recursion mentioned in the Adding Multiple Affixes section above, and Example 41 demonstrates the idea.

When considering matters only superficially, this behaviour is not what one would expect, and it appears to be the sole point at which the AJS object does not concur with its own insertion rules. In reality, it has nothing to do with those, and so does not constitute a defect on the part of the AJS object. It happens because there is only one prefix and no suffixes, which means that the initial removal-operation returns the interceptee to its normal state, because it has no affix functions at all at that point.

This means that adding a new prefix subsequently creates an entirely new instance of the interception mechanism for the method in question (i.e. a fresh instantiation of the proxy function). This means in turn that the self-removing prefix returns within the context of the original interception-mechanism instance, which knows nothing of the new instance that adding the new prefix emplaced. Hence, only a fresh call to the interceptee will see the new prefix execute, because the new instance of the interception mechanism does not come into play until that point.

This is unavoidable, due to the way that the AJS object operates internally, and there is no other (apparent) way to implement the AJS object that would yield alternative behaviour (while preserving all other existing behavior), and which would also avoid the space leak problem mentioned in the Using Interceptee Methods section above.

Happily, however, the solution is blissfully simple: a lone, self-removing prefix that also adds a new prefix, should perform the addition before the removal, because this will preserve the existing interception-mechanism instance.

Note that this issue does not arise with suffixes because, as pointed out above, when a suffix adds a new suffix to its own interceptee, that new suffix is inserted before all other suffixes in the execution order.


 // -- Example 41 -------------------------------------------------------------

 var MyObj =
    {
    method_A : function () { console.log ("method_A executed"); }
    };

 function prefix ()
    {
    console  .log    ("prefix executed");
    AffixCtrl.remove ();

    AffixCtrl  = AJS.addPrefix (MyObj, "method_A", prefix);

    }

 var AffixCtrl = AJS.addPrefix (MyObj, "method_A", prefix);

 MyObj.method_A ();

 -- Output --------------------------------------------------------------------

 prefix executed
 method_A executed