8398c9048d
code was modified slightly, so the code differs from the original downloadable 1.9.5 version
367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
dojo.provide("dojox.lang.aspect");
|
|
|
|
(function(){
|
|
var d = dojo, aop = dojox.lang.aspect, ap = Array.prototype,
|
|
contextStack = [], context;
|
|
|
|
// this class implements a topic-based double-linked list
|
|
var Advice = function(){
|
|
this.next_before = this.prev_before =
|
|
this.next_around = this.prev_around =
|
|
this.next_afterReturning = this.prev_afterReturning =
|
|
this.next_afterThrowing = this.prev_afterThrowing =
|
|
this;
|
|
this.counter = 0;
|
|
};
|
|
d.extend(Advice, {
|
|
add: function(advice){
|
|
var dyn = d.isFunction(advice),
|
|
node = {advice: advice, dynamic: dyn};
|
|
this._add(node, "before", "", dyn, advice);
|
|
this._add(node, "around", "", dyn, advice);
|
|
this._add(node, "after", "Returning", dyn, advice);
|
|
this._add(node, "after", "Throwing", dyn, advice);
|
|
++this.counter;
|
|
return node;
|
|
},
|
|
_add: function(node, topic, subtopic, dyn, advice){
|
|
var full = topic + subtopic;
|
|
if(dyn || advice[topic] || (subtopic && advice[full])){
|
|
var next = "next_" + full, prev = "prev_" + full;
|
|
(node[prev] = this[prev])[next] = node;
|
|
(node[next] = this)[prev] = node;
|
|
}
|
|
},
|
|
remove: function(node){
|
|
this._remove(node, "before");
|
|
this._remove(node, "around");
|
|
this._remove(node, "afterReturning");
|
|
this._remove(node, "afterThrowing");
|
|
--this.counter;
|
|
},
|
|
_remove: function(node, topic){
|
|
var next = "next_" + topic, prev = "prev_" + topic;
|
|
if(node[next]){
|
|
node[next][prev] = node[prev];
|
|
node[prev][next] = node[next];
|
|
}
|
|
},
|
|
isEmpty: function(){
|
|
return !this.counter;
|
|
}
|
|
});
|
|
|
|
var getDispatcher = function(){
|
|
|
|
return function(){
|
|
|
|
var self = arguments.callee, // the join point
|
|
advices = self.advices, // list of advices for this joinpoint
|
|
ret, i, a, e, t;
|
|
|
|
// push context
|
|
if(context){ contextStack.push(context); }
|
|
context = {
|
|
instance: this, // object instance
|
|
joinPoint: self, // join point
|
|
depth: contextStack.length, // current level of depth starting from 0
|
|
around: advices.prev_around, // pointer to the current around advice
|
|
dynAdvices: [], // array of dynamic advices if any
|
|
dynIndex: 0 // index of a dynamic advice
|
|
};
|
|
|
|
try{
|
|
// process before events
|
|
for(i = advices.prev_before; i != advices; i = i.prev_before){
|
|
if(i.dynamic){
|
|
// instantiate a dynamic advice
|
|
context.dynAdvices.push(a = new i.advice(context));
|
|
if(t = a.before){ // intentional assignment
|
|
t.apply(a, arguments);
|
|
}
|
|
}else{
|
|
t = i.advice;
|
|
t.before.apply(t, arguments);
|
|
}
|
|
}
|
|
|
|
// process around and after events
|
|
try{
|
|
// call the around advice or the original method
|
|
ret = (advices.prev_around == advices ? self.target : aop.proceed).apply(this, arguments);
|
|
}catch(e){
|
|
// process after throwing and after events
|
|
context.dynIndex = context.dynAdvices.length;
|
|
for(i = advices.next_afterThrowing; i != advices; i = i.next_afterThrowing){
|
|
a = i.dynamic ? context.dynAdvices[--context.dynIndex] : i.advice;
|
|
if(t = a.afterThrowing){ // intentional assignment
|
|
t.call(a, e);
|
|
}
|
|
if(t = a.after){ // intentional assignment
|
|
t.call(a);
|
|
}
|
|
}
|
|
// continue the exception processing
|
|
throw e;
|
|
}
|
|
// process after returning and after events
|
|
context.dynIndex = context.dynAdvices.length;
|
|
for(i = advices.next_afterReturning; i != advices; i = i.next_afterReturning){
|
|
a = i.dynamic ? context.dynAdvices[--context.dynIndex] : i.advice;
|
|
if(t = a.afterReturning){ // intentional assignment
|
|
t.call(a, ret);
|
|
}
|
|
if(t = a.after){ // intentional assignment
|
|
t.call(a);
|
|
}
|
|
}
|
|
// process dojo.connect() listeners
|
|
var ls = self._listeners;
|
|
for(i in ls){
|
|
if(!(i in ap)){
|
|
ls[i].apply(this, arguments);
|
|
}
|
|
}
|
|
}finally{
|
|
// destroy dynamic advices
|
|
for(i = 0; i < context.dynAdvices.length; ++i){
|
|
a = context.dynAdvices[i];
|
|
if(a.destroy){
|
|
a.destroy();
|
|
}
|
|
}
|
|
// pop context
|
|
context = contextStack.length ? contextStack.pop() : null;
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
aop.advise = function(/*Object*/ obj,
|
|
/*String|RegExp|Array*/ method,
|
|
/*Object|Function|Array*/ advice
|
|
){
|
|
// summary:
|
|
// Attach AOP-style advices to a method.
|
|
//
|
|
// description:
|
|
// Attaches AOP-style advices to a method. Can attach several
|
|
// advices at once and operate on several methods of an object.
|
|
// The latter is achieved when a RegExp is specified as
|
|
// a method name, or an array of strings and regular expressions
|
|
// is used. In this case all functional methods that
|
|
// satisfy the RegExp condition are processed. This function
|
|
// returns a handle, which can be used to unadvise, or null,
|
|
// if advising has failed.
|
|
//
|
|
// This function is a convenience wrapper for
|
|
// dojox.lang.aspect.adviseRaw().
|
|
//
|
|
// obj:
|
|
// A source object for the advised function. Cannot be a DOM node.
|
|
// If this object is a constructor, its prototype is advised.
|
|
//
|
|
// method:
|
|
// A string name of the function in obj. In case of RegExp all
|
|
// methods of obj matching the regular expression are advised.
|
|
//
|
|
// advice:
|
|
// An object, which defines advises, or a function, which
|
|
// returns such object, or an array of previous items.
|
|
// The advice object can define following member functions:
|
|
// before, around, afterReturning, afterThrowing, after.
|
|
// If the function is supplied, it is called with a context
|
|
// object once per call to create a temporary advice object, which
|
|
// is destroyed after the processing. The temporary advice object
|
|
// can implement a destroy() method, if it wants to be called when
|
|
// not needed.
|
|
|
|
if(typeof obj != "object"){
|
|
obj = obj.prototype;
|
|
}
|
|
|
|
var methods = [];
|
|
if(!(method instanceof Array)){
|
|
method = [method];
|
|
}
|
|
|
|
// identify advised methods
|
|
for(var j = 0; j < method.length; ++j){
|
|
var t = method[j];
|
|
if(t instanceof RegExp){
|
|
for(var i in obj){
|
|
if(d.isFunction(obj[i]) && t.test(i)){
|
|
methods.push(i);
|
|
}
|
|
}
|
|
}else{
|
|
if(d.isFunction(obj[t])){
|
|
methods.push(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!d.isArray(advice)){ advice = [advice]; }
|
|
|
|
return aop.adviseRaw(obj, methods, advice); // Object
|
|
};
|
|
|
|
aop.adviseRaw = function(/*Object*/ obj,
|
|
/*Array*/ methods,
|
|
/*Array*/ advices
|
|
){
|
|
// summary:
|
|
// Attach AOP-style advices to methods.
|
|
//
|
|
// description:
|
|
// Attaches AOP-style advices to object's methods. Can attach several
|
|
// advices at once and operate on several methods of the object.
|
|
// The latter is achieved when a RegExp is specified as
|
|
// a method name. In this case all functional methods that
|
|
// satisfy the RegExp condition are processed. This function
|
|
// returns a handle, which can be used to unadvise, or null,
|
|
// if advising has failed.
|
|
//
|
|
// obj:
|
|
// A source object for the advised function.
|
|
// Cannot be a DOM node.
|
|
//
|
|
// methods:
|
|
// An array of method names (strings) to be advised.
|
|
//
|
|
// advices:
|
|
// An array of advices represented by objects or functions that
|
|
// return such objects on demand during the event processing.
|
|
// The advice object can define following member functions:
|
|
// before, around, afterReturning, afterThrowing, after.
|
|
// If the function is supplied, it is called with a context
|
|
// object once per call to create a temporary advice object, which
|
|
// is destroyed after the processing. The temporary advice object
|
|
// can implement a destroy() method, if it wants to be called when
|
|
// not needed.
|
|
|
|
if(!methods.length || !advices.length){ return null; }
|
|
|
|
// attach advices
|
|
var m = {}, al = advices.length;
|
|
for(var i = methods.length - 1; i >= 0; --i){
|
|
var name = methods[i], o = obj[name], ao = new Array(al), t = o.advices;
|
|
// create a stub, if needed
|
|
if(!t){
|
|
var x = obj[name] = getDispatcher();
|
|
x.target = o.target || o;
|
|
x.targetName = name;
|
|
x._listeners = o._listeners || [];
|
|
x.advices = new Advice;
|
|
t = x.advices;
|
|
}
|
|
// attach advices
|
|
for(var j = 0; j < al; ++j){
|
|
ao[j] = t.add(advices[j]);
|
|
}
|
|
m[name] = ao;
|
|
}
|
|
|
|
return [obj, m]; // Object
|
|
};
|
|
|
|
aop.unadvise = function(/*Object*/ handle){
|
|
// summary:
|
|
// Detach previously attached AOP-style advices.
|
|
//
|
|
// handle:
|
|
// The object returned by dojox.lang.aspect.advise().
|
|
|
|
if(!handle){ return; }
|
|
var obj = handle[0], methods = handle[1];
|
|
for(var name in methods){
|
|
var o = obj[name], t = o.advices, ao = methods[name];
|
|
for(var i = ao.length - 1; i >= 0; --i){
|
|
t.remove(ao[i]);
|
|
}
|
|
if(t.isEmpty()){
|
|
// check if we can remove all stubs
|
|
var empty = true, ls = o._listeners;
|
|
if(ls.length){
|
|
for(i in ls){
|
|
if(!(i in ap)){
|
|
empty = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(empty){
|
|
// revert to the original method
|
|
obj[name] = o.target;
|
|
}else{
|
|
// replace with the dojo.connect() stub
|
|
var x = obj[name] = d._listener.getDispatcher();
|
|
x.target = o.target;
|
|
x._listeners = ls;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
aop.getContext = function(){
|
|
// summary:
|
|
// Returns the context information for the advice in effect.
|
|
|
|
return context; // Object
|
|
};
|
|
|
|
aop.getContextStack = function(){
|
|
// summary:
|
|
// Returns the context stack, which reflects executing advices
|
|
// up to this point. The array is ordered from oldest to newest.
|
|
// In order to get the active context use dojox.lang.aspect.getContext().
|
|
|
|
return contextStack; // Array
|
|
};
|
|
|
|
aop.proceed = function(){
|
|
// summary:
|
|
// Call the original function (or the next level around advice) in an around advice code.
|
|
//
|
|
// description:
|
|
// Calls the original function (or the next level around advice).
|
|
// Accepts and passes on any number of arguments, and returns a value.
|
|
// This function is valid only in the content of around calls.
|
|
|
|
var joinPoint = context.joinPoint, advices = joinPoint.advices;
|
|
for(var c = context.around; c != advices; c = context.around){
|
|
context.around = c.prev_around; // advance the pointer
|
|
if(c.dynamic){
|
|
var a = context.dynAdvices[context.dynIndex++], t = a.around;
|
|
if(t){
|
|
return t.apply(a, arguments);
|
|
}
|
|
}else{
|
|
return c.advice.around.apply(c.advice, arguments);
|
|
}
|
|
}
|
|
return joinPoint.target.apply(context.instance, arguments);
|
|
};
|
|
})();
|
|
|
|
/*
|
|
Aspect = {
|
|
before: function(arguments){...},
|
|
around: function(arguments){...returns value...},
|
|
afterReturning: function(ret){...},
|
|
afterThrowing: function(excp){...},
|
|
after: function(){...}
|
|
};
|
|
|
|
Context = {
|
|
instance: ..., // the instance we operate on
|
|
joinPoint: ..., // Object (see below)
|
|
depth: ... // current depth of the context stack
|
|
};
|
|
|
|
JoinPoint = {
|
|
target: ..., // the original function being wrapped
|
|
targetName: ... // name of the method
|
|
};
|
|
*/
|