This question already has answers here:
What's wrong with Groovy multi-line String?
(3 answers)
Closed 7 years ago.
We have a habit of overriding the toString method so that we can just get fields value just by calling
println "Object details-->$object"
We thought to write test cases for our build as a good practice and to follow TDD.
My test case failed with some missing lines of data. Test case looks like below:
void "test method:toString"() {
given:
CSV csv = new CSV(accountId: '1', accountName: 'testName')
when:
String exp = "[accountId=" + csv.accountId + ", groupId)" + csv.groupId + ", accountName=" + csv.accountName +"]"
String string = csv.toString()
then:
string == exp
}
Below is my class:
public class CSV {
String accountId
String groupId
String accountName
String chargeMode
String invoiceId
String date
#Override
public String toString() {
return "ChargingCsvLine [accountId="
+ accountId + ", groupId)" + groupId + ", accountName="+
accountName + " "]"
}
}
Test case fails abruptly. Then I gave a careful look and tried with appending '+' in end of line break and not at start of line.
And test case worked properly.
Can anyone tell me whether it's a bug or groovy just accepted both the above cases but case of appending '+' in end of line is the only correct way.
To me it seems that correct way of concatenating using '+' is
"abc"+
"def"
and not
"abc"
+"def"
But why did groovy silently broke in that case and didn't throw any errors. At least operator level exception should be thrown.
Thanks!
Groovy takes your first line (without the ending +) and uses this a return statement and does not execute any further code.
String add1() {
return "1"
+ "2" // never gets executed
}
assert add1()=="1"
If there wheren't a return it would give you a runtime error:
String add1() {
"1" // one statement
+ "2" // another statement - this is the implicit return
}
assert add1()=="12"
And this fails with:
Caught: groovy.lang.MissingMethodException: No signature of method: java.lang.String.positive() is applicable for argument types: () values: []
Possible solutions: notify(), tokenize(), size(), size()
groovy.lang.MissingMethodException: No signature of method: java.lang.String.positive() is applicable for argument types: () values: []
Possible solutions: notify(), tokenize(), size(), size()
at tos.add1(tos.groovy:3)
at tos$add1.callCurrent(Unknown Source)
at tos.run(tos.groovy:6)
shell returned 1
The reason here is, that groovy sees two lines, and strings don't override the positive operator.
It also fails to compile, if static compilation is used:
#groovy.transform.CompileStatic
String add1() {
return "1"
+ "2"
}
assert add1()=="1"
Yields:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
test.groovy: 4: [Static type checking] - Cannot find matching method java.lang.String#positive(). Please check if the declared type is right and if the method exists.
# line 4, column 2.
+ "2"
^
1 error
Takeaways:
groovy has differences to java
don't generate toString in your IDE, when there is #ToString in groovy
don't bang strings together with + in groovy, when there are GStrings.
You can use triple single quotes for multi-lined commands in groovy.
http://www.groovy-lang.org/syntax.html#_string_concatenation
Triple single quoted strings are multiline. You can span the content of
the string across line boundaries without the need to split the string
in several pieces, without contatenation or newline escape characters:
def aMultilineString = '''line one
line two
line three'''
Related
I'm a little bit newer to Groovy and trying to find the best way to split a delimited string. For example I have a string:
1/2/3/4/5/6/7
and I want basically a substring of that. So if I wanted 5 of those values retaining the delimiter my desired result would be
1/2/3/4/5
I've tried the code below which works on my online compiler but not in my actual application. It throws this error "No signature of method: static java.util.Arrays.copyOfRange() is applicable for argument types: ([Ljava.lang.String;, java.lang.Integer, java.lang.Long)"
inputString = "1/2/3/4/5/6/7";
stringDepth = 5;
stringArray = Arrays.copyOfRange(inputString.split("/"), 0, stringDepth);
stringPath = stringArray.join('/');
println "inputString="+inputString;
println "stringDepth="+stringDepth;
println "stringPath="+stringPath;
inputString.split('/').take(stringDepth).join('/')
Should do it?
I have a Groovy method that currently works but is real ugly/hacky looking:
def parseId(String str) {
System.out.println("str: " + str)
int index = href.indexOf("repositoryId")
System.out.println("index: " + index)
int repoIndex = index + 13
System.out.println("repoIndex" + repoIndex)
String repoId = href.substring(repoIndex)
System.out.println("repoId is: " + repoId)
}
When this runs, you might get output like:
str: wsodk3oke30d30kdl4kof94j93jr94f3kd03k043k?planKey=si23j383&repositoryId=31850514
index: 59
repoIndex: 72
repoId is: 31850514
As you can see, I'm simply interested in obtaining the repositoryId value (everything after the = operator) out of the String. Is there a more efficient/Groovier way of doing this or this the only way?
There are a lot of ways to achieve what you want. I'll suggest a simple one using split:
sub = { it.split("repositoryId=")[1] }
str='wsodk3oke30d30kdl4kof94j93jr94f3kd03k043k?planKey=si23j383&repositoryId=31850514'
assert sub(str) == '31850514'
Using a regular expression you could do
def repositoryId = (str =~ "repositoryId=(.*)")[0][1]
The =~ is a regex matcher
or a shortcut regexp - if you are looking only for single match:
String repoId = str.replaceFirst( /.*&repositoryId=(\w+).*/, '$1' )
All the answers here contains regular expressions, however there are a bunch of string methods in Groovy.
String Function
Sample
Description
contains
myStringVar.contains(substring)
Returns true if and only if this string contains the specified sequence of char values
equals
myStringVar.equals(substring)
This is similar to the above but has to be an exact match for the check to return a true value
endsWith
myStringVar.endsWith(suffix)
This method checks the new value contains an ending string
startsWith
myStringVar.startsWith(prefix)
This method checks the new value contains an starting string
equalsIgnoreCase
myStringVar.equalsIgnoreCase(substring)
The same as equals but without case sensitivity
isEmpty
myStringVar.isEmpty()
Checks if myStringVar is populated or not.
matches
myStringVar.matches(substring)
This is the same as equals with the slight difference being that matches takes a regular string as a parameter unlike equals which takes another String object
replace
myStringVar.replace(old,new)
Returns a string resulting from replacing all occurrences of oldChar in this string with newChar
replaceAll
myStringVar.replaceAll(old_regex,new)
Replaces each substring of this string that matches the given regular expression with the given replacement
split
myStringVar.split(regex)
Splits this string around matches of the given regular expression
Source
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)
The code below prints the response variable preceded by spaces.
The number of spaces to be printed before the response variable is equivalent to the difference of the itemNumber and the examineeResponses.
Now, is it possible to pad the string with zero ("0") instead of spaces using String.format?
def converted = examineeResponses+String.format("%${itemNumber - 1}s", response)
Example using the above codes:
examineeResponses = '1' // String
itemNumber = 10 //int
response = '3' // String
Output:
" 3"
Desired output:
"000000003"
I believe you can do this, but it's a bit hard to understand your question:
int itemNumber = 10
String examineeResponses = '1'
char response = '3'
"$response".padLeft( itemNumber - examineeResponses.length(), '0' )
Though I suspect (you don't say) that you just want it printed itemNumbers characters wide. If this is the case, you just need:
"$response".padLeft( itemNumber, '0' )
And you don't need examineeResponses at all.
One of the bits I struggle with in your question is I don't know what examineeResponses.length() is supposed to do (other than throw an error). Another is I'm not sure that this is what you want to do ;-)
You can't zero pad Strings with String.format in Java or Groovy, you can only zero pad numerics
Try something like this:
def formattedString = "blahblah.com?param=${param}&hash=${hash}"
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.