-
Notifications
You must be signed in to change notification settings - Fork 22
/
YTimedASTTransformation.groovy
executable file
·106 lines (89 loc) · 5.07 KB
/
YTimedASTTransformation.groovy
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
package org.grails.plugins.metrics.groovy.ast
import org.apache.log4j.Logger
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.stmt.TryCatchStatement
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.syntax.Token
import org.codehaus.groovy.syntax.Types
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.grails.plugins.metrics.groovy.Metrics
import java.lang.reflect.Modifier
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
@GroovyASTTransformation(phase=CompilePhase.CANONICALIZATION)
public class YTimedASTTransformation implements ASTTransformation {
Logger log = Logger.getLogger(YTimedASTTransformation.class)
public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
if((!nodes) || (!nodes[0]) || (!nodes[1]) || (!(nodes[0] instanceof AnnotationNode)) || (!(nodes[1] instanceof MethodNode))) {
throw new RuntimeException("Internal error: wrong types: $nodes/ $sourceUnit")
}
AnnotationNode annotationNode = nodes[0]
MethodNode methodNode = nodes[1]
String timerName = ensureTimerConfigured(annotationNode, methodNode.declaringClass, methodNode)
makeTimedMethod(timerName, methodNode.declaringClass, methodNode)
}
private void makeTimedMethod(String timerName, ClassNode classNode, MethodNode methodNode){
try {
String contextVariableName = "ctx${timerName}".toString()
//Create a new method, containing the code from the method we are visiting, add to classNode
//Note that the new method is private since there is no expectation for clients to call it directly
def newMethodNode = new MethodNode("_do_${methodNode.name}", Modifier.PRIVATE, methodNode.returnType, methodNode.parameters, methodNode.exceptions, methodNode.code )
classNode.addMethod(newMethodNode)
//Create a call statement to call our new method
def methodCall = new ExpressionStatement( new MethodCallExpression(new VariableExpression('this'), "_do_${methodNode.name}", new ArgumentListExpression( methodNode.parameters ) ) )
//Generate statement to start the timer
def contextAssignmentStatement = new ExpressionStatement(
new DeclarationExpression(
new VariableExpression(contextVariableName),
Token.newSymbol(Types.EQUALS, 0, 0),
new MethodCallExpression(new VariableExpression(timerName), "time", new ArgumentListExpression() )
)
)
//Generate the statement to stop the timer
def contextStopStatement = new ExpressionStatement(
new MethodCallExpression(
new VariableExpression(contextVariableName), "stop", new ArgumentListExpression()
)
)
//New Empty Block to contain method statents
def mBlock = new BlockStatement([], new VariableScope())
//Add start statement, outside of the try block
mBlock.statements.add(contextAssignmentStatement)
//Create and add try/finally, the try will consist of our original method call
mBlock.statements.add(new TryCatchStatement(methodCall, contextStopStatement ))
//rewrite the method
methodNode.code = mBlock
} catch (Throwable t) {
//Not sure that this ia appropriate, but this WILL stop the build which IS what I want
throw new RuntimeException("Unable to execute AST Transformation", t)
}
}
private String ensureTimerConfigured(AnnotationNode annotationNode, ClassNode classNode, MethodNode methodNode){
String timerName = "${methodNode.name}Timer".toString()
//The timer name can be configured from the @Timed Annotation.
def annotationName = annotationNode.getMember('name')
if(annotationName && (annotationName in ConstantExpression) && annotationName.value){
timerName = ((ConstantExpression)annotationName).value
}
//Allowing the code author to define their own timer, we only write a new one if it was not found.
if(!methodNode.declaringClass.fields.find{it.name == timerName}){
FieldNode timerField = new FieldNode(
timerName,
Modifier.PRIVATE,
new ClassNode(com.codahale.metrics.Timer.class),
new ClassNode(classNode.getClass()),
new StaticMethodCallExpression(
new ClassNode(Metrics.class),
'newTimer',
new ArgumentListExpression([
new ConstantExpression(timerName),
])
) )
classNode.addField(timerField)
}
return timerName
}
}