JVM Scripting with Groovy

Craig Burke

About Me

  • Groovy/Grails Developer at Carnegie Mellon

  • Creator of the Groovy Document Builder

  • www.craigburke.com

  • @craigburke1

  • @groovypgh

What is a DSL?

Definition and Examples

What is a DSL?

A Domain Specific Language (DSL) is a computer programming language of limited expressiveness focused on a particular domain.
— Martin Fowler

What is a DSL?

Makes use of the language and metaphors used by domain experts

DSL Example: SQL

INSERT INTO EMPLOYEE (ID, NAME) VALUES (1, 'Craig Burkee');

UPDATE EMPLOYEE SET NAME = 'Craig Burke' WHERE ID = 1;

SELECT ID, NAME FROM EMPLOYEE;

DELETE FROM EMPLOYEE WHERE ID = 1;

DSL Example: CSS

p {
  font-size: 12px;
  color: #B41F1F;
}

div.content {
  width: 100%;
  padding: 5px;
  border: 1px solid #000000;
}

DSL Example: Gradle

apply plugin: 'groovy'

repositories {
  jcenter()
  mavenCentral()
}

dependencies {
  compile group:'org.codehaus.groovy', name: 'groovy-all', version: '2.4.4'
  runtime group:'org.postgresql', name: 'postgresql' version: '9.4-1201-jdbc41'
}

More Examples

Groovy DSLs

Grails domain class

class User {

  String firstName
  String lastName
  Date birthDate

  static constraints = {
    firstName blank:false
    lastName blank:false
    birthDate nullable:false
  }

}

Bower Installer

bower {
    installBase = 'src/assets/bower'

    'angular'('1.4.x')

    'angular-animate'('1.4.x') {
        source 'angular-animate.js' >> '/angular/modules/'
    }

    'bootstrap'('3.3.x') {
        source 'dist/css/*.min.css' >> 'styles/'
        source 'dist/fonts/**' >> 'fonts/'
        excludes 'jquery'
    }
}

Groovy Document Builder

document {
  paragraph 'Hello Groovy Pittsburgh!', font: [size: 36.pt]

  paragraph(font: [size: 33.pt, color: '#6598A9']) {
    text 'I '
    text 'Love ', font: [bold: true, size: 40.pt, color: '#FF0000']
    text 'Groovy'
  }
}

Groovy Fundamentals

Groovy Features

  • Simple Syntax (Less Noise)

  • Operator Overloading

  • Scripting Support

  • Meta-Programming

Getting started

  • SDKMan!

  • Windows Installer

SDKMan!
curl -s http://get.sdkman.io | bash
sdk install groovy

Power Assert

The assert keyword take a boolean expression and throws an exception if it’s false.

assert true
assert false // throws exception

Groovy Truth

Any Object can be coerced into a boolean and used in a boolean expression.

Equality

Unlike Java the == in Groovy uses equals() to determine equality.

Syntactic Sugar

Maps and lists
Map map = [language: 'Groovy']
List list = ['foo', 'bar']
Optional parenthesis
// equivilent to: say(hello).to('Craig')

Syntactic Sugar

Command chaining and optional parenthesis
// equivilent to: say(hello).to('Craig')
say hello to 'Craig'
Named arguments
class Note {
  static from(Map args, String name) {
    println "${name} said ${args.message}"
  }
}

Note.from 'Craig Burke', message: 'Hey, how are you?'

Functional Programming

Closures and other language features

The Groovy Language

Groovy is an imperative language with the optional functional programming features.

Closures

A closure is a block of code wrapped up in an object.

Closures

It’s a first-class function that can be:

  • Passed as an argument

  • Assigned to a variable

  • Returned from a function

Collection Methods

Groovy method

Equivilant to

findAll

filter

collect

map

inject

fold

Immutability

Most Groovy collection methods include an optional mutate parameter and an asImmutable() method.

Closure Scope

this

instance of the class the closure was defined

owner

usually the same as this unless it was declared inside another closure

delegate

same as owner but can be reassigned

Closure Resolution Strategy

OWNER_FIRST

resolve to owner then delegate (default)

OWNER_ONLY

resolve to owner only

DELEGATE_FIRST

resolve to delegate then owner

DELEGATE_ONLY

resolve to delegate only

Dynamic Programming

The MetaClass

  • Every class in Groovy has a corresponding metaClass

  • Class instances can also have custom metaClasses

Operator Overloading

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.multiply(b)

a ** b

a.power(b)

a / b

a.div(b)

a % b

a.mod(b)

a | b

a.or(b)

a & b

a.and(b)

a ^ b

a.xor(b)

a or a

a.next()

a-- or --a

a.previous()

a << b

a.leftShift(b)

a >> b

a.rightShift(b)

a <⇒ b

a.compareTo(b)

Getters and Setters

class Person {
  String name
  String nickName
}
Person person = new Person()
Equivilent to person.setName('Craig')
person.name = 'Craig'
Equivilent to println person.getName()
println person.name
Equivilent to person.setNickName(person.getName())
person.nickName = person.name

Dynamic Properties

Extending the Number class
Number.metaClass.getDollars = { delegate as BigDecimal }

Number.metaClass.getProperty = { String name ->
  def rates = [euros: 1.1f, pesos: 0.063f]
  delegate * (rates[name] as BigDecimal)
}
Adding different currencies together
def total = 20.dollars + 40.euros + 200.pesos
assert total == 76.60

Dynamic Methods

class Person {
  def methodMissing(String name, args) {
    if (name.startsWith('say')) {
      String message = (name - 'say').trim()
      println message
    }
  }
}
Trying out our dynamic method
Person you = new Person()
you.sayHello()
you."say Craig is Awesome"()

Let’s Get Serious

(Not Really)

dane cook
Figure 1. Dane Cook: the Dane Cook of Comedy

Who is Dane Cook?

  • Comedy Albums Have Sold Millions retaliation

  • Voice of Dusty Crophopper from Planes dusty-planes

  • Estimated Net Worth of $30 million

  • Rides Around in a Sweet Lamborghini

Twitter Smackdown

twitter

A Proposed DSL

'Dane Cook sucks'.watch { tweetedStatus ->
    tweet 'Hey @DaneCook, somebody said this:' << tweetedStatus
    tweet "LEAVE DANE ALONE, @${tweetedStatus.user.screenName}!!!"
}

Case Study: Groovy Document Builder

Generating Documents In Java

car

Requirements

  • Simple and Readable DSL for Document Creation

  • Use Same Code for Word or Pdf Documents

  • Shouldn’t Require Knowledge of a Complex Library

Proof Of Concept

groovy doc twitter

Early DSL

builder.generate {
  paragraph 'OMFG! Look at the cat!'
  picture kitty, [name: 'kitty.png', width: 354, height: 290]
  paragraph 'That cat is amazing!!!'
}

Simple Implementation

class DocumentBuilder {
  File file

  Document(File file) {
    this.file = file
  }

  void generate(Closure builder) {
    builder.delegate = this
    builder()
  }

  abstract void paragraph(String text)
  abstract void picture(Map params = [:], byte[] data)
}

That Works for a Simple Proof of Concept…​

But Things Get Complicated

builder.generate {
  paragraph 'Check out this table'

  table {
    row {
      cell 'First Name'
      cell 'Last Name'
    }

    row {
      cell 'Craig'
      cell 'Burke'
    }
  }
}

I Need To Make Builder

Builder Support

  • Closure Delegation

  • BuilderSupport

  • FactoryBuilderSupport

BuilderSupport

class MyBuilder extends BuilderSupport {
  def createNode(name) { /* TODO */ }
  def createNode(name, value) { /* TODO */ }
  def createNode(name, Map attributes) { /* TODO */ }
  def createNode(name, Map attributes, value) { /* TODO */ }
  void setParent(parent, child) { /* TODO */ }
  void nodeCompleted(parent, node) { /* TODO */ }
}

FactoryBuilderSupport

Now We’re Talking

Grails domain class

class User {

  String firstName
  String lastName
  Date birthDate

  static constraints = {
    firstName blank:false
    lastName blank:false
    birthDate nullable:false
  }

}

Let’s Trash This Code

Original

class User {
  String firstName
  String lastName
  Date birthDate

  static constraints = {
    firstName blank:false
    lastName blank:false
    birthDate nullable:false
  }

}

Explicit Types

class User {
  String firstName
  String lastName
  Date birthDate

  static Closure constraints = {
    firstName([blank:false])
    lastName([blank:false])
    birthDate([nullable:false])
  }

}

Why Stop There?

class User {
  String firstName
  String lastName
  Date birthDate

  static Closure constraints = {
    Map nameConstrants = [blank:false]

    ['firstName', 'lastName'].each {
      "${it}"(nameContraints)
    }

    delegate.birthDate([nullable:false])
  }

}

Resources

Books

Presentations

Links

Thank You