用于生成构建器DSL的AST工具

时间:2016-10-12 10:00:26

标签: java groovy delegates abstract-syntax-tree builder

我知道Groovy提供了AST注释来动态生成构建器,但它们并不符合我想要做的事情。 我创建了一个很好的构建器DSL,它允许表达涉及强类型的构建(在构建期间利用完整的IDE支持)。这有助于编写它们,以及重构它们。

这看起来像:

     Modem modem = ModemBuilder.modem {
        macAddress = new MacAddress("11:22:33:44:55:66")
        it.returnTechnology = ReturnTechnology.TPM // using it limits scope to current block, easy to discover properties!
        monitorType = MonitoringType.ADVANCED
        forwardId = Id.fromSystemId(17)
        lineUpSettings(outputPowerDbm: 2.dB, bandwidthHz: 10.mhz) // using named params 
        s2Settings {
            transportMode = TransportMode.S2
            centerFrequency = 14.05.ghz
            s2ChannelSettings(name: "mys2") { // using params and closure
                acmEnabled = true
                nominalMod = ModCod._DPSK_6_4
            }
        }
     }  

关键组件是DelegateBuilder

abstract class DelegateBuilder<T> {
   public T build(Closure body)
   {
      return build(body, this)
   }

   public T build(Closure body, DelegateBuilder target, def owner = null)
   {
       if (!owner) owner = body.owner
       def code = body.rehydrate(target, owner, this)
       code.resolveStrategy = Closure.DELEGATE_FIRST
       code(target)
       return target.target as T
   }

   static public <E> E addNamedArguments(Map arguments, E target) {
       arguments.each { k, v -> target."$k" = v }
       return target as E
   }

   abstract T getTarget()

}

然后,您可以使用@Delegate进行自动属性解析,并使用@DelegatesTo进行嵌套,为每个对象创建一个构建器。

以上示例:

class ModemBuilder extends DelegateBuilder<Modem> {

    @Delegate
    Modem target = new Modem()

    static Modem modem(@ClosureParams(value = SimpleType.class, options = "my.company.ModemBuilder")
                   @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = ModemBuilder) Closure body)
    {
        return new ModemBuilder().build(body)
    }

    static Modem modem(Map params,
                   @ClosureParams(value = SimpleType.class, options = "my.company.ModemBuilder")
                   @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = ModemBuilder) Closure body = {})
    {
        return addNamedArguments(params, modem(body))
    }

    LineUpSettings lineUpSettings(@ClosureParams(value = SimpleType.class, options = "my.company.LineUpSettingsBuilder")
                              @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = LineUpSettingsBuilder) Closure body)
    {
        target.lineUpSettings = LineUpSettingsBuilder.lineUpSettings(body)
    }

    LineUpSettings lineUpSettings(Map params,
                              @ClosureParams(value = SimpleType.class, options = "ntc.nms.ts.build.builders.modem.LineUpSettingsBuilder")
                              @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = LineUpSettingsBuilder) Closure body = {})
    {
        target.lineUpSettings = addNamedArguments(params, LineUpSettingsBuilder.lineUpSettings(body))
    }


    S2Settings s2Settings(@ClosureParams(value = SimpleType.class, options = "ntc.nms.ts.build.builders.modem.S2SettingsBuilder")
                      @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = S2SettingsBuilder) Closure body)
    {
        target.s2Settings = S2SettingsBuilder.s2Settings(body)
    }

    S2Settings s2Settings(Map params,
                      @ClosureParams(value = SimpleType.class, options = "my.company.S2SettingsBuilder")
                      @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = S2SettingsBuilder) Closure body = {})
    {
        target.s2Settings = addNamedArguments(params, S2SettingsBuilder.s2Settings(body))
    }

因此,为每个相应的类型创建一个Builder类,并启动两个静态快捷方法。一个也引用每个嵌套对象,以便它可以在一个结构中构建。 这总是相同的代码,并且有助于生成而不是写入,因为除了以下没有涉及的逻辑:

  • 使用您的类型的camelcased名称创建两个静态方法
  • 对于驻留在公司包中的每种类型,创建一个接受构建器代码的方法

由于一切都是惯例(Builder名称是xxxBuilder),这应该由AST执行。我认为可以将其切割为:

@ClosureBuilder(Modem)
class ModemBuilder {}

因为这清楚地说明了存储构建器的位置以及扫描/构建的内容。

我怎么能通过Groovy AST最好地解决这个问题?使用什么工具? AST文档提出了几种方法,我知道有人在开发工具。

0 个答案:

没有答案