aspect.js 11 KB

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