在 Groovy 中使用 AST 转换修改方法
Modify method using AST Transformation in Groovy
我还没来得及做进一步的回答,原来的问题就被删除了,所以我重新发布问题和答案:
我无法使用 AST 转换修改我的方法,因为我不知道如何在修改后执行以前的方法语句。我从方法中提取语句,将其保存在某个临时变量中,但后来,在我修改之后,当我尝试执行它时,我得到 MissingPropertyException: No such 属性: code for class: Calculator as就像我正在尝试使用我的 class 中的 属性 而不是我的方法中的前一个代码块。知道我做错了什么吗?
//annotation
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass("CreatedAtTransformation")
public @interface CreatedAt {
String name() default "";
}
//AST Transformation
@GroovyASTTransformation(phase = SEMANTIC_ANALYSIS)
public class CreatedAtTransformation implements ASTTransformation {
public void visit(ASTNode[] astNodes, SourceUnit source) {
//private final long field creation
ClassNode myClass = (ClassNode) astNodes[1]
ClassNode longClass = new ClassNode(Long.class)
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE | FieldNode.ACC_FINAL, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
myClass.addField(field)
//statement
AstBuilder ab = new AstBuilder()
List<ASTNode> statement = ab.buildFromCode {
timeOfInstantiation
}
//value of the annotation expression(name of the method)
def annotationExpression = astNodes[0].members.name
String annotationValueString = annotationExpression.value
//public final method creation
myClass.addMethod(annotationValueString, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, ClassHelper.Long_TYPE,[] as Parameter[], [] as ClassNode[], statement[0])
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
//modification of method "subtract"
def subtractMethods = myClass.getMethods("subtract")
for(m in subtractMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
}
}
//class
final calculator = new GroovyShell(this.class.getClassLoader()).evaluate('''
@CreatedAt(name = "timestamp")
class Calculator {
int sum = 0
def add(int value) {
int v = sum + value
sum = v
}
def subtract(int value) {
sum -= value
}
}
new Calculator()
''')
//test
assert System.currentTimeMillis() >= calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
def oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.add(10)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.subtract(1)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
println 'well done'
代码比问题实际需要的多。重要的部分是那些方法的修改。提前致谢。
从我的角度来看,我不知道 AST 中的代码是否真的有用,或者只是为了让示例工作、学习 AST 转换...
所以我原来的答案是:
在 AST 转换方面很难获得帮助。虽然这是一个猜测,但我认为它可能与可变范围有关。在 AST 过程的早期有一个 VariableScopeVisitor 运行,它设置变量的范围,但是根据你的 AST 的描述,你正在添加你想要稍后访问的代码。因此,您可能需要再次 运行 VariableScopeVisitor 来修复它,以便现有代码可以访问您注入的代码。
今年我在 GR8Conf.US 做了一个介绍 AST 的演讲,里面有很多资源:
https://docs.google.com/presentation/d/1D4B0YQd0_0HYxK2FOt3xILM9XIymh-G-jh1TbQldbVA/edit?usp=sharing
我会看看这篇讨论变量作用域的文章:
然而真正的答案是
AST Transforms 可能很困难,使用 AstBuilder 虽然方便,但也会引入问题,所以我经常直接使用 API。一旦我学习了宏和宏方法,Groovy 2.5 中的新功能,我可能不必经常使用 API,但在那之前我使用 API 重写了部分代码,如下所示:
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
//AstBuilder abc = new AstBuilder()
Statement s1 = new ExpressionStatement(
new BinaryExpression(
new VariableExpression('timeOfInstantiation'),
Token.newSymbol(org.codehaus.groovy.syntax.Types.EQUAL,0,0),
new MethodCallExpression(
new ClassExpression(new ClassNode(java.lang.System)),
'currentTimeMillis',
ArgumentListExpression.EMPTY_ARGUMENTS
)
)
)
// List<ASTNode> statement1 = abc.buildFromString('timeOfInstantiation = System.currentTimeMillis()')
// List<ASTNode> statement1 = abc.buildFromCode {
// timeOfInstantiation = System.currentTimeMillis()
// for(c in code){
// c.expression
// }
// }
code.add(0,s1)
//m.setCode(statement1[0])
}
这段代码可以稍微清理一下,但它应该可以工作。我还必须将 timeOfInstantiation 更改为私有的,而不是最终的,这样赋值代码就会像这样工作:
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
我还会查看演示文稿中的测试应用程序参考,因为它将允许调试 AST 转换并使用 Groovy 控制台查看转换在做什么。
我还没来得及做进一步的回答,原来的问题就被删除了,所以我重新发布问题和答案:
我无法使用 AST 转换修改我的方法,因为我不知道如何在修改后执行以前的方法语句。我从方法中提取语句,将其保存在某个临时变量中,但后来,在我修改之后,当我尝试执行它时,我得到 MissingPropertyException: No such 属性: code for class: Calculator as就像我正在尝试使用我的 class 中的 属性 而不是我的方法中的前一个代码块。知道我做错了什么吗?
//annotation
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass("CreatedAtTransformation")
public @interface CreatedAt {
String name() default "";
}
//AST Transformation
@GroovyASTTransformation(phase = SEMANTIC_ANALYSIS)
public class CreatedAtTransformation implements ASTTransformation {
public void visit(ASTNode[] astNodes, SourceUnit source) {
//private final long field creation
ClassNode myClass = (ClassNode) astNodes[1]
ClassNode longClass = new ClassNode(Long.class)
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE | FieldNode.ACC_FINAL, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
myClass.addField(field)
//statement
AstBuilder ab = new AstBuilder()
List<ASTNode> statement = ab.buildFromCode {
timeOfInstantiation
}
//value of the annotation expression(name of the method)
def annotationExpression = astNodes[0].members.name
String annotationValueString = annotationExpression.value
//public final method creation
myClass.addMethod(annotationValueString, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, ClassHelper.Long_TYPE,[] as Parameter[], [] as ClassNode[], statement[0])
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
//modification of method "subtract"
def subtractMethods = myClass.getMethods("subtract")
for(m in subtractMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
}
}
//class
final calculator = new GroovyShell(this.class.getClassLoader()).evaluate('''
@CreatedAt(name = "timestamp")
class Calculator {
int sum = 0
def add(int value) {
int v = sum + value
sum = v
}
def subtract(int value) {
sum -= value
}
}
new Calculator()
''')
//test
assert System.currentTimeMillis() >= calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
def oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.add(10)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.subtract(1)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
println 'well done'
代码比问题实际需要的多。重要的部分是那些方法的修改。提前致谢。
从我的角度来看,我不知道 AST 中的代码是否真的有用,或者只是为了让示例工作、学习 AST 转换...
所以我原来的答案是:
在 AST 转换方面很难获得帮助。虽然这是一个猜测,但我认为它可能与可变范围有关。在 AST 过程的早期有一个 VariableScopeVisitor 运行,它设置变量的范围,但是根据你的 AST 的描述,你正在添加你想要稍后访问的代码。因此,您可能需要再次 运行 VariableScopeVisitor 来修复它,以便现有代码可以访问您注入的代码。
今年我在 GR8Conf.US 做了一个介绍 AST 的演讲,里面有很多资源:
https://docs.google.com/presentation/d/1D4B0YQd0_0HYxK2FOt3xILM9XIymh-G-jh1TbQldbVA/edit?usp=sharing
我会看看这篇讨论变量作用域的文章:
然而真正的答案是
AST Transforms 可能很困难,使用 AstBuilder 虽然方便,但也会引入问题,所以我经常直接使用 API。一旦我学习了宏和宏方法,Groovy 2.5 中的新功能,我可能不必经常使用 API,但在那之前我使用 API 重写了部分代码,如下所示:
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
//AstBuilder abc = new AstBuilder()
Statement s1 = new ExpressionStatement(
new BinaryExpression(
new VariableExpression('timeOfInstantiation'),
Token.newSymbol(org.codehaus.groovy.syntax.Types.EQUAL,0,0),
new MethodCallExpression(
new ClassExpression(new ClassNode(java.lang.System)),
'currentTimeMillis',
ArgumentListExpression.EMPTY_ARGUMENTS
)
)
)
// List<ASTNode> statement1 = abc.buildFromString('timeOfInstantiation = System.currentTimeMillis()')
// List<ASTNode> statement1 = abc.buildFromCode {
// timeOfInstantiation = System.currentTimeMillis()
// for(c in code){
// c.expression
// }
// }
code.add(0,s1)
//m.setCode(statement1[0])
}
这段代码可以稍微清理一下,但它应该可以工作。我还必须将 timeOfInstantiation 更改为私有的,而不是最终的,这样赋值代码就会像这样工作:
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
我还会查看演示文稿中的测试应用程序参考,因为它将允许调试 AST 转换并使用 Groovy 控制台查看转换在做什么。