-
Notifications
You must be signed in to change notification settings - Fork 21
/
aop.js
339 lines (339 loc) · 17.3 KB
/
aop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/**
* jQuery AOP - jQuery plugin to add features of aspect-oriented programming (AOP) to jQuery.
* http://jquery-aop.googlecode.com/
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Version: 1.3
*
* Cross-frame type detection based on Daniel Steigerwald's code (http://daniel.steigerwald.cz)
* http://gist.github.com/204554
*
*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
var aop = (function () {
var _after = 1;
var _afterThrow = 2;
var _afterFinally = 3;
var _before = 4;
var _around = 5;
var _intro = 6;
var _regexEnabled = true;
var _arguments = 'arguments';
var _undef = 'undefined';
var isFunc = function (obj) { return typeof (obj) == 'function'; };
/**
* Private weaving function.
*/
var weaveOne = function (source, method, advice) {
var old = source[method];
// Work-around IE6/7 behavior on some native method that return object instances
if (advice.type != _intro && !isFunc(old)) {
var oldObject = old;
old = function () {
var code = arguments.length > 0 ? _arguments + '[0]' : '';
for (var i = 1; i < arguments.length; i++) {
code += ',' + _arguments + '[' + i + ']';
}
return eval('oldObject(' + code + ');');
};
}
var aspect;
if (advice.type == _after || advice.type == _afterThrow || advice.type == _afterFinally)
aspect = function () {
var returnValue, exceptionThrown = null;
try {
returnValue = old.apply(this, arguments);
}
catch (e) {
exceptionThrown = e;
}
if (advice.type == _after)
if (exceptionThrown == null)
returnValue = advice.value.apply(this, [returnValue, method]);
else
throw exceptionThrown;
else if (advice.type == _afterThrow && exceptionThrown != null)
returnValue = advice.value.apply(this, [exceptionThrown, method]);
else if (advice.type == _afterFinally)
returnValue = advice.value.apply(this, [returnValue, exceptionThrown, method]);
return returnValue;
};
else if (advice.type == _before)
aspect = function () {
advice.value.apply(this, [arguments, method]);
return old.apply(this, arguments);
};
else if (advice.type == _intro)
aspect = function () {
return advice.value.apply(this, arguments);
};
else if (advice.type == _around) {
aspect = function () {
var invocation = { object: this, args: Array.prototype.slice.call(arguments) };
return advice.value.apply(invocation.object, [{ arguments: invocation.args, method: method, proceed: function () {
return old.apply(invocation.object, invocation.args);
}
}]);
};
}
aspect.unweave = function () {
source[method] = old;
source = aspect = old = null;
};
source[method] = aspect;
return aspect;
};
/**
* Private method search
*/
var search = function (source, pointcut, advice) {
var methods = [];
// using getOWnPropertyNames because in modern JS, class methods are not enumerable.
// the previous 'for...in' statement wouldn't see methods on class.prototype which is passed by caller of this function
for (var method of Object.getOwnPropertyNames(source)) {
var item = null;
// Ignore exceptions during method retrival
try {
item = source[method];
}
catch (e) { }
if (item != null && method.match(pointcut.method) && isFunc(item))
methods[methods.length] = { source: source, method: method, advice: advice };
}
return methods;
};
/**
* Private weaver and pointcut parser.
*/
var weave = function (pointcut, advice) {
var source = typeof (pointcut.target.prototype) != _undef ? pointcut.target.prototype : pointcut.target;
var advices = [];
// If it's not an introduction and no method was found, try with regex...
if (advice.type != _intro && typeof (source[pointcut.method]) == _undef) {
// First try directly on target
var methods = search(pointcut.target, pointcut, advice);
// No method found, re-try directly on prototype
if (methods.length == 0)
methods = search(source, pointcut, advice);
for (var i = 0; i < methods.length; i++)
advices[advices.length] = weaveOne(methods[i].source, methods[i].method, methods[i].advice);
}
else {
// Return as an array of one element
advices[0] = weaveOne(source, pointcut.method, advice);
}
return _regexEnabled ? advices : advices[0];
};
return {
/**
* Creates an advice after the defined point-cut. The advice will be executed after the point-cut method
* has completed execution successfully, and will receive one parameter with the result of the execution.
* This function returns an array of weaved aspects (Function).
*
* @example aop.after( {target: window, method: 'MyGlobalMethod'}, function(result) {
* alert('Returned: ' + result);
* return result;
* } );
* @result Array<Function>
*
* @example aop.after( {target: String, method: 'indexOf'}, function(index) {
* alert('Result found at: ' + index + ' on:' + this);
* return index;
* } );
* @result Array<Function>
*
* @name after
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called after the execution of the point-cut. It receives one parameter
* with the result of the point-cut's execution. The function can choose to return this same value or a different one.
*
* @type Array<Function>
* @cat Plugins/General
*/
after: function (pointcut, advice) {
return weave(pointcut, { type: _after, value: advice });
},
/**
* Creates an advice after the defined point-cut only for unhandled exceptions. The advice will be executed
* after the point-cut method only if the execution failed and an exception has been thrown. It will receive one
* parameter with the exception thrown by the point-cut method.
* This function returns an array of weaved aspects (Function).
*
* @example aop.afterThrow( {target: String, method: 'indexOf'}, function(exception) {
* alert('Unhandled exception: ' + exception);
* return -1;
* } );
* @result Array<Function>
*
* @example aop.afterThrow( {target: calculator, method: 'Calculate'}, function(exception) {
* console.log('Unhandled exception: ' + exception);
* throw exception;
* } );
* @result Array<Function>
*
* @name afterThrow
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called after the execution of the point-cut. It receives one parameter
* with the exception thrown by the point-cut method.
*
* @type Array<Function>
* @cat Plugins/General
*/
afterThrow: function (pointcut, advice) {
return weave(pointcut, { type: _afterThrow, value: advice });
},
/**
* Creates an advice after the defined point-cut. The advice will be executed after the point-cut method
* regardless of its success or failure, and it will receive two parameters: one with the
* result of a successful execution or null, and another one with the exception thrown or null.
* This function returns an array of weaved aspects (Function).
*
* @example aop.afterFinally( {target: window, method: 'MyGlobalMethod'}, function(result, exception) {
* if (exception == null)
* return 'Returned: ' + result;
* else
* return 'Unhandled exception: ' + exception;
* } );
* @result Array<Function>
*
* @name afterFinally
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called after the execution of the point-cut regardless of its success or failure.
* It receives two parameters, the first one with the result of a successful execution or null, and the second one with the
* exception or null.
*
* @type Array<Function>
* @cat Plugins/General
*/
afterFinally: function (pointcut, advice) {
return weave(pointcut, { type: _afterFinally, value: advice });
},
/**
* Creates an advice before the defined point-cut. The advice will be executed before the point-cut method
* but cannot modify the behavior of the method, or prevent its execution.
* This function returns an array of weaved aspects (Function).
*
* @example aop.before( {target: window, method: 'MyGlobalMethod'}, function() {
* alert('About to execute MyGlobalMethod');
* } );
* @result Array<Function>
*
* @example aop.before( {target: String, method: 'indexOf'}, function(index) {
* alert('About to execute String.indexOf on: ' + this);
* } );
* @result Array<Function>
*
* @name before
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called before the execution of the point-cut.
*
* @type Array<Function>
* @cat Plugins/General
*/
before: function (pointcut, advice) {
return weave(pointcut, { type: _before, value: advice });
},
/**
* Creates an advice 'around' the defined point-cut. This type of advice can control the point-cut method execution by calling
* the functions '.proceed()' on the 'invocation' object, and also, can modify the arguments collection before sending them to the function call.
* This function returns an array of weaved aspects (Function).
*
* @example aop.around( {target: window, method: 'MyGlobalMethod'}, function(invocation) {
* alert('# of Arguments: ' + invocation.arguments.length);
* return invocation.proceed();
* } );
* @result Array<Function>
*
* @example aop.around( {target: String, method: 'indexOf'}, function(invocation) {
* alert('Searching: ' + invocation.arguments[0] + ' on: ' + this);
* return invocation.proceed();
* } );
* @result Array<Function>
*
* @example aop.around( {target: window, method: /Get(\d+)/}, function(invocation) {
* alert('Executing ' + invocation.method);
* return invocation.proceed();
* } );
* @desc Matches all global methods starting with 'Get' and followed by a number.
* @result Array<Function>
*
*
* @name around
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
* @param Function advice Function containing the code that will get called around the execution of the point-cut. This advice will be called with one
* argument containing one function '.proceed()', the collection of arguments '.arguments', and the matched method name '.method'.
*
* @type Array<Function>
* @cat Plugins/General
*/
around: function (pointcut, advice) {
return weave(pointcut, { type: _around, value: advice });
},
/**
* Creates an introduction on the defined point-cut. This type of advice replaces any existing methods with the same
* name. To restore them, just unweave it.
* This function returns an array with only one weaved aspect (Function).
*
* @example aop.introduction( {target: window, method: 'MyGlobalMethod'}, function(result) {
* alert('Returned: ' + result);
* } );
* @result Array<Function>
*
* @example aop.introduction( {target: String, method: 'log'}, function() {
* alert('Console: ' + this);
* } );
* @result Array<Function>
*
* @name introduction
* @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
* @option Object target Target object to be weaved.
* @option String method Name of the function to be weaved.
* @param Function advice Function containing the code that will be executed on the point-cut.
*
* @type Array<Function>
* @cat Plugins/General
*/
introduction: function (pointcut, advice) {
return weave(pointcut, { type: _intro, value: advice });
},
/**
* Configures global options.
*
* @name setup
* @param Map settings Configuration options.
* @option Boolean regexMatch Enables/disables regex matching of method names.
*
* @example aop.setup( { regexMatch: false } );
* @desc Disable regex matching.
*
* @type Void
* @cat Plugins/General
*/
setup: function (settings) {
_regexEnabled = settings.regexMatch;
}
};
})();
return aop;
});