source: Dev/trunk/src/client/dojox/lang/aspect.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 10.7 KB
Line 
1dojo.provide("dojox.lang.aspect");
2
3(function(){
4        var d = dojo, aop = dojox.lang.aspect, ap = Array.prototype,
5                contextStack = [], context;
6               
7        // this class implements a topic-based double-linked list
8        var Advice = function(){
9                this.next_before = this.prev_before =
10                this.next_around = this.prev_around =
11                this.next_afterReturning = this.prev_afterReturning =
12                this.next_afterThrowing = this.prev_afterThrowing =
13                        this;
14                this.counter = 0;
15        };
16        d.extend(Advice, {
17                add: function(advice){
18                        var dyn = d.isFunction(advice),
19                                node = {advice: advice, dynamic: dyn};
20                        this._add(node, "before", "", dyn, advice);
21                        this._add(node, "around", "", dyn, advice);
22                        this._add(node, "after", "Returning", dyn, advice);
23                        this._add(node, "after", "Throwing", dyn, advice);
24                        ++this.counter;
25                        return node;
26                },
27                _add: function(node, topic, subtopic, dyn, advice){
28                        var full = topic + subtopic;
29                        if(dyn || advice[topic] || (subtopic && advice[full])){
30                                var next = "next_" + full, prev = "prev_" + full;
31                                (node[prev] = this[prev])[next] = node;
32                                (node[next] = this)[prev] = node;
33                        }
34                },
35                remove: function(node){
36                        this._remove(node, "before");
37                        this._remove(node, "around");
38                        this._remove(node, "afterReturning");
39                        this._remove(node, "afterThrowing");
40                        --this.counter;
41                },
42                _remove: function(node, topic){
43                        var next = "next_" + topic, prev = "prev_" + topic;
44                        if(node[next]){
45                                node[next][prev] = node[prev];
46                                node[prev][next] = node[next];
47                        }
48                },
49                isEmpty: function(){
50                        return !this.counter;
51                }
52        });
53
54        var getDispatcher = function(){
55       
56                return function(){
57                       
58                        var self = arguments.callee,    // the join point
59                                advices = self.advices,         // list of advices for this joinpoint
60                                ret, i, a, e, t;
61
62                        // push context
63                        if(context){ contextStack.push(context); }
64                        context = {
65                                instance: this,                                 // object instance
66                                joinPoint: self,                                // join point
67                                depth: contextStack.length,             // current level of depth starting from 0
68                                around: advices.prev_around,    // pointer to the current around advice
69                                dynAdvices: [],                                 // array of dynamic advices if any
70                                dynIndex: 0                                             // index of a dynamic advice
71                        };
72
73                        try{
74                                // process before events
75                                for(i = advices.prev_before; i != advices; i = i.prev_before){
76                                        if(i.dynamic){
77                                                // instantiate a dynamic advice
78                                                context.dynAdvices.push(a = new i.advice(context));
79                                                if(t = a.before){ // intentional assignment
80                                                        t.apply(a, arguments);
81                                                }
82                                        }else{
83                                                t = i.advice;
84                                                t.before.apply(t, arguments);
85                                        }
86                                }
87
88                                // process around and after events
89                                try{
90                                        // call the around advice or the original method
91                                        ret = (advices.prev_around == advices ? self.target : aop.proceed).apply(this, arguments);
92                                }catch(e){
93                                        // process after throwing and after events
94                                        context.dynIndex = context.dynAdvices.length;
95                                        for(i = advices.next_afterThrowing; i != advices; i = i.next_afterThrowing){
96                                                a = i.dynamic ? context.dynAdvices[--context.dynIndex] : i.advice;
97                                                if(t = a.afterThrowing){ // intentional assignment
98                                                        t.call(a, e);
99                                                }
100                                                if(t = a.after){ // intentional assignment
101                                                        t.call(a);
102                                                }
103                                        }
104                                        // continue the exception processing
105                                        throw e;
106                                }
107                                // process after returning and after events
108                                context.dynIndex = context.dynAdvices.length;
109                                for(i = advices.next_afterReturning; i != advices; i = i.next_afterReturning){
110                                        a = i.dynamic ? context.dynAdvices[--context.dynIndex] : i.advice;
111                                        if(t = a.afterReturning){ // intentional assignment
112                                                t.call(a, ret);
113                                        }
114                                        if(t = a.after){ // intentional assignment
115                                                t.call(a);
116                                        }
117                                }
118                                // process dojo.connect() listeners
119                                var ls = self._listeners;
120                                for(i in ls){
121                                        if(!(i in ap)){
122                                                ls[i].apply(this, arguments);
123                                        }
124                                }
125                        }finally{
126                                // destroy dynamic advices
127                                for(i = 0; i < context.dynAdvices.length; ++i){
128                                        a = context.dynAdvices[i];
129                                        if(a.destroy){
130                                                a.destroy();
131                                        }
132                                }
133                                // pop context
134                                context = contextStack.length ? contextStack.pop() : null;
135                        }
136                       
137                        return ret;
138                };
139        };
140
141        aop.advise = function(/*Object*/ obj,
142                                                /*String|RegExp|Array*/ method,
143                                                /*Object|Function|Array*/ advice
144                                                ){
145                // summary:
146                //              Attach AOP-style advices to a method.
147                //
148                // description:
149                //              Attaches AOP-style advices to a method. Can attach several
150                //              advices at once and operate on several methods of an object.
151                //              The latter is achieved when a RegExp is specified as
152                //              a method name, or an array of strings and regular expressions
153                //              is used. In this case all functional methods that
154                //              satisfy the RegExp condition are processed. This function
155                //              returns a handle, which can be used to unadvise, or null,
156                //              if advising has failed.
157                //
158                //              This function is a convenience wrapper for
159                //              dojox.lang.aspect.adviseRaw().
160                //
161                // obj:
162                //              A source object for the advised function. Cannot be a DOM node.
163                //              If this object is a constructor, its prototype is advised.
164                //
165                // method:
166                //              A string name of the function in obj. In case of RegExp all
167                //              methods of obj matching the regular expression are advised.
168                //
169                // advice:
170                //              An object, which defines advises, or a function, which
171                //              returns such object, or an array of previous items.
172                //              The advice object can define following member functions:
173                //              before, around, afterReturning, afterThrowing, after.
174                //              If the function is supplied, it is called with a context
175                //              object once per call to create a temporary advice object, which
176                //              is destroyed after the processing. The temporary advice object
177                //              can implement a destroy() method, if it wants to be called when
178                //              not needed.
179               
180                if(typeof obj != "object"){
181                        obj = obj.prototype;
182                }
183
184                var methods = [];
185                if(!(method instanceof Array)){
186                        method = [method];
187                }
188               
189                // identify advised methods
190                for(var j = 0; j < method.length; ++j){
191                        var t = method[j];
192                        if(t instanceof RegExp){
193                                for(var i in obj){
194                                        if(d.isFunction(obj[i]) && t.test(i)){
195                                                methods.push(i);
196                                        }
197                                }
198                        }else{
199                                if(d.isFunction(obj[t])){
200                                        methods.push(t);
201                                }
202                        }
203                }
204
205                if(!d.isArray(advice)){ advice = [advice]; }
206
207                return aop.adviseRaw(obj, methods, advice);     // Object
208        };
209       
210        aop.adviseRaw = function(/*Object*/ obj,
211                                                /*Array*/ methods,
212                                                /*Array*/ advices
213                                                ){
214                // summary:
215                //              Attach AOP-style advices to methods.
216                //
217                // description:
218                //              Attaches AOP-style advices to object's methods. Can attach several
219                //              advices at once and operate on several methods of the object.
220                //              The latter is achieved when a RegExp is specified as
221                //              a method name. In this case all functional methods that
222                //              satisfy the RegExp condition are processed. This function
223                //              returns a handle, which can be used to unadvise, or null,
224                //              if advising has failed.
225                //
226                // obj:
227                //              A source object for the advised function.
228                //              Cannot be a DOM node.
229                //
230                // methods:
231                //              An array of method names (strings) to be advised.
232                //
233                // advices:
234                //              An array of advices represented by objects or functions that
235                //              return such objects on demand during the event processing.
236                //              The advice object can define following member functions:
237                //              before, around, afterReturning, afterThrowing, after.
238                //              If the function is supplied, it is called with a context
239                //              object once per call to create a temporary advice object, which
240                //              is destroyed after the processing. The temporary advice object
241                //              can implement a destroy() method, if it wants to be called when
242                //              not needed.
243
244                if(!methods.length || !advices.length){ return null; }
245               
246                // attach advices
247                var m = {}, al = advices.length;
248                for(var i = methods.length - 1; i >= 0; --i){
249                        var name = methods[i], o = obj[name], ao = new Array(al), t = o.advices;
250                        // create a stub, if needed
251                        if(!t){
252                                var x = obj[name] = getDispatcher();
253                                x.target = o.target || o;
254                                x.targetName = name;
255                                x._listeners = o._listeners || [];
256                                x.advices = new Advice;
257                                t = x.advices;
258                        }
259                        // attach advices
260                        for(var j = 0; j < al; ++j){
261                                ao[j] = t.add(advices[j]);
262                        }
263                        m[name] = ao;
264                }
265               
266                return [obj, m];        // Object
267        };
268
269        aop.unadvise = function(/*Object*/ handle){
270                // summary:
271                //              Detach previously attached AOP-style advices.
272                //
273                // handle:
274                //              The object returned by dojox.lang.aspect.advise().
275               
276                if(!handle){ return; }
277                var obj = handle[0], methods = handle[1];
278                for(var name in methods){
279                        var o = obj[name], t = o.advices, ao = methods[name];
280                        for(var i = ao.length - 1; i >= 0; --i){
281                                t.remove(ao[i]);
282                        }
283                        if(t.isEmpty()){
284                                // check if we can remove all stubs
285                                var empty = true, ls = o._listeners;
286                                if(ls.length){
287                                        for(i in ls){
288                                                if(!(i in ap)){
289                                                        empty = false;
290                                                        break;
291                                                }
292                                        }
293                                }
294                                if(empty){
295                                        // revert to the original method
296                                        obj[name] = o.target;
297                                }else{
298                                        // replace with the dojo.connect() stub
299                                        var x = obj[name] = d._listener.getDispatcher();
300                                        x.target = o.target;
301                                        x._listeners = ls;
302                                }
303                        }
304                }
305        };
306       
307        aop.getContext = function(){
308                // summary:
309                //              Returns the context information for the advice in effect.
310               
311                return context; // Object
312        };
313       
314        aop.getContextStack = function(){
315                // summary:
316                //              Returns the context stack, which reflects executing advices
317                //              up to this point. The array is ordered from oldest to newest.
318                //              In order to get the active context use dojox.lang.aspect.getContext().
319               
320                return contextStack;    // Array
321        };
322       
323        aop.proceed = function(){
324                // summary:
325                //              Call the original function (or the next level around advice) in an around advice code.
326                //
327                // description:
328                //              Calls the original function (or the next level around advice).
329                //              Accepts and passes on any number of arguments, and returns a value.
330                //              This function is valid only in the content of around calls.
331               
332                var joinPoint = context.joinPoint, advices = joinPoint.advices;
333                for(var c = context.around; c != advices; c = context.around){
334                        context.around = c.prev_around; // advance the pointer
335                        if(c.dynamic){
336                                var a = context.dynAdvices[context.dynIndex++], t = a.around;
337                                if(t){
338                                        return t.apply(a, arguments);
339                                }
340                        }else{
341                                return c.advice.around.apply(c.advice, arguments);
342                        }
343                }
344                return joinPoint.target.apply(context.instance, arguments);
345        };
346})();
347
348/*
349Aspect = {
350        before: function(arguments){...},
351        around: function(arguments){...returns value...},
352        afterReturning: function(ret){...},
353        afterThrowing: function(excp){...},
354        after: function(){...}
355};
356
357Context = {
358        instance:  ..., // the instance we operate on
359        joinPoint: ..., // Object (see below)
360        depth:     ...  // current depth of the context stack
361};
362
363JoinPoint = {
364        target:     ...,        // the original function being wrapped
365        targetName: ...         // name of the method
366};
367*/
Note: See TracBrowser for help on using the repository browser.