Groovy for testers Apache Groovy

Groovy for testers

[Back home]

Groovy is one of several programming languages designed to run on the JVM. It borrows features from, among others, Ruby (e.g. blocks and other language features), Python (e.g. closures and list comprehension) and Java (Java is legal Groovy). Groovy's syntactic sugar makes it effortless to do tasks that are more cumbersome in Java. It has the power and the ease of library incorporation that Java has. Groovy has become one of the de-facto programming languages for software test automation. It has had Geb and Spock for long, it is the built-in scripting language in SoapUI and JMeter. It plays very well with Selenium, Cucumber and Gherkin. I am trying it out with Playwright at the moment. It is an excellent language for the work that is required for back-end testing. Interact with queues, databases, filesystems and so on with less effort than Java or Python.

Below are happy little code snippets that I have found remarkable or useful. For further reading, the official Groovy documentation is excellent. The unofficial official book on Groovy is this one. For more about the merits of Groovy, read this article by Paul King.

Kick-off
String manipulation
Closures, ranges and other constructs
Collection iteration
Random numbers and strings
Filesystem interaction
Handling xml
Database work
Graphing with charts
Bonus level (run Python code from Groovy)

Kick-off

This is a way to get your important system variables. For example, you are sitting in front of a PC with SoapUI installed, and want an inventory of what you have. Unfortunately the version of Groovy that ships with SoapUI is normally quite old.
println "os.arch: " + System.getProperty("os.arch")
println "java -version".execute().err.text
println "GroovySystem.version: " + GroovySystem.version
println "java.library.path: " + System.properties["java.library.path"]
def localhost = InetAddress.getLocalHost()
println hostName = localhost.getHostName()
Query a specific library:
xmlSlurper = new XmlSlurper()
println xmlSlurper.class.getCanonicalName()
println xmlSlurper.getClass().getPackage().getImplementationTitle()
println xmlSlurper.getClass().getPackage().getImplementationVendor()
println xmlSlurper.getClass().getPackage().getSpecificationVersion()
println xmlSlurper.getClass().getPackage().getImplementationVersion()
println xmlSlurper.class.getProtectionDomain().getCodeSource().getLocation().getPath()
println xmlSlurper.class.getProperties().each { println it }

String manipulation

Test automation is all about pattern matching, replacing values and other ways of manipulating data that is inputted or emitted from systems. For the uninitiated, using assert like this in examples means it is running code, but you can also see the output without actually running it. Neat.
assert 'Groovy'.length() == 6
assert 'Groovy'.substring(3) == 'ovy'
assert 'Groovy'.substring(0,2) == 'Gr'
assert 'Groovy'.take(4) == 'Groo'
assert 'GroovyDancing'[4..-1] == 'vyDancing'
assert 'Groovy'.reverse().take(4).reverse() == 'oovy'

assert 'Groovy'.replace('oo', 'a') == 'Gravy' 
assert 'Groovy'.take(3).replace('o', 'u') == 'Gru'
assert 'Groovy'.replaceAll('o', 'a') == 'Graavy'

assert 'Groovy'.toLowerCase() == 'groovy'
assert 'Groovy'.toUpperCase() == 'GROOVY'
assert 'groovy'.capitalize() == 'Groovy'
assert 'grOOvy'.capitalize() == 'GrOOvy'
assert 'grOOvy'.toLowerCase().capitalize() == 'Groovy'
assert 'Groovy'.reverse() == 'yvoorG'

assert 'Groovy'.count('o') == 2
assert 'Groovy'.contains('oo') == true
assert 'o' in 'Groovy'.collect() == true  //it works on Collection not String
assert 'Groovy'.equals('Groovy') == true
assert 'Groovy'.equalsIgnoreCase('gRoOvY') == true
assert 'Groovy'.startsWith('Gr')
assert 'Groovy'.endsWith('vy')
assert 'Groovy'.isEmpty() == false
assert 'Groovy'.indexOf('v') == 4

assert 'Groovy'.matches(/^Gr.*/) == true
assert 'Groovy'.matches(/.*oo.*/) == true
assert 'Groovy'.matches(/.*vy$/) == true
assert 'Groovy'.split('r') == ['G', 'oovy']

assert 'Groovy'.padLeft(10, '*') == '****Groovy'
assert 'Groovy'.padRight(10, '*') == 'Groovy****'
assert 'Groovy'.center(10, '*') == '**Groovy**'

assert 'Groovy' - 'o' == 'Grovy'
assert 'Groovy' + 'Baby' == 'GroovyBaby'
assert 'Groovy' * 2 == 'GroovyGroovy'

assert 'Groovy '.trim() == 'Groovy'
assert ' Groovy'.trim() == 'Groovy'
assert ' Groovy '.trim() == 'Groovy'
Let's make our own left trim and right trim as an exercise in closures:
def ltrim = { it.replaceAll(/^* /, '') }
def rtrim = { it.replaceAll(/ *$/, '') }
 
assert ltrim(' Groovy') == 'Groovy'
assert rtrim('Groovy ') == 'Groovy'
Or the same with Runtime Metaprogramming (it looks more consistent with trim()):
String.metaClass.ltrim = { return delegate.replaceAll(/^* /, '') }
String.metaClass.rtrim = { return delegate.replaceAll(/ *$/, '') }

assert ' Groovy'.ltrim() == 'Groovy'
assert 'Groovy '.rtrim() == 'Groovy'
assert '      Groovy'.ltrim() == 'Groovy'
assert 'Groovy       '.rtrim() == 'Groovy'
Test if a string contains only lower or uppercase letters:
assert 'Groovy'.matches(/.*[a-z].*/) == true
assert 'Groovy'.matches(/.*[A-Z].*/) == true
assert 'GROOVY'.matches(/.*[a-z].*/) == false
assert 'groovy'.matches(/.*[A-Z].*/) == false
Case insensitive contains:
assert 'A sEnTeNcE'.toLowerCase().contains('t') == true
assert 'A sEnTeNcE'.toUpperCase().contains('C') == true
assert ['A', 'B', 'C']*.toLowerCase().contains('b') == true
assert ['a', 'b', 'c']*.toUpperCase().contains('B') == true
Split a string by Uppercase letters using a Character Class Intersection pattern:
assert 'GroovyBaby'.split("(?=\\p{Upper})").join(" ") == 'Groovy Baby'
assert 'GroovyBaby'.split("(?=\\p{Lower})").join(" ") == 'G r o o v yB a b y'
assert 'Best.Language.Ever'.split("(?=\\p{Punct})").join(" ") == 'Best .Language .Ever'
assert 'Best,Language!Ever'.split("(?=\\p{Punct})").join(" ") == 'Best ,Language !Ever'
assert 'Best.Language.Ever'.split("(?=\\p{Punct})").join(" ").toString().replaceAll("\\.", "") == 'Best Language Ever' //escape the fullstop
Replace part of a string based on a pattern match:
assert '05.00'.replaceAll(/^0/, '') == '5.00'
Capitalisation:
s = "A silly string with words"
Groovy's capitalise only works on the first word of a sentence:
assert s.capitalize() == 'A silly string with words'
To get capitalisation of every word in a sentence, use this:
groovy.grape.Grape.grab(group:'org.apache.commons', module:'commons-lang3', version:'3.3.2')
assert org.apache.commons.lang3.text.WordUtils.capitalizeFully(s) == 'A Silly String With Words'

A side note about @Grab which you will see again further down: This annotation automatcally uses Ivy / Maven to get the jars you require,
and their dependencies. It can be a bit of a dark art to set up, but once it works, it works well. In theory, on a new install of Groovy, without a proxy,
this *should* work out of the box. It does work in groovyConsole if you have internet access. I have it here for reference to the libs that are required, more than as a working example. YMMV.


If you want to get a substring of a string:
assert s[0..9] == 'A silly st'
You will get an error if the substring is larger than string. To prevent this, use take(size):
assert s.take(10) == 'A silly st'
assert s.take(1) == 'A'
assert s.take(100) == 'A silly string with words'
Multiple assignments, more punctuation than Python...
def (a, b) = [1, 2]
assert b == 2
Here we have a list, but it is derived from a string:
assert 'I love Groovy'.tokenize() == ['I', 'love', 'Groovy']
Rounding:
assert Math.round(15.8) == 16
assert Math.round(15.45555555555554) == 15
assert Math.round(0.123456789) == 0
Working with dates:
import java.text.SimpleDateFormat

now = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date())
println "now: ${now}"

today = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
println "today: ${today}"

tenMinsAgo = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date(System.currentTimeMillis()-10*60*1000))
println "tenMinsAgo: ${tenMinsAgo}"

halfHourAgo = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date(System.currentTimeMillis()-30*60*1000))
println "halfHourAgo: ${halfHourAgo}"

hourAgo = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date(System.currentTimeMillis()-1*60*60*1000))
println "hourAgo: ${hourAgo}"

dayAgo = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date(System.currentTimeMillis()-24*60*60*1000))
println "dayAgo: ${dayAgo}"

yesterday = new SimpleDateFormat('yyyy-MM-dd').format(new Date(System.currentTimeMillis()-24*60*60*1000))
println "yesterday: ${yesterday}"

twoDaysAgo = new SimpleDateFormat('yyyy-MM-dd').format(new Date(System.currentTimeMillis()-48*60*60*1000))
println "twoDaysAgo: ${twoDaysAgo}"
Measuring duration:
import groovy.time.TimeCategory
import groovy.time.TimeDuration

Date timeStart = new Date()
//something you want to measure the duration of
Date timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)
println "duration: ${duration}"
println "duration in ms: ${duration.toMilliseconds()}"
println "duration in seconds: ${duration.getSeconds()}"
println "three seconds less duration in ms: ${3000 - duration.toMilliseconds()}"

now = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new Date())
println now
later = '2020-07-15 09:39:47'

if (now < later) {
    println 'now < later'
}
else if (now > later) {
    println 'now > later'
}

duration = TimeCategory.minus(Date.parse('yyyy-MM-dd HH:mm:ss', later), Date.parse('yyyy-MM-dd HH:mm:ss', now))
println "duration: ${duration}"
println "duration in ms: ${duration.toMilliseconds()}"
println "duration in days: ${duration.getDays()}"
println now.dump()
println later.dump()
println duration.dump()
You can reverse-engineer dates in other formats:
Date date1 = Date.parse('dd-MMM-yyyy', '07-OCT-2018')
newDate1 = date1.format('d-M-yyyy') 
assert newDate1 == '7-10-2018'

Date date2 = Date.parse('dd MMM yy', '7 Oct 18')
newDate2 = date2.format('d-M-yyyy')
assert newDate2 == '7-10-2018'

Date date3 = Date.parse('dd/MM/yy', '7/10/18')
newDate3 = date3.format('d-M-yyyy')
assert newDate3 == '7-10-2018'

Closures, ranges and other constructs

Closure:
def squareIt = { int x ->
    square = x**2
    return square
}

assert squareIt(5) == 25
Closure with uncertain amount of arguments (varargs):
def addUp = { int... varargs ->
    added = varargs.sum()
    return added
}

assert addUp(10, 11, 12) == 33
Ranges with closures, similar to a block in Ruby:
1.upto(4) { println "Number ${it}" }

3.times { println "Hello World ${it}" }

'Groovy'.collect { println 'letter:' + it  }

(1..10).collect { println it * 2 }

(1..10).each { println ((it % 5 == 0) ? "${it} is a factor of 5" : "${it} is not a factor of 5") }

('a'..'f').eachWithIndex { it, index ->
    println "${it} is at position ${index + 1}"
}
sum() works a little differently:
def range = (1..1000) as Integer[]
assert range.sum() == 500500
No continue in closures:
def list = (1..10).collect()
In loops you can continue...
for (i in list) {
    if (i < 6) {
        continue
    }
    println i
}
...in closures you can't continue, but you can use return...
list.each {
    if (it < 6) {
	return
    }
    println it
}
...return also works in the catch in a try-catch.
list.each {
    try {
        def undef = 1.0 / 0
    }
    catch(Exception e) {
        println "${e} caught for ${it}, but looping will continue"
        return
    }
}
Elvis operator in a closure:
def urkel() {
    if ('a'.equals('a')) { 
        return 'a'
    }
    else {
	return 'b' 
    }
}
//or
def elvis = ('a'.equals('a') ? 'a' : 'b')

assert urkel() == 'a'
assert elvis == 'a'
One line FizzbBuzz with the Elvis operator:
(1..100).each { println ( (it % 3  ? "" : "Fizz") + ( it % 5 ? "" : "Buzz") ?: it ) }
I had a case where I had to convert a decimal datatype to an integer of the total value of the size before and after the decimal point:
[ 's', '1000', '12.2', '15', 'A', '1', 'D', 'A.b', '12.', '.2'].each {
    if (it.contains(".") && it =~ /\d.*\.\d.*/) { // if it has a decimal and integers on either side
    	println it.tokenize(".").sum { it.toInteger() } //add the two digits
    }
}
Round numbers up to factors of four:
[3, 4, 15, 16, 151694653, 1022].each { upToFour(it) }

def upToFour(num) {
    while (num % 4 != 0) {
    	num++
    }
    println num
}
Return multiple values from a method:
def returnTwo() {
    def a = 1
    def b = 2
    return [a, b]
}

assert returnTwo() == [1, 2]
println returnTwo().getClass() //or
assert returnTwo().class == java.util.ArrayList //or
assert returnTwo() instanceof java.util.ArrayList // we'll use this going forward
Enum and switch in one example:
enum WeekdayColours {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
	
    public static String getColour(WeekdayColours day) {
    	String colour = null
    	switch(day) {
            case Monday: colour = 'GREEN'; break
            case Tuesday: colour = 'YELLOW'; break
            case Wednesday: colour = 'GREY'; break
            case Thursday: colour = 'BLUE'; break
            case Friday: colour = 'BROWN'; break
            case Saturday: colour = 'BLACK'; break
            case Sunday: colour = 'WHITE'; break
            default: println "Something else"
	}
	return colour
    }
}
  
a = WeekdayColours.Friday
assert WeekdayColours.getColour(a) == 'BROWN'
Threads - making sure that one process' threads complete before launching the second process. I used this to put a bunch of messages on a queue, saving correlation ID's in a map, and after all were put, get all responses:
//lists to keep the thread instances
List threads1 = []
List threads2 = []

//first task (e.g. putting on Q)
def doThis = { i, j ->
    sleep(randNum(100))
    println "doThis(${i}) thread: ${j}"
}

//second task (e.g. getting from Q)
def doThat = { i, j ->
    sleep(randNum(20000))
    println "doThat(${i}) thread: ${j}"
}

//call first task (e.g. for eachRow from DB, put msg on Q)
20.times { i ->
    t1 = Thread.start { doThis(i, t1.name) }
    threads1.add(t1)
}

//wait for all task 1 threads to end
for (thread in threads1) {
    thread.join()
    println "doThis joined: ${thread.name}"
}

//call second task (e.g. for each correlation ID, get msg from Q)
20.times { i ->
    t2 = Thread.start { doThat(i, t2.name) }
    threads2.add(t2)
}

//wait for all task 2 threads to end
for (thread in threads2) {
    thread.join()
    println "doThat joined: ${thread.name}"
}

println "all done"

Collection iteration

Lists
[1, 2, 3, 4, 5, 6, 7, 8, 9].each { println it * 2 }

[1, 2, 3, 4, 5, 6, 7, 8, 9].each { i ->
    if ( i % 2 == 0 ) {
        println i * 2
    } 
}
li = [14, 12, 35, 22, -5, 46, 98, 53, 29, 87, 44]
assert li instanceof java.util.ArrayList

assert li[0] == 14
assert li[-1] == 44
assert li.getAt(1) == 12
assert li.get(1) == 12
assert li.getAt(-1) == 44
assert li.count(12) == 1
assert li.intersect([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) == [22, 29]
Change a value:
li.set(1, 88)
assert li.getAt(1) == 88
li.set(1, 12)
assert li.get(1) == 12

assert li.size() == 11
assert li.min() == -5
assert li.max() == 98
assert li.sum() == 435
Let's make our own average method using Runtime Metaprogramming:
List.metaClass.avg = {
    return delegate.size() ? delegate.sum() / delegate.size() : 0
}
assert li.avg() == 39.5454545455
Cool. Continuing with lists:
assert li.join("|") == '14|12|35|22|-5|46|98|53|29|87|44'

assert li.find { it > 97 } == 98
assert li.find { it.toString() =~ /^4.*/ } == 46
assert li.findAll { it.toString() =~ /^4.*/ } == [46, 44]
assert (li.contains(35)) ? 'yes' : 'no' == 'no'

assert li.findAll { it < 15 } == [14, 12, -5]
assert li.findIndexOf{ it == 12 } == 1
assert li.contains(13) == false
assert li.isEmpty() == false

li2 = li
li2.addAll([7, 32])
assert li2 == [14, 12, 35, 22, -5, 46, 98, 53, 29, 87, 44, 7, 32]
li2.removeElement(-5)
assert li2.sort() == [7, 12, 14, 22, 29, 32, 35, 44, 46, 53, 87, 98]
assert li2.reverse() == [98, 87, 53, 46, 44, 35, 32, 29, 22, 14, 12, 7]
Sorting list elements:
li3 = ['i', 'l', 'o', 'v', 'e', 'g', 'r', 'o', 'o', 'v', 'y']
assert li3.each  { it } == ['i', 'l', 'o', 'v', 'e', 'g', 'r', 'o', 'o', 'v', 'y']
assert li3.toSorted().each  { it } == ['e', 'g', 'i', 'l', 'o', 'o', 'o', 'r', 'v', 'v', 'y']
assert li3.toUnique().each  { it } == ['i', 'l', 'o', 'v', 'e', 'g', 'r', 'y']
assert li3.toSorted().toUnique().each  { it } == ['e', 'g', 'i', 'l', 'o', 'r', 'v', 'y']
Sort is case sensitive:
li4 = ['I', 'l', 'o', 'V', 'e', 'G', 'r', 'o', 'O', 'v', 'y']
assert li4.each  { it } == ['I', 'l', 'o', 'V', 'e', 'G', 'r', 'o', 'O', 'v', 'y']
assert li4.toSorted().each  { it } == ['G', 'I', 'O', 'V', 'e', 'l', 'o', 'o', 'r', 'v', 'y']
assert li4.toUnique().each  { it } == ['I', 'l', 'o', 'V', 'e', 'G', 'r', 'O', 'v', 'y']
assert li4.toSorted().toUnique().each  { it } == ['G', 'I', 'O', 'V', 'e', 'l', 'o', 'r', 'v', 'y']
For non-case sensitive sort use this:
assert li4.sort{it.toLowerCase()} == ['e', 'G', 'I', 'l', 'o', 'o', 'O', 'r', 'V', 'v', 'y']
assert li4.sort{it.toUpperCase()}.toUnique() == ['e', 'G', 'I', 'l', 'o', 'O', 'r', 'V', 'v', 'y']
assert li4.sort{it.toUpperCase()} == ['e', 'G', 'I', 'l', 'o', 'o', 'O', 'r', 'V', 'v', 'y']
assert li4.sort{it.toUpperCase()}.toUnique() == ['e', 'G', 'I', 'l', 'o', 'O', 'r', 'V', 'v', 'y']
Regex and capturing Match object:
li5 = ['Blinky', 'Pinky', 'Inky', 'Clyde']

li5.each { i ->			        //use if statement else you'll get an error if there's no match
   def m = i =~ /^(C.*)/ 		//brackets around the match object you want to capture
   if (m) { assert m[0][1] == 'Clyde' } //first capture in match
}

li5.each { i ->
    def m = i =~ /(.*e)$/
    if (m) { assert m[0][1][3] == 'd' } //fourth element of capture
}
Overlaps:
li6 = ['a', 'a', 'b', 'c', 'd', 'e', 'e']
li7 = ['a', 'b', 'c', 'd', 'e', 'f']
def overlaps = li6.intersect(li7)
assert overlaps == ['a', 'b', 'c', 'd', 'e']
Combines:
def combo = li6.plus(li7)
assert combo == ['a', 'a', 'b', 'c', 'd', 'e', 'e', 'a', 'b', 'c', 'd', 'e', 'f']
Difference (not literal, just elements):
def diff = li7.minus(li6) //removes all occurences of element !
assert diff == ['f']
Literal difference (one directional):
def literalDiff = []
li6.each { if (li6.count(it) > 1) { literalDiff.add(it) } }
assert literalDiff == ['a', 'a', 'e', 'e']
Duplicates:
def duplicates = li6.findAll { x -> li6.findAll { y -> y == x }.size() > 1 }.unique()
assert duplicates == ['a', 'e']

Filter:
def (passed, failed) = [39, 56, 67, 76, 82, 89, 91].split{ it > 60 }
// or
println ([39, 56, 67, 76, 82, 89, 91].groupBy { it > 60 ? true : false })

Maps
m = [sky:'blue', grass:'green', ocean:'blue', ground:'brown', sun:'yellow']

assert m['grass'] == 'green'
assert m.get('ocean') == 'blue'
assert m.size() == 5

m.put('night', 'black')
assert m['night'] == 'black'
assert m.size() == 6

m.remove('sun')
assert m.size() == 5

m.each { key, value -> if (key.startsWith("gr")) println value }

def found = m.find{ it.key == "sky" }?.value
assert found.equals("blue")
assert found instanceof java.lang.String

def allFound = m.findAll{ key, value -> key.startsWith('gr') }
assert allFound == ([grass:'green', ground:'brown'])
assert allFound instanceof java.util.HashMap

def allFoundRegex = m.findAll{ key, value -> key.matches(/^gr.*/) }
assert allFoundRegex == ([grass:'green', ground:'brown'])
Reverse sort a map:
map = ['02.00':2, '06.00':6, '01.00':1, '04.00':4, '05.00':5, '03.00':3]
println map.sort { -it.value }
Accessing map keys and values:
inv = ['telephone':'Bell', 'aeroplane':'Wright', 'lightbulb':'Edison',\
       'penicillin':'Pasteur', 'pi':'Archimedes', 'electricity':'Franklin', 'telegraph':'Morse']
Find value by key:
assert inv['pi'] == 'Archimedes'
assert inv.get('pi') == 'Archimedes'
Find key by value (you would think it is straightforward):
assert inv.find { it.value == 'Wright' }?.key == 'aeroplane'
Test if map contains key or value:
assert inv.containsKey('pi') == true
assert inv.containsValue('Archimedes') == true
List as value in a map:
def msl  = [:]
def li8 = ['a', 'b', 'c', 'd', 'e']
def li9 = ['f', 'g', 'h', 'i', 'j']
def li10 = ['k', 'l', 'm', 'n', 'o']

msl.'1'= li8
assert msl.'1'[0] == 'a'
println msl.'1'[1]

msl.each { k, v ->
    println v[2]
}

msl.'2' = li9
msl.'3' = li10
Now access the list items:
Iterator<Map.Entry<String, List>> iter = msl.iterator()
iter.each { i ->
    println "key: ${i.getKey()} val: ${i.getValue()} last list item: ${i.getValue()[-1]}"
}

Random numbers and strings

The ability to create random numbers or strings is often handy for quickly generating test data. Here we define four methods that provide four different types of random data.
def randStr() { //random size
    return new Random().with { (1..(Math.abs(new Random().nextInt() % 5) + 1))
			    .collect { (('A'..'Z')+('0'..'9')+('a'..'z'))
			    .join()[ nextInt( (('A'..'Z')+('0'..'9')+('a'..'z'))
			    .join().length() ) ] }
			    .join() }
}
	
def randNum() { //random size
    return Math.abs(new Random().nextInt() % (Math.abs(new Random().nextInt()))) + 1
}

def randStr(desiredSize) { 
    return new Random().with { (1..desiredSize)
        		    .collect { (('A'..'Z')+('0'..'9')+('a'..'z'))
			    .join()[ nextInt( (('A'..'Z')+('0'..'9')+('a'..'z'))
			    .join().length() ) ] }
			    .join() }
}
	
def randNum(maxSize) {
    return Math.abs(new Random().nextInt() % maxSize) + 1 
}

println "random string: ${randStr()}"
println "random number: ${randNum()}"
println "random string of 10 characters: ${randStr(10)}"
println "random number between 1 and 3: ${randNum(3)}"
println "random number between 1 and a million: ${randNum(1000000)}"

Filesystem interaction

Files are easy to work with:
def myFile = new File("./src/myfile.txt")
myFile.write("Hello from Groovy\n")  //overwrites existing file
myFile << "More text\n" // appends
myFile.append("Last line")

assert myFile.text instanceof java.lang.String
println myFile.text
println myFile.text.size()

assert myFile.readLines() instanceof java.util.ArrayList
println myFile.readLines().size()
Get current file details:
File fileObject = new File("${this.class.getName()}.groovy")
println fileObject.name
println fileObject.absolutePath
Use date and time in a filename:
testDate = new SimpleDateFormat('yyyy-MM-dd HH-mm').format(new Date())
def logFile = new File("./src/log-${testDate}.txt")
Get last modified date and time of files:

new File('.').eachFileRecurse { file ->
    println "On ${new Date(file.lastModified()).format('EEE MMM dd hh:mm:ss a yyyy')} ${file.name} was modified"
}
Recursive search:
import static groovy.io.FileType.FILES
List li = []
new File('.').eachFileRecurse(FILES) { f -> 
    if(f.name.endsWith('.groovy')) {
	println "file name ${f} found" 
	f.readLines().each { l ->
	    if (l.startsWith("import")) {
		println "found import: ${l}"
		li.add(l)
	    }
	}
    }
}

li.sort().unique().each { i ->
    println i
}
Extremely fast file sort:
new File("./src/sorted.txt").write(new File("./src/unsorted.txt").with { it.text = it.readLines().findAll { it }.sort().unique().join('\n') })
File lines as string:
new File("./src/siteSnippets.groovy").eachLine { line ->
    assert line instanceof java.lang.String
    assert line.length() instanceof java.lang.Integer
}
File lines as list:
List listFileContents = new File('./src/siteSnippets.groovy').collect { it }
assert listFileContents instanceof java.util.ArrayList
println listFileContents.size()
Find the longest line in a file:
index = 0
def elementLength = listFileContents[0].length()

listFileContents.eachWithIndex { element, index ->
    if (listFileContents[index].length() > elementLength) {
    	elementLength = listFileContents[index].length()
    }
}
println "${listFileContents[index]} is the longest line in the file"
AntBuilder - handy for manipulating files and directories:
groovy.grape.Grape.grab(group:'org.apache.ant', module:'ant', version:'1.9.8')
groovy.grape.Grape.grab(group:'org.apache.ant', module:'ant-launcher', version:'1.9.8')

def ant = new AntBuilder()

def file = new File(ant.project.baseDir,"src/myfile.txt")
assert file.exists()

ant.replace(file: file, token: ", );", value: ");")

ant.mkdir(dir: "../backup")

ant.copy( todir:"../backup" ) {
    fileset( dir:"./src/" )
  }
  
def zipfile = '../backup/backup.zip'
def current = './src'
ant.zip(destfile: zipfile) {
    fileset(dir: current) {
        include(name: '**/*.*')
    }
}

ant.delete(dir:'../backup',failonerror:false)
Reading CSV files:
new File("src/data.csv").splitEachLine(",") { line ->
	println "FirstName: ${line[0]}; LastName: ${line[1]}"
}
Or you can use a library if you want more features:
groovy.grape.Grape.grab(group:'com.opencsv', module:'opencsv', version:'4.5')
groovy.grape.Grape.grab(group:'com.xlson.groovycsv', module:'groovycsv', version:'1.3')

import static com.xlson.groovycsv.CsvParser.parseCsv

for(line in parseCsv(new FileReader('./src/data.csv'), separator: ',')) {
	   println "$line.FirstName $line.LastName"
}

Handling xml

The simplicity of Groovy's standard library for XML handling is poetic.

HTML's handling of xml - not so poetic, if the display of this XML breaks in your browser, I'm sorry... here it is in a file

def myXmlString = '''
<transaction> <payment> <txID>68246894</txID> <customerName>Huey</customerName> <accountNo type="Current">15778047</accountNo> <txAmount>899</txAmount> </payment> <receipt> <txID>68246895</txID> <customerName>Dewey</customerName> <accountNo type="Current">16288</accountNo> <txAmount>120</txAmount> </receipt> <payment> <txID>68246896</txID> <customerName>Louie</customerName> <accountNo type="Savings">89257067</accountNo> <txAmount>210</txAmount> </payment> <payment> <txID>68246897</txID> <customerName>Dewey</customerName> <accountNo type="Cheque">123321</accountNo> <txAmount>500</txAmount> </payment> </transaction>
'''
Note how the XML handle and gpath in your code is in line with the XML structure and xpath. The root node becomes your variable for the XML string.
def transaction = new XmlSlurper().parseText(myXmlString)
Get some values from nodes and elements:
assert transaction.payment.txAmount[0] == '899'
assert transaction.payment.accountNo.@type[1] == 'Savings'
assert transaction.payment[2].accountNo.@type == 'Cheque'
assert transaction.receipt[0].txID == '68246895'
Find values based on criteria:
transaction.payment.findAll { tx ->
    tx.txAmount.toInteger() > 300
}.each { tx ->
    println "${tx.customerName} made payment of ${tx.txAmount} with transaction id ${tx.txID}."
}
Search with closure and a note on traversing:
def accNos = { node -> node.text() }
def validAccTypes = { node -> node.@type in ['Current', 'Cheque'] }

//descends to lowest node
println transaction.depthFirst().findAll(validAccTypes).collect(accNos) //equivalent of:
println transaction.'**'.findAll(validAccTypes).collect(accNos)

//only iterates first generation child nodes
println transaction.children().findAll(validAccTypes).collect(accNos) //equivalent of:
println transaction.'*'.findAll(validAccTypes).collect(accNos)

//descends to lowest node, one parent node at a time
println transaction.breadthFirst().findAll(validAccTypes).collect(accNos)
Update a node or element's value in the XML:
transaction.payment[1].customerName = 'Bob'
def stringWriter = new StringWriter()
println groovy.xml.XmlUtil.serialize(transaction)
Replace a node:
transaction.receipt[0].replaceNode{
    receipt(receiptNo:"65413"){
    	   receiptdate("2018-09-30")
    	   customerName("Dewey")
	   accountType("Current")
	   accountNo("16288")
	   txAmount("120") }
}
println groovy.xml.XmlUtil.serialize( transaction )
Add a node:
def newTransaction = new XmlParser( false, true ).parseText( myXmlString )
newValue = "        <receiptDate>2018-09-30</receiptDate>"
newNode = new XmlParser( false, true ).parseText( newValue )
newTransaction.'**'.find { it.name() == 'receipt' }.children().add( 2, newNode )
println groovy.xml.XmlUtil.serialize( newTransaction )
Remove a node:
newTransaction.NameValuePairs.NameValuePair.findAll { it.name.text() == 'accountNo' }*.replaceNode{}
println groovy.xml.XmlUtil.serialize( newTransaction )
Print every node in the xml's gpath and it's value (courtesy of tim_yates):
import groovy.util.XmlSlurper
import groovy.util.slurpersupport.GPathResult

def transactions = new XmlSlurper().parseText(myXmlString)

def leaves = transactions.depthFirst().findAll { it.children().size() == 0 }

def path(GPathResult node) {
    def result = [node.name()]
    def pathWalker = [hasNext: { -> node.parent() != node }, next: { -> node = node.parent() }] as Iterator
    (result + pathWalker.collect { it.name() }).reverse().join('/')
}

leaves.each { node ->
    println "${path(node)} = ${node.text()}"
}

Database work

Working with a database in Groovy is so straightforward, I am not even going to say much about it:
groovy.grape.Grape.grab(group:'mysql', module:'mysql-connector-java', version:'5.1.32')
@GrabConfig(systemClassLoader=true)

import groovy.sql.Sql
def myDB = Sql.newInstance('jdbc:mysql://127.0.0.7:3306/employees', 'user1', 'pass1', 'com.mysql.jdbc.Driver')
Selecting one row:
def numberOfRecords = myDB.firstRow("select count(*) as count from employees.employees;")
println "There are ${numberOfRecords.count} in the employees table"
Select the column names:
println sql.firstRow("SELECT * FROM employees.employees;").keySet().collect().join(",")
Rowset as iterable list:
myDB.eachRow("select first_name, last_name from employees.employees;"){
    println "${it.first_name} ${it.last_name}"
}
Rowset to list of lists:
List listOfLists = [][]
myDB.eachRow("select first_name, last_name, age from employees.employees;") { row ->
    List innerList = []
    innerList.add(row.first_name)
    innerList.add(row.last_name)
    innerList.add(row.age.toString())
    listOfLists.add(innerList)
}

listOfLists.each { li ->
    def first_name = li[0]
    def last_name = li[1]
    def age = li[2]
    // do something with it...
}
Changing data:
myDB.execute("use employees;")

myDB.executeUpdate("""update employees.employees
                    set last_name = 'Bradford'
                    where last_lame = 'Simmel'
                    and first_name = 'Bezalel';""")

myDB.executeInsert("""INSERT INTO `employees` VALUES 
                    (10000,'1960-10-01','Peter','Morgan','M','1995-02-10');""")
Remember to tidy up:
myDB.close()
Make a method that inserts for you:

The ? parameters is for JDBC to optimise the query by precompiling it with parameters. Always use it.
If you use "${}" gstrings in your query for variables, the SQL is not optimised by JDBC, and it will carp about it.

def insertEmployee(emp_no, birth_date, first_name, last_name, gender, hire_date) {
    def db = Sql.newInstance('jdbc:mysql://127.0.0.7:3306/employees', 
                           'user1', 'pass1', 'com.mysql.jdbc.Driver')
	
    db.executeInsert("insert into employees VALUES (?, ?, ?, ?, ?, ?);",
                      [emp_no, birth_date, first_name, last_name, gender, hire_date])
    db.close()
}
Connect to MS-SQL Server with domain authentication:
Sql.newInstance("""jdbc:jtds:sqlserver://SERVERNAME:1433/dbname;
                 useLOBs=false;
                 instance=SERVERNAME;
                 autoCommit=true;
                 useNTLMv2=true;
                 domain=MYDOMAIN",
                 "MyUser", "MyPassword", "net.sourceforge.jtds.jdbc.Driver""")

Graphing with charts

For long I looked for a library to use for making graphs. I finally found one that works well with Groovy, and that doesn't require writing lots of lines of code. Examples are from the XChart project examples, that I converted to Groovy examples.

Bar chart:
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart-parent', version:'3.5.2', type:'pom')
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart', version:'3.5.2')

import org.knowm.xchart.CategoryChart
import org.knowm.xchart.CategoryChartBuilder
import org.knowm.xchart.SwingWrapper
import org.knowm.xchart.style.Styler.LegendPosition
import org.knowm.xchart.BitmapEncoder
import org.knowm.xchart.BitmapEncoder.BitmapFormat

CategoryChart chartB = new CategoryChartBuilder().width(800)
                                                .height(600)
                                                .title("Score Histogram")
                                                .xAxisTitle("Score")
                                                .yAxisTitle("Number")
                                                .build()

chartB.getStyler().setLegendPosition(LegendPosition.InsideNW)
chartB.getStyler().setHasAnnotations(true)

chartB.addSeries("test 1", [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], [ 1, 4, 9, 6, 5, 3, 5, 7, 8, 2 ])

new SwingWrapper(chartB).displayChart()
BitmapEncoder.saveBitmap(chartB, "./src/Bar.png", BitmapFormat.PNG)
Pie chart:
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart-parent', version:'3.5.2', type:'pom')
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart', version:'3.5.2')

import java.awt.Color

import org.knowm.xchart.PieChart
import org.knowm.xchart.PieChartBuilder
import org.knowm.xchart.SwingWrapper
import org.knowm.xchart.internal.chartpart.Chart
import org.knowm.xchart.BitmapEncoder
import org.knowm.xchart.BitmapEncoder.BitmapFormat

PieChart chartP = new PieChartBuilder().width(800)
				    .height(600)
				    .title(getClass().getSimpleName())
				    .build()

Color[]  sliceColors = [ new Color(0, 100, 0),
			 new Color(205, 133, 63),
			 new Color(255, 215, 0) ]

chartP.getStyler().setSeriesColors(sliceColors)

chartP.addSeries("Larry", 50)
chartP.addSeries("Curly", 21)
chartP.addSeries("Moe", 39)
   
new SwingWrapper(chartP).displayChart()
BitmapEncoder.saveBitmap(chartP, "./src/Pie.png", BitmapFormat.PNG)
Line chart:
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart-parent', version:'3.5.2', type:'pom')
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart', version:'3.5.2')

import java.awt.Color
import org.knowm.xchart.CategorySeries
import org.knowm.xchart.XYChart
import org.knowm.xchart.XYChartBuilder
import org.knowm.xchart.SwingWrapper
import org.knowm.xchart.XYSeries
import org.knowm.xchart.style.Styler.LegendPosition
import org.knowm.xchart.style.Styler.TextAlignment
import org.knowm.xchart.internal.chartpart.Chart
import org.knowm.xchart.BitmapEncoder
import org.knowm.xchart.BitmapEncoder.BitmapFormat

List xData = new ArrayList()
List yData = new ArrayList()
for (int i = -3; i <= 3; i++) {
	xData.add(i)
	yData.add(Math.pow(10, i))
}

XYChart chartL = new XYChartBuilder().width(800)
				    .height(600)
				    .title("CPU Utilisation")
                                    .xAxisTitle("Seconds")
                                    .yAxisTitle("% CPU")
                                    .build()
 
chartL.getStyler().setChartTitleVisible(true)
chartL.getStyler().setLegendPosition(LegendPosition.InsideNW)
chartL.getStyler().setYAxisMax(100.00)
chartL.getStyler().setYAxisTicksVisible(true)
chartL.getStyler().setXAxisLabelRotation(45)
chartL.getStyler().setPlotGridLinesVisible(true)
chartL.getStyler().setPlotBackgroundColor(new Color(0, 38, 66))
chartL.getStyler().setXAxisLabelAlignment(TextAlignment.Right)
chartL.getStyler().setXAxisLabelRotation(90)
chartL.getStyler().setXAxisLabelRotation(0)
chartL.getStyler().setMarkerSize(0)

xData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]

XYSeries series3 = chartL.addSeries('15 sec', xData, [1, 2, 14, 21, 17, 9, 5, 2, 4, 5, 11, 16, 17, 18, 15, 10, 4, 9, 12, 27, 10, 5, 9, 12, 17, 35, 30, 28, 16, 17, 18, 15, 9, 6, 2, 1])
XYSeries series2 = chartL.addSeries( '5 sec', xData, [1, 2, 6, 5, 4, 5, 3, 2, 3, 4, 5, 6, 5, 5, 4, 4, 3, 4, 6, 5, 4, 3, 4, 5, 3, 5, 3, 4, 5, 6, 5, 5, 4, 3, 1, 1])
XYSeries series1 = chartL.addSeries( '1 sec', xData, [1, 2, 3, 3, 2, 3, 2, 1, 2, 3, 3, 4, 3, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 3, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1])

series1.setLineColor(new Color(2, 19, 255)).setLineWidth(4.00)
series2.setLineColor(new Color(255, 175, 3)).setLineWidth(4.00)
series3.setLineColor(new Color(0x05d43d)).setLineWidth(4.00)

new SwingWrapper(chartL).displayChart()
BitmapEncoder.saveBitmap(chartL, "./src/Line.png", BitmapFormat.PNG)
Dial chart:
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart-parent', version:'3.5.2', type:'pom')
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart', version:'3.5.2')

import java.awt.Color
import org.knowm.xchart.DialChart
import org.knowm.xchart.DialChartBuilder
import org.knowm.xchart.DialSeries
import org.knowm.xchart.SwingWrapper

DialChart chartD = new DialChartBuilder().width(800)
					.height(600)
					.title("CPU")
					.build()

DialSeries dseries = chartD.addSeries("CPU", 0.9381, "93.81 %")
dseries.setFillColor(Color.GRAY) //arrow colour
dseries.setLineColor(Color.WHITE) //arrow outline
chartD.getStyler().setHasAnnotations(true) //the value is displayed

double[] values = [0,   0.1, 0.2,  0.3, 0.4,  0.5, 0.6, 0.7,  0.8,  0.9, 1.0] //the lines in the donut
String[] ticks =  ["0", " ", "20", " ", "40", " ", "60", " ", "80", " ", "100"] //the values of the lines

chartD.getStyler().setAxisTickValues(values)
chartD.getStyler().setAxisTickLabels(ticks)
chartD.getStyler().setAxisTickLabelsVisible(true)
chartD.getStyler().setAxisTicksMarksVisible(true)
chartD.getStyler().setAxisTickMarksColor(Color.BLACK) //lines between the colours on the donut

chartD.getStyler().setPlotBackgroundColor(Color.BLACK)
chartD.getStyler().setChartFontColor(Color.GRAY)
chartD.getStyler().setAntiAlias(true)
chartD.getStyler().setChartTitleVisible(false)
chartD.getStyler().setLegendVisible(false)
chartD.getStyler().setToolTipsEnabled(false)

// the donut
chartD.getStyler().setArcAngle(320)
chartD.getStyler().setDonutThickness(0.15)
chartD.getStyler().setGreenFrom(0)
chartD.getStyler().setGreenTo(0.3)
chartD.getStyler().setGreenColor(new Color(0, 100, 0)) //customise green
chartD.getStyler().setNormalColor(Color.ORANGE)
chartD.getStyler().setNormalFrom(0.3)
chartD.getStyler().setNormalTo(0.7)
chartD.getStyler().setRedFrom(0.7)
chartD.getStyler().setRedTo(1.0)
chartD.getStyler().setRedColor(new Color(128, 0, 0)) //customise red

//play with pointer dimensions
chartD.getStyler().setArrowArcAngle(chartD.getStyler().getArrowArcAngle() + 5.00) //20.0 + 5.00
chartD.getStyler().setArrowArcPercentage(chartD.getStyler().getArrowArcPercentage() + 0.10) //0.15 + 0.10
chartD.getStyler().setArrowLengthPercentage(chartD.getStyler().getArrowLengthPercentage() + 0.05) //0.85 + 0.05

new SwingWrapper(chartD).displayChart()
BitmapEncoder.saveBitmap(chartD, "./src/Dial.png", BitmapFormat.PNG)
Stacked bar chart:
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart-parent', version:'3.5.2', type:'pom')
groovy.grape.Grape.grab(group:'org.knowm.xchart', module:'xchart', version:'3.5.2')

import org.knowm.xchart.CategoryChart
import org.knowm.xchart.CategoryChartBuilder
import org.knowm.xchart.Histogram
import org.knowm.xchart.SwingWrapper
import org.knowm.xchart.style.Styler.LegendPosition

CategoryChart chartS = new CategoryChartBuilder().width(800)
                                                .height(600)
                                                .title("Score Histogram")
                                                .xAxisTitle("Mean")
                                                .yAxisTitle("Count")
                                                .build()

chartS.getStyler().setLegendPosition(LegendPosition.InsideNW)
chartS.getStyler().setAvailableSpaceFill(0.96)
chartS.getStyler().setOverlapped(true)

histogram1 = new Histogram(getGaussianData(10000), 20, -20, 20)
histogram2 = new Histogram(getGaussianData(5000), 20, -20, 20)

CategorySeries series1 = chartS.addSeries("Failed", histogram1.getxAxisData(), histogram1.getyAxisData())
CategorySeries series2 = chartS.addSeries("Passed", histogram2.getxAxisData(), histogram2.getyAxisData())

seriesA.setFillColor(Color.RED)
seriesB.setFillColor(Color.GREEN)

 private List getGaussianData(int count) {
    List data = new ArrayList(count)
    Random r = new Random()
    for (int i = 0; i < count; i++) {
        data.add(r.nextGaussian() * 10)
   }
   return data
}

new SwingWrapper(chartS).displayChart()
BitmapEncoder.saveBitmap(chartS, "./src/StackedBar.png", BitmapFormat.PNG)
Make a wordcloud with a different graphing library:
groovy.grape.Grape.grab(group:'com.kennycason', module:'kumo', version:'1.17', type:'pom')
groovy.grape.Grape.grab(group:'com.kennycason', module:'kumo-core', version:'1.17')
groovy.grape.Grape.grab(group:'com.kennycason', module:'kumo-api', version:'1.17')

import static groovy.io.FileType.FILES
import com.kennycason.kumo.CollisionMode
import com.kennycason.kumo.WordFrequency
import com.kennycason.kumo.WordCloud
import com.kennycason.kumo.bg.RectangleBackground
import com.kennycason.kumo.font.scale.LinearFontScalar
import com.kennycason.kumo.nlp.FrequencyAnalyzer
import java.awt.Dimension
import java.awt.Color

ofile = new File("./src/words.txt")
ofile.write('')

["./src"].each { dir ->
    findWords(dir, ofile)
 }

def findWords(dir, ofile) {
    new File(dir).eachFileRecurse(FILES) {
	if(it.name.endsWith('.groovy')) {
            //println it.name
            it.readLines().each { l ->
                if (! l.contains("//")) {
                    w = l.split()
                    w.each() { word ->
			if (! [ "//", "\\", '=', '==', '{', '}', '[', ']', '->',
                                '|', '>=', '<=', '>', '<', '+', '-'].contains(word)) {
                            //println word
			    ofile.append("${word}\n")
                        }
                    }
		}
	    }
	}
    }
}

FrequencyAnalyzer frequencyAnalyzer = new FrequencyAnalyzer()
List wordFrequencies = frequencyAnalyzer.load("./src/words.txt")
Dimension dimension = new Dimension(600, 600)
WordCloud wordCloud = new WordCloud(dimension, CollisionMode.RECTANGLE)
wordCloud.setPadding(0)
wordCloud.setBackground(new RectangleBackground(dimension))
wordCloud.setFontScalar(new LinearFontScalar(10, 40))
wordCloud.build(wordFrequencies)
wordCloud.writeToFile("./src/wordcloud.png")

Bonus level

I once needed to execute Python code from within Groovy. Don't ask. I used Jython.
groovy.grape.Grape.grab(group:'org.python', module:'jython-standalone', version:'2.7.1b3')

import org.python.util.PythonInterpreter
import org.python.core.*

class AJythonCall {
    static void callJython() throws PyException {
	
	PythonInterpreter jython = new PythonInterpreter()

	println "Hello, Jython"
	jython.exec("import sys")
	jython.exec("print sys")

	jython.set("a", new PyInteger(42))
	jython.exec("print a")
		
	jython.exec("x = 2 + 2")
	PyObject x = jython.get("x")

	println "x: " + x
	println "Bye, Jython"
    }
}

//or

class AJythonScriptCall {
    static void callJythonScript() throws PyException {
		
	PythonInterpreter jython = new PythonInterpreter()
		
	jython.exec("""
		    |num = 1
		    |if num >= 0:
		    |    if num == 0:
		    |        print("Zero")
		    |    else:
		    |        print("Positive number")
		    |else:
		    |    print("Negative number")
		    """.stripMargin())
	}
}

a = new AJythonCall().callJython()
b = new AJythonScriptCall().callJythonScript()

© 2001-2023 ou-ryperd.net