在 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 控制台查看转换在做什么。