我知道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类,并启动两个静态快捷方法。一个也引用每个嵌套对象,以便它可以在一个结构中构建。 这总是相同的代码,并且有助于生成而不是写入,因为除了以下没有涉及的逻辑:
由于一切都是惯例(Builder名称是xxxBuilder),这应该由AST执行。我认为可以将其切割为:
@ClosureBuilder(Modem)
class ModemBuilder {}
因为这清楚地说明了存储构建器的位置以及扫描/构建的内容。
我怎么能通过Groovy AST最好地解决这个问题?使用什么工具? AST文档提出了几种方法,我知道有人在开发工具。