Tutorial

Affix and Wrapper Objects

Affix Objects

addPrefix and addSuffix return a reference to an 'Affix' object that corresponds to the prefix or suffix function that has been added to the interceptee. It is through this that the prefix or suffix in question can be manipulated, and through which additional prefixes and suffixes can be applied (as an alternative to re-calling an AJS method, or calling the equivalent methods that the interceptee function-object gains when it acquires an initial affix).

The example listing shows the structure of an Affix object, as if it had been defined statically using object-literal syntax (and with, for clarity's sake, the elision of implementation details). See the API documentation for complete details of the Affix type.

Note that should client code lose all references to an affix object, and where the affix is set to execute an unlimited number of times, then it will be impossible to remove that affix.


 //
 // Members of an Affix object, shown figuratively
 // and as if it had been defined statically using
 // object-literal notation
 //

 var MyAffix =
    {
    addBefore      : function ( ... ) { ... },
    addAfter       : function ( ... ) { ... },

    remove         : function ()      { ... },

    isAttached     : function ()      { ... },
    getAffixTot    : function ()      { ... },

    getFirstSib    : function ()      { ... },
    getLastSib     : function ()      { ... },

    getNext        : function ()      { ... },
    getPrev        : function ()      { ... },

    getFunc        : function ()      { ... },
    setFunc        : function ( ... ) { ... },

    getArg         : function ()      { ... },
    setArg         : function ( ... ) { ... },

    getExecs       : function ()      { ... },
    setExecs       : function ( ... ) { ... },

    getSuspensions : function ()      { ... },

    suspend        : function ()      { ... },
    resume         : function ()      { ... },

    suspendAll     : function ()      { ... },
    resumeAll      : function ()      { ... },

    promote        : function ()      { ... },
    demote         : function ()      { ... }

    };
            

Wrapper Objects

In line with this scheme, addWrapper returns a 'Wrapper' object, which subsumes a reference to two Affix objects (one for the prefix and one for the suffix), and which exposes twenty-two methods that allow collective manipulation of those members.

A Wrapper's Affix-object references are identical in type to the objects returned by addPrefix and addSuffix, and correspond respectively to the prefix and suffix functions that have been wrapped around the interceptee.

The example listing shows (again, figuratively) the structure of a Wrapper object, as if it had been defined statically using object-literal syntax.

As with individual affix objects, should client code lose all references to a given Wrapper object (where there are no copies of its Affix-object references), and where a given affix is set to execute an unlimited number of times, then removing that affix will be impossible.


 //
 // Members of a Wrapper object, shown figuratively
 // and as if it had been defined statically using
 // object-literal notation
 //

 var MyWrapper =
    {
    remove     : function ()      { ... },

    isAttached : function ()      { ... },

    getPrefix  : function ()      { ... },
    getSuffix  : function ()      { ... },

    setFunc    : function ( ... ) { ... },
    setArg     : function ( ... ) { ... },
    setExecs   : function ( ... ) { ... },

    suspend    : function ()      { ... },
    resume     : function ()      { ... },

    suspendAll : function ()      { ... },
    resumeAll  : function ()      { ... },

    promote    : function ()      { ... },
    demote     : function ()      { ... }

    };
            

Affix Suspension and Resumption

Affixes can be 'suspended' and 'resumed'. Suspending an affix means that it remains attached to the interceptee but does not execute when that method is invoked; correspondingly, to resume a suspended affix is to reinstate its operation.

Example 20 illustrates this.

In line with this, calling suspend on a Wrapper object suspends both the prefix and suffix, and calling resume on a Wrapper object reinstates those affixes.

Furthermore, a call to the suspend method of a Wrapper object, where only one of its affixes is active, causes suspension of that affix, while its counterpart's suspension count increases by one. Similarly, a call to the resume method of a Wrapper object, where only one of the affixes is suspended, decrements its suspension count by one (which may mean it is reinstated), while its counterpart remains active.

Note that suspend and resume have stack-like behaviour (in both affix and wrapper objects). This means that, for example, calling suspend twice on an affix requires two calls to resume to reinstate it. Similarly, three calls to suspend require three calls to resume to reinstate the affix, and so on. To determine an affix's degree of suspension, call the getSuspensions method of the Affix object concerned.

Note also that suspending an affix also freezes its execution count. In other words, executing an interceptee that has a suspended affix does not bring that affix one step closer to expiration (where it has a finite execution-limit).

Finally, note that an affix can suspend itself during its execution, but that its execution count is still decremented for that execution. This means that, if that execution is its final one, it will be detached from the interceptee automatically as soon as it finishes executing, in which case its act of self-suspension is redundant.


 // Example 20

 function prefixFunc () { alert ("prefixFunc executed"); }
 function suffixFunc () { alert ("suffixFunc executed"); }

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

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

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

 SuffixObj.suspend ();
 myFunc            ();

 SuffixObj.resume  ();
 myFunc            ();

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

 Output:

 prefixFunc executed
 myFunc executed
 prefixFunc executed
 myFunc executed
 suffixFunc executed
            

Removing Affixes

To remove a given prefix or suffix, simply call the remove method of the relevant Affix object. In line with this, calling the remove method of a given Wrapper object will remove both of its affixes simultaneously.

Note that, should an interceptee loses all of its affixes, the interception mechanism for that interceptee disappears from the execution space, meaning that the relationship between the interceptee and its owner-object returns to normal,

In Example 21 the code applies a single prefix, before calling the interceptee. The prefix is then removed, meaning that a subsequent call to the interceptee results in normal non-prefixed execution.

Note that, once removed, an Affix or Wrapper object cannot be 're-attached' to the original (or any other) interceptee. Moreover, removing an Affix or Wrapper puts the object into an emasculated state, such that calling its methods has no effect, and will return only safe values such as zero or null. Given this, removal of an Affix or Wrapper renders such objects useless thereafter in relation to the AJS object.

Do note also that it is impossible to remove an affix explicitly while the interception mechanism is in the process of executing it and its siblings (although a prefix can remove its interceptee's suffixes, and vice versa). Were this allowed, it would be possible to confuse the interception mechanism with regard to affix-execution order.

Given this, and if an affix must remove itself whilst executing, it should call its own setExecs method, passing a value of 1. Once the thread of execution leaves the affix's scope, its execution count will be decremented to 0, whereupon it will be removed automatically by the AJS object.


 // Example 21

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

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

 myFunc ();

 PrefixObj.remove ();

 myFunc ();

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

 Output:

 prefixFunc executed
 myFunc executed
 myFunc executed
            

Affix-Lifetime Rationale

Affix objects have a 'single use' nature because implementing a mechanism that allowed detached Affixes to be re-attached to a given interceptee would complicate the AJS object. Moreover, such a mechanism would be redundant, as the same effect can be achieved simply by calling the addBefore or addAfter methods of another Affix object to reattach the affix function concerned (AspectJS supports multiple affixes for a given interceptee), or by calling AJS.addPrefix etc.

Do bear in mind, however, that despite the redundant nature of detached Affixes and Wrappers, the memory that these objects occupy will not be recovered by the interpreter's garbage collector until all references to them have expired.

Given this, references to Affix or Wrapper objects that persist after their removal from an interceptee should be set to null, or pointed at something else. Failure to do so will consume memory unnecessarily (called in some circles, and incorrectly, a 'memory leak').

Happily, using the aJS_DbC function yields automatic detection of such redundant objects whenever client code calls one of their methods.

Setting Execution-Maxima

While the execution limit of a new prefix or suffix can be set when calling addPrefix etc, the execution limit of an existing affix can also be set to a new value by calling the setExecs method of its corresponding Affix object.

Similarly, calling the setExecs method of a Wrapper object will set the execution limits for both its affixes simultaneously. If two arguments are provided, the Wrapper's prefixFunc object receives the first, and the suffixFunc receives the second. If only one argument is supplied this is passed to both the prefixFunc and suffixFunc.

Example 22 demonstrates the former case, wherein a prefix is attached to myFunc with an execution limit of infinity (which is the default in the absence of a value for Execs). The execution limit is then reset to three, through the setExecs method of the prefixFunc object returned by addPrefix. Four invocations of myFunc then result in only three executions of prefixFunc.

Note that the value passed as NewExecs refers to the number of times the affix should execute (on a call to the interceptee) after the call to setExecs. It does not denote the total number of times that the affix should have executed since it was attached to the interceptee.

Note also that it is an error to pass undefined or non-numeric values as an execution-limit argument, or to pass values less than 1. To detect such errors automatically you should use the aJS_DbC adjunct to the AJS object.

As noted in the section on affix removal, an affix function can set its own execution maximum, meaning that setting this to a value of 1 will cause the affix to be removed from the interceptee as soon as it ceases execution.


 // Example 22

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

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

 PrefixObj.setExecs (3);

 myFunc ();
 myFunc ();
 myFunc ();
 myFunc ();

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

 Output:

 prefixFunc executed
 myFunc executed
 prefixFunc executed
 myFunc executed
 prefixFunc executed
 myFunc executed
 myFunc executed
            

Getting Execution-Maxima

To determine the number of executions that remain for a given prefix or suffix, use the getExecs method of its corresponding Affix object. This is demonstrated in Example 23,

Here, a prefix is attached to myFunc, with an execution limit of three. After two calls to myFunc, the associated Affix object reports that only one execution remains. A further call to myFunc invokes prefixFunc for a final time, after which the prefix is detached. A subsequent call to getExecs reports, correctly, that no further executions remain for the prefix, underlining the fact that it is safe (although of little use) to interact with a Affix object after its corresponding affix-function has been detached from the interceptee.


 // Example 23

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

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

 myFunc ();
 myFunc ();

 alert  (PrefixObj.getExecs ()
     + " execution(s) remain for this prefix");

 myFunc ();

 alert  (PrefixObj.getExecs ()
     + " execution(s) remain for this prefix");

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

 Output:

 prefixFunc executed
 myFunc executed
 prefixFunc executed
 myFunc executed
 1 execution(s) remain for this prefix
 prefixFunc executed
 myFunc executed
 0 execution(s) remain for this prefix
            

Getting/Setting Func and Arg Parameters

In the same way that one can get and set the execution count for a given affix, it is also possible to access and change the affix function and the value of the static argument stipulated initially by the interceptor.

In other words, and for example, if a suffix is applied to an interceptee, such that function F executes after the execution of the interceptee, then it is possible to replace F with, say, function G.

Similarly, if the interceptor stipulates initially that function F is to act as a suffix to a given interceptee, and is to be passed the numerical value 42 (again, for example) on execution, it is possible to change this value to something (anything) else subsequently.

These effects (and others) are accomplished using the getFunc, setFunc, getArg and setArg methods of the Affix type, which operate in the same way as getExecs and setExecs.

Note that the setExecs, setFunc and setArg methods return nothing (rather than the old parameter they have replaced) because the Affix and Wrapper types are designed to be as interchangeable as possible.

Were, for example, setExecs to return the old value for an Affix's execution limit, this would require that the Wrapper implementation of setExecs also return something. However, given that a Wrapper object subsumes a prefix and suffix, it would have to return a object containing two values. Clearly this object would differ in type from the single scalar-value returned by the Affix implementation, which would erode the substitutability of Affixes and Wrappers.

Affix/Wrapper-Object Differences

On a related theme (and it is here that Affixes and Wrappers part company), the Wrapper type does not support getExecs, getFunc and getArg methods to compliment its set????? methods. Such methods are possible, but would also require definition of 'helper' types that subsumed the relevant values for the prefix and suffix in question.

For example, a Wrapper implementation of getExecs would have to return an object that looked like the following:

... =
   {
   PrefixExecs : ... // Value for prefix's execution limit
   SuffixExecs : ... // Value for suffix's execution limit
   } 

Given this, the execution maximum for the prefix could then be retrieved using the following expression:

var PrefixExecs = MyWrapper.getExecs ().PrefixExecs;

However, as AspectJS stands, the following expression yields precisely the same effect:

var PrefixExecs = MyWrapper.getPrefix ().getExecs ();

The first example yields no compelling advantage over the second, and would complicate the AJS object, therefore the Wrapper type does not support getExecs, getFunc or getArg methods.

Go forward to Part 4 of this tutorial.
Go back to Part 2 of this tutorial.