source: Dev/branches/rest-dojo-ui/client/dojox/lang/aspect.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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.