aspect.js 11 KB

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