Groovy Safari

Dan Hyun

Who?

Dan Hyun

Software Engineer Mindspark

What?

Introduction to Groovy

Tools and tips for working with Groovy

Groovy

Amazing general purpose programming language

Productivity

Readability

Happiness

JVM

  • Know Java? You already know Groovy

  • Compiles to JVM bytecode

  • Fully interoperable with Java

Community

  • Amazing OSS Projects

  • Active twitter participation

  • Great tools and libraries

Getting Started

Install Groovy

Download binaries

Or use GVM (Recommended )

One simple command
$ curl -s get.gvmtool.net | bash

Java is Groovy

HelloWorld.java
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

Hello World, Groovified

HelloWorld.groovy
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

Ok…​

more

Groovy++

HelloWorld.groovy
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!") (1)
  }
}
1Drop the semicolon!

Groovy++

HelloWorld.groovy
class HelloWorld { (1)
  static void main(String[] args) { (1)
    System.out.println("Hello, World!")
  }
}
1Drop public, everything is public in Groovy

Groovy++

HelloWorld.groovy
class HelloWorld {
  static void main(String[] args) {
    println("Hello, World!") (1)
  }
}
1Drop System.out

Groovy++

HelloWorld.groovy
class HelloWorld {
  static void main(String[] args) {
    println "Hello, World!" (1)
  }
}
1Drop parentheses

Groovy++

HelloWorld.groovy
println "Hello, World!" (1)
1Don’t need a main class wrapper to run code

Running Groovy

$ groovy HelloWorld.groovy
Hello, World!

Groovy Eval

$ groovy -e ' println "Hello, World!" ' #1
Hello, World!
1Skip the file altogether

Groovy Console

$ groovyConsole

Invokes

groovyConsole

groovyc

Groovy is always compiled before execution

$ groovyc HelloWorld.groovy && ls -l HelloWorld*
-rw-r--r--    1 danny    Administ     5152 May 27 22:53 HelloWorld.class
-rw-r--r--    1 danny    Administ       87 May 27 22:46 HelloWorld.groovy

Running the generated class File

$ java -cp ".:$GROOVY_HOME/embeddable/groovy-all-2.4.3.jar" HelloWorld
Hello, World!

Groovy FU

kyle

Strings

Express yourself

println 'I\'m a string'
println "Me too"
println(/So am I!/)

I'm a string
Me too
So am I!

GString

println "GStrings are created with a pair of \""
String value = "interpolation"
println "They allow for easy $value"

GStrings are created with a pair of "
They allow for easy interpolation

String manipulation

println 'You ' + "can " + /concatenate / + 'Strings'
println ("You can't remove portions of strings" - "'t")

You can concatenate Strings
You can remove portions of strings

Operators

Everything is a method call

Operators invoke method calls

Example with String comparison

Plus example
assert 1 + 1 == 2
assert 1.plus(1) == 2

Equality

Equality example
assert 'a' + 'b' == "ab" (1)
assert ('a'.plus('b')).equals("ab")
1== invokes .equals()

Implement your own

GroovyBeer.groovy
class GroovyBeer {
  String brewer
  String name

  GroovyBeer plus(GroovyBeer gb) {
    [brewer: brewer + gb.brewer, name: name + gb.name] (1)
  }
}
1Implicit return
GroovyBeer beer1 = [brewer: 'GR8', name: 'Groovy ']
GroovyBeer beer2 = [brewer: 'Conf', name: 'IPA']

GroovyBeer groovyiestBeer = beer1 + beer2

assert groovyiestBeer.brewer == 'GR8Conf'
assert groovyiestBeer.name == 'Groovy IPA'

Lists

Lists in Java
public class Example {
  public static void main(String[] args) {
    List<GroovyBeer> beers = new ArrayList<>();

    GroovyBeer b1 = new GroovyBeer();
    b1.setBrewer("GR8Conf");
    b1.setName("Groovy Porter");
    beers.add(b1);

    GroovyBeer b2 = new GroovyBeer();
    b2.setBrewer("GR8Conf");
    b2.setName("Groovy Gose");
    beers.add(b2);

    for(GroovyBeer b: beers) {
      System.out.println("I am enjoying a " + b.getName() + " by " + b.getBrewer());
    }

//    Java 8 Flavor
//    beers.stream()
//      .forEach(b ->
//        System.out.println("I am enjoying a " + b.getName() + " by " + b.getBrewer()));
  }
}

Lists

List<GroovyBeer> beers = [] (1)
beers << new GroovyBeer(brewer: 'GR8Conf', name: 'Groovy Porter') (2)
beers << new GroovyBeer(brewer: 'GR8Conf', name: 'Groovy Gose') (2)
beers.each { b -> println "I am enjoying a $b.name by $b.brewer" } (3)
1List literal syntax! ArrayList by default
2Invoke .leftShift() to add to beer list
3Apply a closure to each beer in beer list

Lists

List concat and equality
List<String> l1 = ['a', 'b'] (1)
List<String> l2 = ['c', 'd'] (1)

List l3 = l1 + l2 (2)
assert l3 == ['a', 'b', 'c', 'd'] (3)
1List creation
2List Concatenation
3List equality based on elements and order of elements

Maps

public class Example {
  public static void main(String[] args) {
    Map<String, GroovyBeer> beerByType = new HashMap<>();
    GroovyBeer pils = new GroovyBeer();
    pils.setName("Groovy Pils");
    beerByType.put("light", pils);

    GroovyBeer sour = new GroovyBeer();
    sour.setName("Groovy Flemish Ale");
    beerByType.put("sour", sour);

    beerByType.entrySet()
      .stream().forEach(e ->
        System.out.println("Enjoy " + e.getValue().getName() + ", a " + e.getKey() + " beer."));
  }
}

Maps

Map<String, GroovyBeer> beerByType = [:] (1)
beerByType.pils = new GroovyBeer(name: 'Groovy Pils') (2)
beerByType.sour = new GroovyBeer(name: 'Groovy Flemish Ale') (2)

beerByType.each { e -> println "Enjoy $e.value.name, a $e.key beer"} (3)
1Map literal syntax, LinkedHashMap by default
2Populate map as if assigning properties
3Apply closure to entry set

Maps

Equality

assert [a: 'foo', b: 'bar'] == [b: 'bar', a: 'foo'] (1)
1Equality is based on entries, order doesn’t matter

Objects

public class Beer {
  private String brewer;
  private String name;

  public String getBrewer() { return brewer; }
  public void setBrewer(String brewer) { this.brewer = brewer; }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
}

Java Object Use From Groovy

Beer beer = new Beer()
beer.setBrewer("GR8Conf")
beer.setName("Groovy Stout")

assert beer.getBrewer() == "GR8Conf"
assert beer.getName() == "Groovy Stout"

Groovier usage

Beer beer = new Beer()
beer.brewer = "GR8Conf" (1)
beer.name = "Groovy Stout" (1)

assert beer.brewer == "GR8Conf" (2)
assert beer.name == "Groovy Stout" (2)
1Set as if property
2Get as if property

Even Groovier

Beer beer = new Beer(brewer: "GR8Conf", name: "Groovy Stout") (1)

assert beer.brewer == "GR8Conf"
assert beer.name == "Groovy Stout"
1Pass map literal to constructor

Even Groovier

Beer beer = [brewer: "GR8Conf", name: "Groovy Stout"] (1)

assert beer.brewer == "GR8Conf"
assert beer.name == "Groovy Stout"
1Use a map as a constructor

Groovier Beer

Drop public access modifier and semicolons

class GroovyBeer {
  private String brewer
  private String name

  String getBrewer() { return brewer }
  void setBrewer(String brewer) { this.brewer = brewer }

  String getName() { return name }
  void setName(String name) { this.name = name }
}

Groovier Beer

Properties are automatically private

Setters/getters are generated

class GroovyBeer {
  String brewer
  String name
}

Use Groovy Objects from Java

public class Example {
  public static void main(String[] args) {
    GroovyBeer groovyBeer = new GroovyBeer();
    groovyBeer.setBrewer("GR8Conf");
    groovyBeer.setName("Groovy Ale");
    assert groovyBeer.getBrewer().equals("GR8Conf");
    assert groovyBeer.getName().equals("Groovy Ale");
  }
}

Dealing with null

String getSomeDeeplyNestedItem(def someItem) {
  someItem?.foo?.bar?.doesThisExist?.whoCares?.areWeSafe?.desiredItem (1)
}

assert null  == getSomeDeeplyNestedItem(null)
assert null  == getSomeDeeplyNestedItem([someItem: null])
assert 'yay!' == getSomeDeeplyNestedItem(
   [foo:
      [bar:
         [doesThisExist:
            [whoCares:
               [areWeSafe:
                  [desiredItem: 'yay!']]]]]])

Dealing with null

String getValueOrDefault(String s) {
  s ?: 'Sorry' (1)
}

assert 'Sorry' == getValueOrDefault(null)
assert 'Sorry' == getValueOrDefault('')
assert ' ' == getValueOrDefault(' ')
assert 'Hurray' == getValueOrDefault('Hurray')
1Elvis (else if) operator, if s is Groovy False return right hand operand

Closures

Object that captures initialized state and succinctly defines behavior

String value = 'world!'
Closure c = { "$it, $value" } (1)

assert 'Hello, world!' == c('Hello') (2)
assert 'Hello, world!' == c.call('Hello') (3)
1Closure literal syntax, it is implicit argument
2Invoke closure directory
3Invoke closure via .call()

Currying

Closure adder = { a, b -> a + b }
assert 3 == adder(1, 2)

Closure plusOne = adder.curry(1)
assert 3 == plusOne(2)

Functional Methods

Groovy enables functional programming

def list = [1,2,3,4,5]

assert 15 == list.sum() (1)
assert [1,2,3] == list.findAll { it < 4 } (2)
assert [0,1,2,3,4] == list.collect { it - 1 } (3)
assert '1, 2, 3, 4, 5' == list.join(', ') (4)
assert 120 == list.inject(1) { sum, i -> sum * i } (5)
1Sum all elements, accepts closure as variant to return derived value for summation
2Inclusive filter for all elements that satisfy predicate
3Akin to .map()
4Concatenates to String
5Reduce elements to multiplicative product

Groovy Koans

Download

$ git clone git@github.com:nadavc/groovykoans.git

Get started

View Tasks (Gradle)
$ cd groovykoans
$ ./gradlew tasks
... lots of output ...

Other tasks
-----------
removeSolutions - Removes the solutions from all Koans, so you can start fresh!
wrapper

Rules
-----
... more output ...
Pattern: koan<Number>: Runs a single Koan

Prep work

Remove Solutions
$ ./gradlew removeSolutions
Removed 73 solutions from Koans. Good luck!

Work flow

$ ./gradlew clean check -i
Groovy Koans 0.5:
The truth is out there. Anybody got the URL?
--------------------------------------------
... lots of output ...

52 tests completed, 50 failed, 2 skipped

FAILURE: Build failed with an exception.

Optional

Set up for use with IDE

IntelliJ
$ ./gradlew idea
Eclipse
$ ./gradlew eclipse

Your first koan

koan fail 1 1

Fix test

Recheck

Use dynamic Gradle task

Previous ./gradlew tasks output
Rules
-----
Pattern: koan<Number>: Runs a single Koan

Run a specific Koan

$ ./gradlew koan01
Groovy Koans 0.5:
Latest survey shows that 3 out of 4 people make up 75% of the world's population.
---------------------------------------------------------------------------------

Running exercises in test03_MapsInGroovy()..................FAILURE
Running exercises in test05_ElvisAndSafeNavigation()........FAILURE
Running exercises in test02_GStrings()......................FAILURE
Running exercises in test04_Lists().........................FAILURE
Running exercises in test01_AssertionsAndSomeSyntax().......SUCCESS

5 tests completed, 4 failed

Enlightenment

enlightened

Daily Groovy Usage

File I/O In Java 8

public class Example {
  public static void main(String[] args) throws IOException {
    List<String> buildFile = Files
      .lines(Paths.get("build.gradle"))
      .map(s -> "GR8Conf " + s)
      .collect(Collectors.toList()); (1)

    File tmp = File.createTempFile("blah", ".tmp");
    tmp.deleteOnExit();

    Files.write(tmp.toPath(), buildFile); (2)

    assert buildFile.size() == Files.readAllLines(tmp.toPath()).size();

    assert Files.lines(tmp.toPath())
        .allMatch(s -> s.startsWith("GR8Conf ")); (3)
  }
}
1Prepend `GR8Conf ` to all lines in file
2Write new lines to a temp File
3All lines written are prefixed with `GR8Conf `

File IO in Groovy

def newLines = new File('D:/projects/gr8conf-2015-groovy-safari/build.gradle')
                .readLines()
                .collect { "GR8Conf $it" } (1)

def tmpFile = File.createTempFile('blah', '.tmp')
tmpFile.deleteOnExit()

tmpFile.withPrintWriter { w ->
  newLines.each { l -> w.println l } (2)
}

assert newLines.size() == tmpFile.readLines().size()
assert tmpFile.eachLine { it.startsWith 'GR8Conf ' } (3)
1Prepend `GR8Conf ` to all lines in file
2Write new lines to a temp File
3All lines written are prefixed with `GR8Conf `

Web stuff

Screen Scraping
String gr8ConfHTML = 'http://gr8conf.eu'.toURL().text
assert gr8ConfHTML.length()
assert gr8ConfHTML
  .startsWith('<!doctype html> <html class="no-js" ng-app="gr8conf2015">')

API calls

Download and parse JSON
import groovy.json.JsonSlurper

def response = 'https://api.github.com/orgs/groovy/repos'.toURL().text
def repos = new JsonSlurper().parseText response (1)

assert repos.size()
assert repos.name (2)
  .sort {a, b -> a.toLowerCase() <=> b.toLowerCase() } ==
  ['artwork', 'gmaven', 'GMavenPlus',
   'groovy-android-gradle-plugin', 'groovy-core', 'groovy-eclipse',
   'groovy-website', 'groovy-windows-installer']
1Parse response text as JSON
2Use dot notation to navigate object graph

Groovy Sql

import groovy.sql.Sql

Sql sql = Sql.newInstance(
  url: 'jdbc:h2:mem:db',
  driver: 'org.h2.Driver',
  user: 'sa', password: '')

sql.execute('create table beer (name char(255))')

def beers = ['Lager', 'Pale Ale', 'Saison', 'Black IPA']
beers.each { b ->
  sql.execute('INSERT INTO `beer` (name) VALUES (?)', b) (1)
}

def rows = sql.rows('select * from beer') (2)
assert rows.size() == beers.size()
assert rows.name == beers (3)
1Insert each beer name into the db
2Simple select, returns List<GroovyRowResult>
3Use simple dot notation to access name key in each result

More to explore

Build

Testing

Web Frameworks

Resources

Official Groovy Docs

Books

Twitter

Questions?