123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- /*
- Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- if(!dojo._hasResource["dojox.lang.aspect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["dojox.lang.aspect"] = true;
- 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
- };
- */
- }
|