Are Groovy String expressions anything more than syntactic sugar? - string

I saw this piece of code in the Groovy tutorial -
import groovy.sql.Sql
sql = Sql.newInstance( 'jdbc:jtds:sqlserver://serverName/dbName-CLASS;domain=domainName', 'username', 'password', 'net.sourceforge.jtds.jdbc.Driver' )
sql.eachRow( 'select * from tableName' ) { println "$it.id -- ${it.firstName} --" }
And it is the first occurrence of Groovy expressions (anything inside a ${} gets evaluated as an expression, not a string). My question is, does this Groovy expression feature actually provide some new functionality? It seems to me like there is nothing here that can be done with a good old string concat. IE -
println it.id + " -- " + it.firstName + " --"

Yes. That's what they are. Being able to add code and variables into strings is a feature of Groovy that make Groovy groovy. String in Groovy can be just like templates.
Now, Groovy strings are enclosed in (") quotes. But a standard Java String in Groovy is enclosed in an apostrophe ('). Standard Java strings in groovy cannot contain variable references or code.
It makes code easier to read. Sometimes looking at all the '+' signs in Java as part of string concatenation is a PITA.
What would you rather code and read:
println "$it.id -- ${it.firstName} --"
or
println it.id + " -- " + it.firstName + " --"
Now, with local variables it becomes much easier to read too:
Groovy
def name = "some name"
def age = 30
def waist = 42
println "Did $name really have a size $waist waist at age $age?"
Java:
String name = "some name";
int age = 30;
int waistSize = 42;
System.out.println("Did " + name + " really have a " + waist + " waist at age " age + "?";

Another thing you can do with them is use them as slightly Lazy Templates, ie:
def x = "abc"
// Closure expansion rather than variable
def s = "x: ${->x}"
// Prints 'x: abc'
println s
// Change x
x = 400
// Prints 'x: 400'
println s
With the pure String concatenation, you'd end up writing a print function, or repeating your concatenation code
Also, don't forget the multi-line string operators -- such as """ -- and the stripMargin method which allows you to :
def (name,age,sender) = [ 'Alice', 30, 'Tim' ]
println """Hello $name
|
|$age today? Congratulations!
|
|$sender""".stripMargin()
Which prints:
Hello Alice
30 today? Congratulations!
Tim
Again, could be done with String concatenation, but ends up being loads more typing and error prone (imo)

Lots more about Groovy strings (various flavours) here: http://groovy.codehaus.org/Strings+and+GString
I believe any string of the form "a string" (i.e. double quotes) is an instance of GString in Groovy (not String). It is GString that provides these extra capabilities.

With the accepted answer here we seem to have landed on the conclusion that, yes, GString (i.e. a double-quoted string with one or more ${<expr>} expressions) is just syntax sugar. (I'm going by the first sentence of the accepted answer: "Yes. That's what they are.")
But that seems to be wrong because of lazy evaluation, as noted above by tim_yates.
Expanding a bit on the above, if one or more expressions in the string are closures, they are only evaluated when toString() is called on the GString. So, in groovyconsole:
def stime = "The time is: ${-> new Date().getTime()}."
println stime
Thread.sleep(500)
println stime
The time is: 1384180043679.
The time is: 1384180044194.
How would you do this with + without creating a new string every time? (The answer, in two Java projects I've worked on, was to invent a Template.java class, to do this sort of thing.) This suggests to me that there is more than just syntax sugar. Or perhaps that it is syntax sugar - but for the GString class, not for java.lang.String or strings generally.
Another example:
def vals = ['a':42]
def tmpl = "The answer is ${-> vals.a}."
println tmpl
vals.a = 43
println tmpl
The answer is 42.
The answer is 43.
I'm not sure I actually like this feature of GString; I'm sort of used to strings being immutable (coming from Java). Groovy's strings are not (quite). It seems like you ought to be able to assume that if two Groovy 'strings' reference the same instance, they will have the same textual value, but that is not quite true.
http://groovy.codehaus.org/Strings+and+GString (about halfway down)

Related

Splitting a string over multiple lines with no extra whitespaces using Groovy?

Lets say I have a long string, and putting it on one line would decrease the readability.
This is would be the solution and it works:
def string = "This is a very\
long string"
But what if im in a Method or an if Statement were the lines are already indented. Then i would have to put the second part of the string like this which isnt very readable.
if (condition) {
def string = "This is a very\
long string"
}
How can i make the output look like this:
This is a very long string
With something like this:
if (condition) {
def string = "This is a very\
long string"
}
If you would want to have the line breaks, there is stripMargin to
help with removal of leading white space (at least until groovy supports
the new multi-line string literals from Java).
But since you don't want the line breaks, I'd just "add" the strings.
This usually is a no-no, because strings are immutable in java and this
will create intermediate instances. Yet then the compiler
should be able to optimize that, if you just add up string
literals (the (dynamic) groovy compiler 4.x does not). But then again,
it might not matter. And if you only want to pay once, make that
a public static final String MY_CONST_STRING = ... somewhere.
if (1) {
println "This is a very " +
"long string " +
"and more"
}
How can i make the output look like this:
This is a very long string
The code you show in the question is an idiomatic way to do it if you really need/want the literal definition to span lines in the source file without including a newline character in the literal:
def string = "This is a very \
long string"
You could also do something like this:
def string = '\
this is a \
very long string.'

Kotlin String.split, ignore when delimiter is inside a quote

I have a string:
Hi there, "Bananas are, by nature, evil.", Hey there.
I want to split the string with commas as the delimiter. How do I get the .split method to ignore the comma inside the quotes, so that it returns 3 strings and not 5.
You can use regex in split method
According to this answer the following regex only matches , outside of the " mark
,(?=(?:[^\"]\"[^\"]\")[^\"]$)
so try this code:
str.split(",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*\$)".toRegex())
You can use split overload that accepts regular expressions for that:
val text = """Hi there, "Bananas are, by nature, evil.", Hey there."""
val matchCommaNotInQuotes = Regex("""\,(?=([^"]*"[^"]*")*[^"]*$)""")
println(text.split(matchCommaNotInQuotes))
Would print:
[Hi there, "Bananas are, by nature, evil.", Hey there.]
Consider reading this answer on how the regular expression works in this case.
You have to use a regular expression capable of handling quoted values. See Java: splitting a comma-separated string but ignoring commas in quotes and C#, regular expressions : how to parse comma-separated values, where some values might be quoted strings themselves containing commas
The following code shows a very simple version of such a regular expression.
fun main(args: Array<String>) {
"Hi there, \"Bananas are, by nature, evil.\", Hey there."
.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)".toRegex())
.forEach { println("> $it") }
}
outputs
> Hi there
> "Bananas are, by nature, evil."
> Hey there.
Be aware of the regex backtracking problem: https://www.regular-expressions.info/catastrophic.html. You might be better off writing a parser.
If you don't want regular expressions:
val s = "Hi there, \"Bananas are, by nature, evil.\", Hey there."
val hold = s.substringAfter("\"").substringBefore("\"")
val temp = s.split("\"")
val splitted: MutableList<String> = (temp[0] + "\"" + temp[2]).split(",").toMutableList()
splitted[1] = "\"" + hold + "\""
splitted is the List you want

What's the difference of strings within single or double quotes in groovy?

def a = "a string"
def b = 'another'
Is there any difference? Or just like javascript to let's input ' and " easier in strings?
Single quotes are a standard java String
Double quotes are a templatable String, which will either return a GString if it is templated, or else a standard Java String. For example:
println 'hi'.class.name // prints java.lang.String
println "hi".class.name // prints java.lang.String
def a = 'Freewind'
println "hi $a" // prints "hi Freewind"
println "hi $a".class.name // prints org.codehaus.groovy.runtime.GStringImpl
If you try templating with single quoted strings, it doesn't do anything, so:
println 'hi $a' // prints "hi $a"
Also, the link given by julx in their answer is worth reading (esp. the part about GStrings not being Strings about 2/3 of the way down.
My understanding is that double-quoted string may contain embedded references to variables and other expressions. For example: "Hello $name", "Hello ${some-expression-here}". In this case a GString will be instantiated instead of a regular String. On the other hand single-quoted strings do not support this syntax and always result in a plain String. More on the topic here:
http://docs.groovy-lang.org/latest/html/documentation/index.html#all-strings
I know this is a very old question, but I wanted to add a caveat.
While it is correct that single (or triple single) quotes prevent interpolation in groovy, if you pass a shell command a single quoted string, the shell will perform parameter substitution, if the variable is an environment variable. Local variables or params will yield a bad substitution.

How to avoid evaluating an GString

I'm working on extending a legacy script system using groovy. The source scripts are "java-like", so it mostly parses as a groovy script with a little pre-processing.
I'm using invokeMethod() and missingMethod() to pass-through the legacy code, enabling me to use closures and other groovy features to enhance the scripts. However, the original script uses strings of the type "$foo" to refer to variables. When a legacy method is caught by missingMethod(), I need it to not evaluate this string as a GString, but simply outputting it verbatim. Is this possible in any way?
there are two way to escape the $foo:
escape the '$' as '\$'
use ' instead of " as string delimiter
example:
def test = "bad"
def s0 = "$test"
def s1 = "\$test"
assert s1 != s0
def s2 = '$test'
assert s2 == s1
println s0
println s1
println s2
So I guess you have to use your preprocessor in order to escape your strings

What's wrong with Groovy multi-line String?

Groovy scripts raises an error:
def a = "test"
+ "test"
+ "test"
Error:
No signature of method: java.lang.String.positive() is
applicable for argument types: () values: []
While this script works fine:
def a = new String(
"test"
+ "test"
+ "test"
)
Why?
As groovy doesn't have EOL marker (such as ;) it gets confused if you put the operator on the following line
This would work instead:
def a = "test" +
"test" +
"test"
as the Groovy parser knows to expect something on the following line
Groovy sees your original def as three separate statements. The first assigns test to a, the second two try to make "test" positive (and this is where it fails)
With the new String constructor method, the Groovy parser is still in the constructor (as the brace hasn't yet closed), so it can logically join the three lines together into a single statement
For true multi-line Strings, you can also use the triple quote:
def a = """test
test
test"""
Will create a String with test on three lines
Also, you can make it neater by:
def a = """test
|test
|test""".stripMargin()
the stripMargin method will trim the left (up to and including the | char) from each line
Similar to stripMargin(), you could also use stripIndent() like
def a = """\
test
test
test""".stripIndent()
Because of
The line with the least number of leading spaces determines the number to remove.
you need to also indent the first "test" and not put it directly after the inital """ (the \ ensures the multi-line string does not start with a newline).
You can tell Groovy that the statement should evaluate past the line ending by adding a pair of parentheses ( ... )
def a = ("test"
+ "test"
+ "test")
A second option is to use a backslash, \, at the end of each line:
def a = "test" \
+ "test" \
+ "test"
FWIW, this is identical to how Python multi-line statements work.

Resources