Groovy AstBuilder使用argumentList作为方法参数

dohp0rv5  于 8个月前  发布在  其他
关注(0)|答案(2)|浏览(64)

我想在方法的开头添加以下if-语句:

@Grant( [ Permission.admin ] )
def someMethod( someArg ){
  if( GrantUtil.checkAuthorization( someArg, Permission.admin ){ // 2nd param comes from annotation
    // nothing
  }else{
    return null
  }

  // the rest
}

为此,我使用了自定义的AST转换:

class GrantASTTransformation implements ASTTransformation {
  
  @Override
  void visit( ASTNode[] nodes, SourceUnit source ) {
    if( !nodes ) return
    
    MethodNode methodNode = (MethodNode) nodes.find{ it in MethodNode }
    
    if( methodNode ) augmentMethod methodNode
  }

  void augmentMethod( MethodNode methodNode ) {       
    List<AnnotationNode> annos = methodNode.getAnnotations new ClassNode( Grant )
    Expression rolesValue = annos.first().getMember 'value'
    
    Expression param = new VariableExpression( methodNode.parameters[ 0 ] )

    Expression args = new ArgumentListExpression( [ param, rolesValue ] )
    StaticMethodCallExpression callExp = new StaticMethodCallExpression( new ClassNode( GrantUtil ), 'checkAuthorization', args )
    BooleanExpression checkExpression = new BooleanExpression( callExp )

    IfStatement ifStmt = new IfStatement( checkExpression, new EmptyStatement(), new ReturnStatement( ConstantExpression.NULL ) )
  
    ((BlockStatement)methodNode.code).statements.add 0, ifStmt
  }
}

而且效果很好
现在我想用一个更透明的AstBuilder重写这个方法,到目前为止我得到了:

void augmentMethod( MethodNode methodNode ) {       
  List<AnnotationNode> annos = methodNode.getAnnotations new ClassNode( Grant )
  Expression rolesValue = annos.first().getMember 'value'
  
  IfStatement ifStmt = (IfStatement)new AstBuilder().buildFromSpec{
      ifStatement{
        booleanExpression{
          staticMethodCall( GrantUtil, 'checkAuthorization' ){
            argumentList{ 
              // this doesn't work
              param
              rolesValue
            }
          }
        }
        //if block
        empty()
        //else block
        returnStatement{ constant null }
      }
  }.first()
    
  ((BlockStatement)methodNode.code).statements.add 0, ifStmt
}

但它无法填充argumentList

groovy.lang.MissingMethodException: No signature of method: static GrantUtil.checkAuthorization() is applicable for argument types: () values: []
Possible solutions: checkAuthorization(io.vertx.ext.web.RoutingContext, java.util.List)

我浏览了test,但找不到将“外部”参数引入argumentList的方法。
做这件事的正确方法是什么?AstBuilder支持吗?
更新:
我尝试了@cfrick的建议,它工作到50%:

staticMethodCall(GrantUtil, 'checkAuthorization') {
  argumentList {
    variable methodNode.parameters[0].name // works like charm!
    constant( [ Permission.user ] as Permission[] )
  }
}

在执行时,我得到了一个异常:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during instruction selection: Cannot generate bytecode for constant: [Lio.lootwalk.domain.Permission;@1e7f19b4 of type: [Lio.lootwalk.domain.Permission;

因此,在argumentList中将枚举对象数组作为constant传递失败。

6mw9ycah

6mw9ycah1#

您可以使用Groovy的GeneralUtils类中的静态方法来改进您的原始解决方案:

class GrantASTTransformation implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source ) {
        nodes?.find{ it in MethodNode }?.with{ augmentMethod it }
    }

    void augmentMethod( MethodNode mn ) {
        def annos = mn.getAnnotations new ClassNode( Grant )
        def rolesValue = annos.first().getMember 'value'
        def param = varX( mn.parameters[ 0 ] )

        def args = args( param, rolesValue )
        def callCheck = callX( new ClassNode( GrantUtil ), 'checkAuthorization', args )
        def notAuthorized = notX( boolX( callCheck ) )
        def returnEarly = returnS( nullX() )

        def guard = ifS( notAuthorized, returnEarly )
        mn.code.statements.add 0, guard
    }
}

至于AstBuilder,它现在不如它的替代品宏那么受欢迎。使用宏编写augmentMethod的一种方法是:

void augmentMethod( MethodNode mn ) {
    def grantNode = ClassHelper.make(Grant)
    def grantUtilNode = ClassHelper.make(GrantUtil)
    def annos = mn.getAnnotations grantNode
    def rolesValue = annos.first().getMember 'value'
    def param = varX( mn.parameters[ 0 ] )
    def guard = macro {
        if (!$v{ classX(grantUtilNode) }.checkAuthorization( $v{ param }, $v{ rolesValue } )) {
            return
        }
    }
    mn.code.statements.add 0, guard
}

你只需要小心在代码中引用类,这就是为什么GrantUtil没有显式出现的原因。
要坚持使用AstBuilder,您可以用途:

void augmentMethod( MethodNode mn ) {
    def annos = mn.getAnnotations new ClassNode( Grant )
    def rolesValue = annos.first().getMember('value').expressions[0]
    def param = varX( mn.parameters[ 0 ] )
    def guard = new AstBuilder().buildFromSpec {
        ifStatement{
            booleanExpression{
                staticMethodCall( GrantUtil, 'checkAuthorization' ) {
                    argumentList {
                        variable param.name
                        property {
                            classExpression rolesValue.objectExpression.type.typeClass
                            constant rolesValue.propertyAsString
                        }
                    }
                }
            }
            empty()
            returnStatement{ constant null }
        }
    }.first()
    mn.code.statements.add 0, guard
}

可能有一个简化的可能,但上述工作。

vktxenjb

vktxenjb2#

错误的原因是传递给argumentList的闭包基本上是一个no-op。你可以在那里写任何你想要的(非DSL)代码,但它不会对生成的代码产生任何直接影响;因此,在运行时最后一次调用时会出现缺少参数的错误。
你必须使用DSL的动词来实际创建参数。在你的例子中,它们是variableconstant
这是生成正确的代码:

staticMethodCall(GrantUtil, 'checkAuthorization') {
    argumentList {
        variable methodNode.parameters[0].name
        constant AbstractASTTransformation.getMemberStringValue(annos.first(), 'value')
    }
}

编辑

对于记录版本,使其与枚举数组一起工作:

static void augmentMethod(MethodNode methodNode) {
    List<AnnotationNode> annos = methodNode.getAnnotations new ClassNode(Grant)

    IfStatement ifStmt = (IfStatement) new AstBuilder().buildFromSpec {
        ifStatement {
            booleanExpression {
                staticMethodCall(GrantUtil, 'checkAuthorization') {
                    argumentList {
                        variable methodNode.parameters[0].name
                        expression << makeList(annos.first(), 'value')
                    }
                }
            }
            // then
            empty()
            // else
            returnStatement { constant null }
        }
    }.first()

    ((BlockStatement) methodNode.code).statements.add 0, ifStmt
}

static ListExpression makeList(AnnotationNode annotation, String name) {
    Expression expr = annotation.getMember(name)
    if (expr == null) {
        return null
    }
    if (expr instanceof ListExpression) {
        return expr
    }
    return new ListExpression([expr])
}

这只是确保,总是有一个列表字面周围,然后只是采取任何在那里;那我们就把它当成是表达了
这适用于:

@Grant([Permission.user, Permission.anon])
@Grant(Permission.admin)

相关问题