Groovy

From NovaOrdis Knowledge Base
Jump to: navigation, search

External

TODO

  • Tutorial: www.tutorialspoint.com/groovy/index.htm

TODO Groovy basics: https://docs.gradle.org/current/userguide/writing_build_scripts.html#groovy-dsl-basics

Overview

Introducing Groovy to a Java project is a simple as adding the Groovy JAR file to its classpath.

To experiment with syntax, use the IntelliJ Groovy Console:

IntelliJ Groovy Console

Data Types

String

Single-Quoted vs. Double-Quoted Strings

Groovy has both double-quoted and single-quoted String literals. The main difference is that double-quoted String literals support String interpolation:

def x = 10
println "result is $x" // prints: result is 10

Rules for String Interpolation

Groovy supports declaring a string with either single quotes or double quotes.

If the string is single-quoted, the variable interpolation behavior is turned off, similarly to how bash handles single-quoted strings.

def user="Elemental"
echo 'Hello ${user}'

will output:

Hello ${user}

Double-quoted string support dollar-sign based interpolation:

def user="Elemental"
echo "Hello ${user}"

will output:

Hello Elemental

Note that the variable passed to ${} can be a function that returns a String:

static indent(String lead, String object) {
    return lead + object
}
def a = "something"
print "${indent("----", a)}"

prints:

----something

Multi-Line Strings

Multi-line strings can be represented in Groovy by enclosing them in """ or ''':

print """
  this is 
  a multi-line
  string
""".stripIndent()
print '''
  this is 
  a multi-line
  string
'''.stripIndent()

The difference between """ or ''' is in how variable interpolation is handled when variables are declared inside the quoted string. The rules describe here apply: Rules for String Interpolation. As such, the first example below will interpolate the variable, while the second will not:

def v="multi-line"
print """
  this is 
  a ${v}
  string
""".stripIndent()

will display:

this is 
a multi-line
string

while this

def v="multi-line"
print '''
  this is 
  a ${v}
  string
'''.stripIndent()

will display:

this is
a ${v}
string

String Comparison

Operators == and != work with strings. Explain why.

Array

String[] arr = ["a", "b", "c"]
def arr = ["a", "b", "c"].toArray()


Boolean

A boolean variable evaluates to true if assigned a different type variable that is not null or not empty, and false otherwise.

The following expressions assign "true" to b:

boolean b
b = "something"

HashSet s = new HashSet()
s.add("something")
b = s

The following expressions assign "false" to b:

boolean b
b = ""
b = new ArrayList()
b = new HashSet()

List

def list = ["A", "B", "C"]

Internally is stored as an java.util.ArrayList.

To access an element:

list.get(0)
list[0]

Map

Empty map:

Map testingBranches = [:]

Initialization:

def something = [a: 10, b:20, c:30]
def artifacts = [

        "chart-A": [
                "watchFor": ["src/charts/chart-A"],
                "chartName": "a",
        ],
        "chart-B": [
                "watchFor": ["src/charts/chart-B"],
                "chartName": "b",
        ],
        "script": [
                "watchFor": ["src/bin/run", "src/ansible", "script/bin/lib/a.shlib"],
        ]
]

Element insertion:

something.put('a', 10)
something['a'] = 10
something.a = 10
something.'a' = 10

Element access:

something.get('a')
something['a']
something.a
something.'a'

⚠️ Note that when m.n syntax is used, "n" is interpreted as a literal, not a variable, so even if the variable is defined and initialized, it will not be used.

something.put('a', 1)
String b = 'a'
something.b // ⚠️ will attempt to retrieve something.'b', not something.'a'



Adding elements from another map:

something << ['d':40]

Variables

Variables can be defined using either their type (String) or with the keywords def, which is equivalent with var

String a
def b
var c

def/var act as a replacement for the type name when the actual type is not provided (either because you don't care about it or you're relying on type inference). You can think about them as an alias to Object. def is used when we don't want to restrict the type of an object, and we want to change what can be assigned to a variable any time during runtime.

For scripts, undeclared variables are assumed to come from the Script binding.

Working with Closures

http://groovy-lang.org/closures.html

Defining a Closure

def myClosure = { e -> println "Clicked on $e.source" }

Implicit Paramenter

When a closure does not explicitly define a parameter using the '->' syntax, the closure 'always defines an implicit parameter named "it".

Passing Closures to Methods

If the closure is the last argument for a method, it can be passed outside the argument list.

https://mrhaki.blogspot.com/2009/11/groovy-goodness-passing-closures-to.html

Passing a Method as Closure

def withTestSetup(Closure test) {
  performTestSetup()
  test()
}

Invocation - the test setup is prior performed on the stack and then doTestLogic1() and doTestLogic2() methods are invoked, wrapped in a closure:

withTestSetup({
  doTestLogic1()
  doTestLogic2()
})

Template Engines

Groovy Template Engines

Files

filename = 'example.txt'
File f = new File(filename)
def lines = f.readLines()
for (line in lines) {
    // ...
}
filename = 'example.txt'
File f = new File(filename)
f.eachLine({
 if (!it.startsWith("#") && !it.trim().isEmpty()) {
   ...
  }
})

Dynamic Keywords

@Field

https://docs.groovy-lang.org/latest/html/gapi/groovy/transform/Field.html

Variable annotation used for changing the scope of a variable within a script from being within the run method of the script to being at the class level for the script. The annotated variable will become a private field of the script class.

In Jenkins, if Jenkinsfile variables are not declared @Field, they are not available in methods defined in Jenkinsfile.

Script

Groovy is automatically wrapping every program in a class called Script.

Methods

Methods may be invoked without parentheses-enclosed arguments. The following are equivalent:

log('something')
log 'something'

If the method has multiple arguments, the parentheses can be omitted but the arguments must be separated by comma:

log 'something', 'something else'

Method Parameters with Default Values

Default values can be assigned to a parameter in a method:

def say(msg = 'Hello', name = 'world') {
  "$msg $name!"
}

⚠️ Avoid using default method parameters with constructors, some compilers (for example, the Jenkins runtime Groovy compiler) will complain:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
file:/var/lib/jenkins/groovyStash/jenkins-jenkinsfile-refactoring-86/playground/ClusterTestingInfo.groovy: -1: cannot reference this inside of this ((java.lang.Object) pipeline, (java.lang.String -> java.lang.String) serverRoot, (java.lang.String -> java.lang.String[]) testRoots, (java.lang.String -> java.lang.String[]) org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(this, false, false, CLUSTER_TEST_ROOTS))(....) before supertype constructor has been called
. At [-1:-1]  @ line -1, column -1.

Control Flow

if/else

A null condition evaluates to false:

def a = null
if (a) {
  println "blue"
}
else {
  println "red"
}

will display "red".

Gradle Support

Gradle Groovy Plugin

Testing

https://groovy-lang.org/testing.html