Apache GROOVY-1875

What is it? ๐Ÿค”

Known for

Disclaimer ๐Ÿ—’

Differences with Java

Verbosity

Java vs Groovy: You vs the guy she told you not to worry about

Dynamic Typing

            def a = 123
Integer b = 123
def c = "123" as Integer
        
def calc(def x, def y)
def calc(x, y) // def in params can be skipped
Float calc(Float x, Integer y)

Multimethods

            int method(String arg) { return 1; }
int method(Object arg) { return 2; }
Object o = "Object";
int result = method(o);
            assertEquals(2, result); // in Java
        
            assertEquals(1, result); // in Groovy โš ๏ธ
        

Omitting return & ;


            Integer fib(Integer n) {
                if (n < 2) return 1 ;
                return fib(n - 1) + fib(n - 2) ;
            }
        

Omitting return & ;


            Integer fib(Integer n) {
                if (n < 2) return 1
                fib(n - 1) + fib(n - 2)
            }
        

Omitting ()

            def sum(a, b) {
    a + b
}
            def a = sum(4, 5)
            def b = sum 4, 5
            println sum(4, 5)
        
            def noParam() { 123 }
            def c = noParam()
        

Groovy Truth ๐Ÿ‘ฉโ€โš–๏ธ

if (<expr>) { ... }
  • true, !false
  • non-empty lists: [1, 2, 3], ![]
  • non-empty maps: [abc: 123], ![:]
  • non-empty (G)strings: 'abc', !""
  • non-zero numbers: 1, -5.4, !0, !0.0f
  • non-null objects: new Object(), !null
def a = [1, 2, 3]
if (!a.empty) {
if (a) {
    print '๐Ÿ‘'
}

Closures

Closure is a piece of code that is defined and then executed at a later point. It can take arguments, return a value and be assigned to a variable. It may reference variables declared in its surrounding scope.

            def sum = { int a, int b -> a + b }
            
            assert sum(2, 3) == 5
        
            def greet = { name -> "Hello ${name}!" }
            def greet = { "Hello ${it}!" } // โš ๏ธ implicit `it`
            assert greet('John') == 'Hello John!'
        

Closures: Delegation ๐Ÿคฏ

            class One {
                def value() { "A" }
                def cl = { "value = ${value()}" } 
            } 
            println new One().cl() // value = A
        

Closures: Delegation Strategies ๐Ÿคฏ

Closures: Delegation Example

                def calendar = Calendar.instance
calendar .clear()
calendar .set calendar .YEAR, 2019
calendar .set calendar .MONTH, calendar .DECEMBER
calendar .set calendar .DAY_OF_MONTH, 2
println  calendar .time // Mon Dec 02 00:00:00 CET 2019
            

Closures: Delegation Example

                def closure = {
                        clear()
                        set YEAR, 2019
                        set MONTH, DECEMBER
                        set DAY_OF_MONTH, 2
                        println time
                    }
                    def calendar = Calendar.instance
                    closure.delegate = calendar
                    closure.resolveStrategy = Closure.DELEGATE_ONLY
                    closure() // Mon Dec 02 00:00:00 CET 2019

            

Closures: with

                def calendar = Calendar.instance
calendar.with {
    clear()
    set YEAR, 2019
    set MONTH, DECEMBER
    set DAY_OF_MONTH, 2
    println time // Mon Dec 02 00:00:00 CET 2019
}
            

Closures as Method Params

            def method(Closure cl) { cl 'world' }
            def closure = { println "Hello $it!" }
            method(closure)
        
            method({ println "Hello $it!" })
            method { println "Hello $it!" }
        
            method {
                 println "Hello $it!"
            }
        

Groovy Closures vs Java Lambdas

DSL ๐Ÿค˜

A domain-specific language (DSL) is a computer language specialized to a particular application domain.

DSL: Example

                    class PipelineBuilder {
    List<String> cmds = []

    def stage(String name, Closure cl) {
        cmds << "# stage ${name}"
        cl()
    }

    def echo(String command) {
        cmds << "echo '${command}'"
    }

    def sh(String command) {
        cmds << "/bin/sh -c '${command}'"
    }
}


                
                    def pipeline(Closure cl) {
    def builder = new PipelineBuilder()
    cl.delegate = builder
    cl()
    builder.cmds.each { println it }
}

pipeline {
    stage('preparation') {
        sh './clone.sh'
        sh './init.sh'
    }

    stage('build') {
        sh './build.sh --env=prod'
        echo 'DONE!'
    }
}


                

Basic Types

Lists: Adding

            List a = [1, 2, 3]
a << 4 // a == [1, 2, 3, 4]
a << [5, 6] // a == [1, 2, 3, 4, [5, 6]]
        
            def a = [1, 2, 3]
a += [4, 5] // a == [1, 2, 3, 4, 5]
        
            List<Integer> a = [1, 2, 3]
a.addAll([4, 5]) // โš ๏ธ returns true, a == [1, 2, 3, 4, 5]
        

Lists: Iterating

for โ†’ .each
[1, 2, 3].each {
    println "Item: $it"
}
['a', 'b', 'c'].eachWithIndex { it, i ->
    println "Index = $i, Item = $it"
}

Lists: Filtering and Transforming ๐ŸŽฉ

[1, 2, 3].collect { it * 2 } // [2, 4, 6]
[1, 2, 3].find { it > 1 } // 2
[1, 2, 3].findAll { it > 1 } // [2, 3]
        ['a', 'b', 'c'].collectEntries { [(it): it.toUpperCase()] }
// ['a': 'A', 'b': 'B', 'c': 'C']
        (0..10).findAll { it % 2 == 0 }.collect { it ** 2 }.sum()
// 220 = 0 + 4 + 16 + 36 + 64 + 100

Lists: Slicing ๐Ÿ”ช

def a = [1, 2, 3, 4, 5]
assert a[0] == 1
assert a[-2] == 4
assert a[2..4] == [3, 4, 5]
assert a[4..<2, 0, 0, 4] == [5, 4, 1, 1, 5]
assert 'hello'[0, 4..<0] == 'holle'
assert 'hello'.split('').first() == 'h' // โš ๏ธ fails if null/[]
assert 'hello'.split('').last() == 'o' // โš ๏ธ fails if null/[]
        

Maps: Adding/Merging

            Map a = [key1: 'val1']
assert a.key1 == 'val1'
assert a['key1'] == 'val1'
        
            a << [key2: 'val20', key3: 'val3']
// a == [key1: 'val1', key2: 'val20', key3: 'val3']
        
            a += [key3: 'val30']
// a == [key1: 'val1', key2: 'val20', key3: 'val30']
        

Maps: Iterating

for โ†’ .each
[a: 1, b: 2, c: 3].each {
    println "k = ${it.key}, v = ${it.value}"
}
[a: 1, b: 2, c: 3].each { k, v ->
    println "k = ${k}, v = ${v}"
}

Maps: Filtering and Transforming ๐ŸŽฉ

[a: 1, b: 2, c: 3].collect { it.value * 2 } // [2, 4, 6]
[a: 1, b: 2, c: 3].collect { k, v -> v * 2 } // [2, 4, 6]
[a: 1, b: 2, c: 3].find { it.value > 1 }.value // 2 โš ๏ธ
[a: 1, b: 2, c: 3].findAll { it.value > 1 } // [b: 2, c: 3]
        ['a': 1, 'b': 2, 'c': 3].collectEntries {
    k, v -> [(k): v ** 3]
}
// ['a': 1, 'b': 8, 'c': 27]

in operator

'a' in ['a', 'b', 'c'] // true
'b' in [a: 1, b: 2, c: 3] // true
'a' in null // false

Ranges, Times

                assert (2..5) == [2, 3, 4, 5]
assert (2..<5) == [2, 3, 4]
assert (-5..3).step(2) == [-5, -3, -1, 1, 3]
        
                assert ('d'..<'a') == ['d', 'c', 'b']
        
                3.times { println "i = ${it}" }
// i = 0
// i = 1
// i = 2
        

Power Assertions ๐Ÿ’ช


            def expected = [0, 2, 4, 6]
            assert (0..10).findAll { it % 2 == 0 } == expected
            
                               |                       |  |
                               [0, 2, 4, 6, 8, 10]     |  [0, 2, 4, 6]
                                                       false
            
        

Getters, Setters

Properties — fields without access modifier

                class Company {
    String name
}
        
                def name = company.getName()
company.setName("whatever")
        
                def name = company.name
company.name = "whatever"
        

Getters, Setters

                class Company {
    def getInformation() {
        // implementation
    }
}
        
                assert company.getInformation() == company.information
        

Getters, Setters

                class A {
                    boolean  is Something() {
                        // implementation
                    }
                }
        
                def a = new A()
assert a.isSomething() == a.something
        

Map Constructors ๐Ÿ—บ

                class Person {
                    String firstName
                    String lastName
                    String fullName() {
                        "${firstName} ${lastName}"
                    }
                }
        
                def p = new Person(firstName: 'John', lastName: 'Doe')
                assert p.fullName() == 'John Doe'
        

The call method ๐Ÿ“ž

                class A {
    def call(Integer x) { "got int" }
    def call(Float x) { "got float" }
    def call(Object x) { "got something else" }
    def call() { "got nothing" }
}
                
def a = new A()
println a() // got nothing
println a(1) // got int
println a(2.5f) // got float
println a('abc') // got something else
        
                def p = new Person(firstName: 'John', lastName: 'Doe')
        

Elvis Operator ?:

            def displayName
            if (user.name) {
                displayName = user.name
            } else {
                displayName = 'Anonymous'
            }
        
            def displayName = user.name ? user.name : 'Anonymous'
        
            def displayName = user.name ?: 'Anonymous'
        

Elvis Operator ?:

?:

Elvis Presley

Star-Dot Operator *.

                def a = new Company(name: 'ABC')
def b = new Company(name: 'BCD')
def c = new Company(name: 'DEF')
        
                def names = [a, b, c].collect { it.name }
// ['ABC', 'BCD', 'DEF']
        
                def names = [a, b, c]*.name
        
                assert [a, b, c]*.name*.toLowerCase() == ['abc', 'bcd', 'def']
        

Null-Safe Navigation ?.

                class Company {
                    Address address
                }
        
                if (company != null && company.address != null && 
                    company.address.street != null) {
                    println company.address.street.name
                }
        
                println company?.address?.street?.name
        

Crouching private, hidden public

Power Switch ๐Ÿ”Œ

                def testSwitch(val) {
    def result
    switch (val) {
        case 60..90:
            result = 'Range contains'; break
        case [21, 'test', 9.12]:
            result = 'List contains'; break
        case { it instanceof Integer && it < 50 }:
            result = 'Closure boolean'; break
    }
    result
}
assert testSwitch(21) == 'List contains'
            

RegExes

                def finder = ('groovy' ==~ /gr.*/)
assert finder == true
        
def group = ('groovy and grails, ruby and rails' =~ 
            /(\w+) and (\w+)/)
assert group instanceof java.util.regex.Matcher
assert 2 == group.size()
assert ['groovy and grails', 'groovy', 'grails'] == group[0]
assert ['ruby and rails', 'ruby', 'rails'] == group[1]

Example: JSON fetching & parsing

import groovy.json.JsonSlurper

String url = 'https://jsonplaceholder.typicode.com/todos/1'
String response = url.toURL().text
def contents = new JsonSlurper().parseText(response)
assert contents.title == 'delectus aut autem'

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                for (int j = 0; j < entries.length; j++) {
                    def entry = entries[j]
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    def files = new ArrayList(entry.affectedFiles)
                    for (int k = 0; k < files.size(); k++) {
                        def file = files[k]
                        body(jenkinsFileNames, entry, file)
                    }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                for (int j = 0; j < entries.length; j++) {
                    def entry = entries[j]
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    def files = new ArrayList(entry.affectedFiles)
                    for (int k = 0; k < files.size(); k++) {
                        def file = files[k]
                        body(jenkinsFileNames, entry, file)
                    }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                for (int j = 0; j < entries.length; j++) {
                    def entry = entries[j]
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    def files = new ArrayList(entry.affectedFiles)
                    for (int k = 0; k < files.size(); k++) {
                        body(jenkinsFileNames, entry, files[k])
                    }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                for (int j = 0; j < entries.length; j++) {
                    def entry = entries[j]
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    for (int k = 0; k < entry.affectedFiles.size(); k++) {
                        body(jenkinsFileNames, entry, entry.affectedFiles[k])
                    }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                for (int j = 0; j < entries.length; j++) {
                    def entry = entries[j]
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            for (int i = 0; i < changeLogSets.size(); i++) {
                def entries = changeLogSets[i].items
                entries.each { entry -> 
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            changeLogSets.each { changeLogSet -> 
                def entries = changeLogSet.items
                entries.each { entry -> 
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            changeLogSets.each { changeLogSet -> 
                changeLogSet.items.each { entry -> 
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Example: Refactoring

            def changeLogSets = currentBuild.changeSets ?: []
            changeLogSets*.items.each { entries -> 
                entries.each { entry -> 
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Example: Refactoring

            currentBuild.changeSets*.items.each { entries -> 
                entries.each { entry -> 
                    echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
                    entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
                }
            }
        

Traits

Traits are a structural construct of the language which allows:
- composition of behaviors
- runtime implementation of interfaces
- behavior overriding
- compatibility with static type checking/compilation

They can be seen as interfaces carrying both default implementations and state.

Traits: Example

            trait Greetable {
    abstract String name()                              
    String greeting() { "Hello, ${name()}!" }           
}

class Person implements Greetable {                     
    String name() { 'Bob' }                             
}

def p = new Person()
assert p.greeting() == 'Hello, Bob!'    
            
        

Traits: Composition

            trait FlyingAbility {                           
    String fly() { 'I am flying!' }          
}

trait SpeakingAbility {
    String speak() { 'I am speaking!' }
}
 
class Duck implements FlyingAbility, SpeakingAbility {}

def d = new Duck()                                      
assert d.fly() == 'I am flying!'
assert d.speak() == 'I am speaking!'
        

Traits: in Runtime ๐Ÿƒโ€โ™‚๏ธ

            trait Extra {
    String extra() { 'Extra method' }            
}
class Something {                                       
    String doSomething() { 'Something' }                
}
def s = new Something() as Extra                        
println s.extra()                                               
println s.doSomething()
        

Groovy in Jenkins

CodeNarc ๐Ÿ‘ฎโ€โ€

CodeNarc analyzes Groovy code for defects, bad practices, inconsistencies, style issues and more. A flexible framework for rules, rulesets and custom rules means it's easy to configure CodeNarc to fit into your project.

jenkins-job-dsl-seed-jobs, 11k LOC

Testing

Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language.
Test structure:
  • given: setup
  • when: method invocation
  • then: result check
  • expect: when + then
  • where: parametrization
  • Testing

                def "mply: tests multiply function"() {
                     when:
                     def result = mply(a, b)
                     
                     then:
                     assert result == expected
                 
                     where:
                     a  | b    | expected
                     3  | 4    | 12
                     0  | 1000 | 0
                     -1 | -1   | 1
                }
            

    Testing

                def "mply: tests multiply function"() {
                     expect:
                     mply(a, b) == expected
                     
                     where:
                     a  | b    | expected
                     3  | 4    | 12
                     0  | 1000 | 0
                     -1 | -1   | 1
                }
            

    StackOverflow.com? ๐Ÿ˜’

    assert ['Hubert', 'Alexander', 'Klein', 'Ikkink'].inject('mr') { nickname, name ->
        nickname + name[0].toLowerCase()
    } == 'mrhaki'

    Awesome Tools ๐Ÿ˜

    Recommended Links ๐Ÿ”—

    How to Write Good Groovy Code ๐Ÿค”

    Leo Tolstoy Ernest Hemingway

    ๐Ÿ‘Ž

    ๐Ÿ‘

    ยซHow can I make it shorter?ยป

    Thank you!

    Questions?