-
Notifications
You must be signed in to change notification settings - Fork 14
/
CallStackIterator.ts
126 lines (110 loc) · 3.37 KB
/
CallStackIterator.ts
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
import { IStackEntry } from "../interface/IStackEntry"
import { IMetadata } from "../interface/IMetadata"
/**
* {class} CallStackIterator
* this class contains all accessible references within the advice call stack and
* controls when advices must be invoked, also whether or not main method should
* be called
*/
export class CallStackIterator {
private index: number = -1
private proceed: boolean = true
/**
* {constructor}
* receives all the metadata and builds up a shorted array with the current
* call stack
* @param {IMetadata} metadata current metadata for this stack
*/
constructor (private metadata: IMetadata, private stack: IStackEntry[], private exceptionEntry?: IStackEntry) {
this.next()
}
/**
* next - this method will resolve by calling the next advice in the call stack
* or calling the main method
*/
next() {
this.index++
let currentEntry = this.stack[this.index]
if(currentEntry === undefined) {
return
}
if(this.proceed && currentEntry === null) {
if(!this.exceptionEntry) {
this.invokeOriginal()
this.next()
return
} else {
try {
this.invokeOriginal()
this.next()
} catch (err) {
this.metadata.exception = err
this.executeAdvice(this.exceptionEntry)
return
}
}
}
if(currentEntry) {
this.executeAdvice(currentEntry)
return
}
}
/**
* stop - this method will alter proceed property of this stack which will
* prevent main method to be invoked
*/
stop () {
this.proceed = false
}
private executeAdvice (currentEntry: IStackEntry) {
currentEntry.advice.apply({ next: this.next.bind(this), stop: this.stop.bind(this) }, this.transformArguments(currentEntry))
if(!this.isAsync(currentEntry.advice)) {
this.next()
}
}
/**
* @private invokeOriginal
*
* this method is responsible of invoke the main method with the correct scope
*/
private invokeOriginal (): void {
this.metadata.result = this.metadata.rawMethod.apply(this.metadata.scope, this.metadata.args)
}
/**
* @private transformArguments
*
* this method organize advice params at requested way
*
* @param {IStackEntry} stackEntry contains various references that were modified
* previously to manage advice params
*
* @return {Array} shorted arguments
*/
private transformArguments (stackEntry: IStackEntry): any[] {
let transformedArguments = []
if (stackEntry.advice.$$params instanceof Array) {
stackEntry.advice.$$params.forEach((requestedArgIndex, index) => {
transformedArguments[index] = stackEntry.args[requestedArgIndex]
})
}
if (typeof stackEntry.advice.$$meta === "number") {
transformedArguments[stackEntry.advice.$$meta] = this.metadata
}
return transformedArguments
}
/**
* @private isAsync
* this method seeks within function body the expression 'this.next' whichs
* means that the advice implementation include some async declaration or
* process
*
* @param {Function} rawAdvice method to be checked
*
* @return {boolean}
*/
private isAsync (rawAdvice: Function): boolean {
return !!rawAdvice
.toString()
.match(/[^a-zA-Z$]this\.next[^a-zA-Z_$0-9]/g)
}
}