Reputation: 4090
I try to understand why in the following snippet, the GString is evaluated fine if it's created inside the closure, but will throw an exception if I try to create the String outside and try to evaluate it inside the closures:
map1 = ['foo': 1, 'bar': 2]
map2 = ['foo': 3, 'bar': 4]
dynamicallyGeneratedString = "key1: ${->key1}, val1: ${->value1}, key2: ${->key2}, val2: ${->value2}"
map1.each { key1, value1 ->
map2.each { key2, value2 ->
println "key1: ${->key1}, val1: ${->value1}, key2: ${->key2}, val2: ${->value2}" // works as expected
// println dynamicallyGeneratedString // throws MissingPropertyException
}
}
The desired output in both cases would be:
key1: foo, val1: 1, key2: foo, val2: 3
key1: foo, val1: 1, key2: bar, val2: 4
key1: bar, val1: 2, key2: foo, val2: 3
key1: bar, val1: 2, key2: bar, val2: 4
My goal is to dynamically generate a String depending on some other conditions, and then to lazily evaluate its contents while looping through the maps.
Is this a valid approach at all?
Upvotes: 1
Views: 3361
Reputation: 684
A bit late to the party but here it is.
I wrote the following class, I use it for dynamically building SQL queries without sacrificing security since fill's result is a GStringImpl instance and groovy.sql.Sql correctly transforms it into parametrized database query.
I wasn't sure if Closure's call method is thread-safe (since I am setting delegate property) so I added synchronized to the fill method.
class GTemplate {
def compiledTemplate
GTemplate(String templateSource) {
compiledTemplate = new GroovyShell().evaluate('{-> """' + escape(templateSource) + '""" }')
}
def static GTemplate compile(String templateSource) {
return new GTemplate(templateSource)
}
def synchronized fill(def args) {
compiledTemplate.delegate = args
return compiledTemplate.call()
}
def synchronized fill(Map<?,?> args) {
compiledTemplate.delegate = args
return compiledTemplate.call()
}
private static String escape(String str) {
StringBuilder buf = new StringBuilder()
for(char c : str) {
if ((c == '"') || (c == '\\'))
buf.append('\\')
buf.append(c)
}
return buf.toString()
}
}
map1 = ['foo': 1, 'bar': 2]
map2 = ['foo': 3, 'bar': 4]
dynamicallyGeneratedString = GTemplate.compile('key1: ${->key1}, val1: ${->value1}, key2: ${->key2}, val2: ${->value2}')
map1.each { key1, value1 ->
map2.each { key2, value2 ->
println dynamicallyGeneratedString.fill(key1: key1, value1: value1, key2: key2, value2: value2)
}
}
Upvotes: 1
Reputation: 4090
In addition to using templating as suggested by @Vampire, I can think of two alternative ways of solving the task.
Re-assigning variables inside the closure:
map1 = ['foo': 1, 'bar': 2]
map2 = ['foo': 3, 'bar': 4]
def k1, v1, k2, v2
dynamicString = "key1: ${->k1}, val1: ${->v1}, key2: ${->k2}, val2: ${->v2}"
map1.each { key1, value1 ->
map2.each { key2, value2 ->
k1 = key1
v1 = value1
k2 = key2
v2 = value2
println dynamicString
}
}
Function evaluation:
map1 = ['foo': 1, 'bar': 2]
map2 = ['foo': 3, 'bar': 4]
def myfunc(key1, value1, key2, value2) {
dynamicallyGeneratedString = "key1: ${key1}, val1: ${value1}, key2: ${key2}, val2: ${value2}"
}
map1.each { key1, value1 ->
map2.each { key2, value2 ->
println myfunc(key1, value1, key2, value2)
}
}
I guess it's just a matter of taste... (or are there any performance considerations I am missing?)
Upvotes: 2
Reputation: 38639
The problem is, when you create the GString, it stores the references to the variables. When you then try to evaluate it, those refernces point to nothing and you get the exception.
If you really want to do it that way, I think you have to use a template engine like with
println new groovy.text.GStringTemplateEngine().createTemplate(dynamicallyGeneratedString).make(key1: key1, value1: value1, key2: key2, value2: value2)
Upvotes: 1