【问题标题】:Groovy DSL: How can I let two delegating classes handle different parts of a DSLScript?Groovy DSL:如何让两个委托类处理 DSLScript 的不同部分?
【发布时间】:2015-07-10 03:57:06
【问题描述】:

假设我有一个这样的 DSL

setup {name = "aDSLScript"}
println "this is common groovy code"
doStuff {println "I'm doing dsl stuff"}

通常会有一个委托类实现“setup”和“doStuff”方法。此外,可以编写通用的 Groovy 代码来执行(println...)。

我正在寻找的是一种分两步执行此操作的方法。在第一步中,只应处理设置方法(println 都不处理)。第二步处理其他部分。

目前,我有两个委派课程。一个实现“设置”,另一个实现“doStuff”。当然,两者都执行 println 语句。

【问题讨论】:

  • 一些代码有助于了解您正在尝试什么。
  • 没有比您在上面看到的更多的代码了。为了解释上面的 DSL 脚本,我进入了类:SetupProcessor,它实现了一个方法设置。实现 doStuff 的 CoreProcessor。该脚本执行 2 次。第一次,我将已解析脚本 (Script.class) 的委托设置为 SetupProcessor。第二次,我将代理设置为 CoreProcessor:def cc = new CompilerConfiguration()def core = new CoreProcessor()cc.scriptBaseClass = DelegatingScript.class.namedef script = sh.parse(dslscript)script.setDelegate(core)script.run()

标签: groovy dsl


【解决方案1】:

您可以创建一个类来拦截来自脚本的方法调用,并让它协调以下方法调用。我是通过反思做到的,但如果你愿意,你可以去声明。这些是模型和脚本类:

class FirstDelegate {
  def setup(closure) { "firstDelegate.setup" }
}

class SecondDelegate {
  def doStuff(closure) { "secondDelegate.doStuff" }
}


class MethodInterceptor {
  def invokedMethods = []

  def methodMissing(String method, args) {
    invokedMethods << [method: method, args: args]
  }

  def delegate() {
    def lookupCalls = { instance ->
      def invokes = instance.metaClass.methods.findResults { method ->
        invokedMethods.findResult { invocation ->
          invocation.method == method.name ? 
              [method: method, invocation: invocation] : null 
        }
      }

      invokes.collect { invoked ->
        invoked.method.invoke(instance, invoked.invocation.args) 
      }
    }

    return lookupCalls(new FirstDelegate()) + lookupCalls(new SecondDelegate())
  }
}

这里是脚本和断言:

import org.codehaus.groovy.control.CompilerConfiguration

def dsl = '''
setup {name = "aDSLScript"}
println "this is common groovy code"
doStuff {println "Ima doing dsl stuff"}
'''


def compiler = new CompilerConfiguration()
compiler.scriptBaseClass = DelegatingScript.class.name

def shell = new GroovyShell(this.class.classLoader, new Binding(), compiler)

script = shell.parse dsl

interceptor = new MethodInterceptor()

script.setDelegate interceptor

script.run()

assert interceptor.invokedMethods*.method == [ 'setup', 'doStuff' ]

assert interceptor.delegate() == 
    ['firstDelegate.setup', 'secondDelegate.doStuff']

请注意,我没有打扰拦截 println 呼叫,这是一个 DefaultGroovyMethods,因此处理起来有点麻烦。

同时让类 MethodInterceptor 实现方法 delegate() 不是一个好主意,因为这允许用户定义的脚本调用它。

【讨论】:

  • 谢谢!这是将 DSL 功能实现分组到不同类的好方法。但遗憾的是它并不能解决我的问题。我想运行脚本两次。第一次运行是进行设置(例如环境的东西)。第二次运行(有时稍后)应该做实际的工作。这就是为什么 println 不被 SetupProcessor 类解释对我来说很重要的原因。
  • 好吧,我首先没有得到控制下一个方法调用的部分。但是 println 或其他常规方法仍然存在问题。难道没有办法避免除“设置”之外的所有方法调用吗?
【解决方案2】:

我找到了一种拆分 DSL 脚本执行的方法。我使用了CompilationCustomizer 从 AST 中删除除了doFirst{} 之外的所有语句。所以第一次运行只会执行doFirst。第二次运行完成其他所有操作。这是一些代码:

class DoFirstProcessor {
    def doFirst(Closure c) {
        c()
    }
}

class TheRestProcessor {
    def doStuff(Closure c) {
        c()
    }

    def methodMissing(String name, args) {
        //nothing to do
    }
}

def dsl = "
println 'this is text that will not be printed out in first line!'

doFirst { println 'First things first: e.g. setting up environment' }

doStuff { println 'doing some stuff now' }

println 'That is it!'
"


class HighlanderCustomizer extends CompilationCustomizer {
    def methodName

    HighlanderCustomizer(def methodName) {
        super(CompilePhase.SEMANTIC_ANALYSIS)
        this.methodName = methodName
    }

    @Override
    void call(SourceUnit sourceUnit, GeneratorContext generatorContext, ClassNode classNode) throws CompilationFailedException {
        def methods = classNode.getMethods()
        methods.each { MethodNode m ->
            m.code.each { Statement st ->
                if (!(st instanceof BlockStatement)) {
                    return
                }
                def removeStmts = []
                st.statements.each { Statement bst ->
                    if (bst instanceof ExpressionStatement) {
                        def ex = bst.expression
                        if (ex instanceof MethodCallExpression) {
                            if (!ex.methodAsString.equals(methodName)) {
                                removeStmts << bst
                            }
                        } else {
                            removeStmts << bst
                        }
                    } else {
                        removeStmts << bst
                    }
                }
                st.statements.removeAll(removeStmts)
            }
        }
    }
}

def cc = new CompilerConfiguration()
cc.addCompilationCustomizers new HighlanderCustomizer("doFirst")
cc.scriptBaseClass = DelegatingScript.class.name

def doFirstShell = new GroovyShell(new Binding(), cc)
def doFirstScript = doFirstShell.parse dsl
doFirstScript.setDelegate new DoFirstProcessor()
doFirstScript.run()

cc.compilationCustomizers.clear()
def shell = new GroovyShell(new Binding(), cc)
def script = shell.parse dsl
script.setDelegate new TheRestProcessor()
script.run()

我做了另一种变体,我一步执行 DSL。请参阅我的博客文章:http://hackserei.metacode.de/?p=247

【讨论】:

  • 非常聪明的解决方案,CompilationCustomizer
猜你喜欢
  • 2011-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
  • 1970-01-01
  • 1970-01-01
  • 2015-10-05
  • 2015-09-03
相关资源
最近更新 更多