Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King

Preview:

DESCRIPTION

 

Citation preview

IndustrialStrength GroovyTools for the Professional Groovy DeveloperDr Paul King, ASERT: @paulk_asert

Topics

• Testing/Mocking: JUnit, TestNG, EasyB, Spock, Instinct, MockFor, Gmock, EasyMock

• Injection: Spring, Guice

• Coverage: Cobertura

• Code style: CodeNarc, IntelliJ

• Duplication: Simian

• Documentation: GroovyDoc

• Builds: Ant, Gant, GMaven, Gradle, Hudson

• Modularisation: Grapes, OSGi

INTRO

SpringOne2gx_Oct2009 - 4

©

ASERT

2006-2009

What is Groovy?

• ―Groovy is like a super versionof Java. It can leverage Java'senterprise capabilities but alsohas cool productivity features like closures, DSL support, builders and dynamic typing.‖

SpringOne2gx_Oct2009 - 5

©

ASERT

2006-2009

Groovy Goodies Overview

• Fully object oriented

• Closures: reusable and assignable pieces of code

• Operators can be overloaded

• Multimethods

• Literal declaration for lists (arrays), maps, ranges and regular expressions

• GPath: efficient object navigation

• GroovyBeans

• grep and switch

• Templates, builder, swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks

Growing Acceptance …

Making Java Groovy(soon)

Now free

… Growing Acceptance …©

ASERT

2006-2009

SpringOne2gx_Oct2009 - 7http://www.java.net

http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes

… Growing Acceptance …©

ASERT

2006-2009

SpringOne2gx_Oct2009 - 8http://www.leonardoborges.com/writings

What alternative JVM language are you using or intending to use

… Growing Acceptance …©

ASERT

2006-2009

SpringOne2gx_Oct2009 - 9

http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com)

… Growing Acceptance

SpringOne2gx_Oct2009 - 10

©

ASERT

2006-2009

Groovy‘s Appeal

• Innovators/Thought leaders

– Ideas, power, flexibility, novelty, thinking community

• Early adopters

– Productivity benefits and collegiate community

– Leverage JVM and potential for mainstream

• Mainstream

– Leverage existing Java skills, low learning curve

– Leverage JVM and production infrastructure

– Professional community

– Tools, tools, tools

SpringOne 2GX 2009. All rights reserved. Do not distribute without permission.

TESTING

Database Drivers

DbUnitDataSetsSqlUnitgroovy.sql JPAJDOBigTableJDBC

SOAP / REST Drivers

GroovyWSXML-RPC CXFAxis2JAX-WS JAX-RS

UtilitiesAllPairs, CombinationsPolyglot languagesLogic programmingThreads, Parallel /Concurrency librariesData-driven librariesNetworking librariesXML ProcessingRead/write files /Excel / Word / CSVReporting, Logging

Other Drivers

FESTEmailFTPAntUnitTelnetSSHExec

ToolsiTest2, SoapUI, Twist, IDEs, JMeter, Text editors, Recorders, Sahi, Build Tools, CI

WebDrivers

WebTestWebDriverJWebUnitTellurium Selenium HtmlUnitWatijHttpBuilderCyberneko

Runners

Native Groovy, JUnit, TestNG, Spock, EasyB,JBehave, Cucumber, Robot Framework

Gro

ovy a

nd T

esting T

ool Spectr

um

Testing: JUnit

import org.junit.Test

import static org.junit.Assert.assertEquals

class ArithmeticTest {

@Test

void additionIsWorking() {

assertEquals 4, 2+2

}

@Test(expected=ArithmeticException)

void divideByZero() {

println 1/0

}

}

Testing: TestNG

import ...

public class SimpleTest {

@BeforeClass

public void setUp() {

// code invoked when test is created

}

@Test(groups = [ "fast" ])

public void aFastTest() {

System.out.println("Fast test");

}

@Test(groups = [ "slow" ])

public void aSlowTest() {

System.out.println("Slow test");

}

}

Mockin

g:

EasyM

ock

import static org.easymock.EasyMock.*

mockControl = createStrictControl()mockReverser = mockControl.createMock(Reverser)storer = new JavaStorer(mockReverser)testStorage()

def testStorage() {expectReverse(123.456, -123.456)expectReverse('hello', 'olleh')mockControl.replay()checkReverse(123.456, -123.456)checkReverse('hello', 'olleh')mockControl.verify()

}

def expectReverse(input, output) {expect(mockReverser.reverse(input)).andReturn(output)

}

def checkReverse(value, reverseValue) {storer.put(value)assert value == storer.get()assert reverseValue == storer.getReverse()

}

class a_default_storer {def storer

@initially void create_new_storer() {storer = new Storer()

}

private check_persist_and_reverse(value, expectedReverse) {storer.put(value)def persisted = storer.get()assert persisted == valuedef reversed = storer.reverseassert reversed == expectedReverse

}

@spec def should_reverse_numbers() {check_persist_and_reverse 123.456, -123.456

}

@spec def should_reverse_strings() {check_persist_and_reverse 'hello', 'olleh'

}

@spec def should_reverse_lists() {check_persist_and_reverse([1, 3, 5], [5, 3, 1])

}}

check_specs_for a_default_storerTesting:

Instinct

Mockin

g:

MockFor

import groovy.mock.interceptor.MockFor

def mocker = new MockFor(Collaborator.class) // create the Mock support

mocker.demand.one(1..2) { 1 } // demand the 'one' method one

// or two times, returning 1

mocker.demand.two() { 2 } // demand the 'two' method

// exactly once, returning 2

mocker.use { // start using the Mock

def caller = new Caller() // caller will call Collaborator

assertEquals 1, caller.collaborateOne() // will call Collaborator.one

assertEquals 1, caller.collaborateOne() // will call Collaborator.one

assertEquals 2, caller.collaborateTwo() // will call Collaborator.two

} // implicit verify here

import groovy.mock.interceptor.MockFor

def mocker = new MockFor(Collaborator.class)

mocker.demand.one(1..2) { 1 }

mocker.demand.two() { 2 }

mocker.use {

def caller = new Caller()

assertEquals 1, caller.collaborateOne()

assertEquals 1, caller.collaborateOne()

assertEquals 2, caller.collaborateTwo()

}

Mocking: Gmock ...

• Method mocking: mockLoader.load("fruit").returns("apple")

• Exception mocking: mockLoader.load("unknown").raises(new RuntimeException())

• Stub mocking: mockLoader.load("fruit").returns("apple").stub()

• Static method mocking: mockMath.static.random().returns(0.5)

• Property mocking: mockLoader.name.returns("loader")

• Constructor mocking: def mockFile = mock(File, constructor('/a/path/file.txt'))

• Partial mocking: mock(controller).params.returns([id: 3])

• Times expectation: mockLoader.load("fruit").returns("apple").atLeastOnce()

• Custom matcher: mockLoader.load(match{ it.startsWith("fru") })

• Strict ordering: ordered { ... }

• Optional support for Hamcrest matcher: mockLoader.put("test", is(not(lessThan(5))))

• GMockController if you can't extend GMockTestCase in your test

Mocking: Gmock

import org.gmock.GMockTestCase

class LoaderTest extends GMockTestCase {

void testLoader(){

def mockLoader = mock()

mockLoader.load('key').returns('value')

play {

assertEquals "value", mockLoader.load('key')

}

}

}

Testing:

Spock .

..

... Testing: Spock

@Speck

@RunWith(Sputnik)

class PublisherSubscriberSpeck {

def "events are received by all subscribers"() {

def pub = new Publisher()

def sub1 = Mock(Subscriber)

def sub2 = Mock(Subscriber)

pub.subscribers << sub1 << sub2

when:

pub.send("event")

then:

1 * sub1.receive("event")

1 * sub2.receive("event")

}

}

Testing: EasyB ...

given "an invalid zip code", {

invalidzipcode = "221o1"

}

and "given the zipcodevalidator is initialized", {

zipvalidate = new ZipCodeValidator()

}

when "validate is invoked with the invalid zip code", {

value = zipvalidate.validate(invalidzipcode)

}

then "the validator instance should return false", {

value.shouldBe false

}

Testing:

EasyB .

..before "start selenium", {given "selenium is up and running", {// start selenium

}}

scenario "a valid person has been entered", {

when "filling out the person form with a first and last name", {selenium.open("http://acme.racing.net/greport/personracereport.html")selenium.type("fname", "Britney")selenium.type("lname", "Smith")

}

and "the submit link has been clicked", {selenium.click("submit")

}

then "the report should have a list of races for that person", {selenium.waitForPageToLoad("5000")values = ["Mclean 1/2 Marathon", "Reston 5K", "Herndon 10K", "Leesburg 10K"]for(i in 0..<values.size()){

selenium.getText("//table//tr[${(i+3)}]/td").shouldBeEqualTo values[i]}

}}

after "stop selenium" , {then "selenium should be shutdown", {// stop selenium

}}

Dependency Injection

• Hollywood Principle

–Don‘t call us, we‘ll call you

• “All problems in computer science can be solved by another level of indirection”

• "...except for the problem of too many layers of indirection“– For attributions, see

http://en.wikipedia.org/wiki/Inversion_of_control

Dependency Injection

• Pattern for loosely coupled & testable objects

class Client {Calculator calcdef executeCalc(a, b) {

calc.add(a, b)}

}

class Client {Calculator calc =

new CalculatorImpl()def executeCalc(a, b) {

calc.add(a, b)}

}

Service locator/factory

Tightly coupled?

Hard to test?

Easy to understand?

Refactoring/navigation?

Need to select setter, constructor, field style

Can add complexity

Manage configuration

Direct or framework

Consistency/lifecycle

Dependency Injection: Spring ...

• Several flavors

– let‘s look at Annotation and BeanBuilder flavors

import org.springframework.stereotype.Component

@Component class AdderImpl {

def add(x, y) { x + y }

}

import org.springframework.beans.factory.annotation.Autowired

import org.springframework.stereotype.Component

@Component class CalcImpl3 {

@Autowired private AdderImpl adder

def doAdd(x, y) { adder.add(x, y) }

}

... Dependency Injection: Spring

import org.springframework.context.support.GenericApplicationContext

import

org.springframework.context.annotation.ClassPathBeanDefinitionScanner

def ctx = new GenericApplicationContext()

new ClassPathBeanDefinitionScanner(ctx).scan('')

ctx.refresh()

def calc = ctx.getBean('calcImpl3')

println calc.doAdd(3, 4) // => 7

def bb = new grails.spring.BeanBuilder()

bb.beans {

adder(AdderImpl)

calcBean(CalcImpl2) { delegate.adder = adder }

}

def ctx = bb.createApplicationContext()

def calc = ctx.getBean('calcBean')

println calc.doAdd(3, 4) // => 7

Dependency I

nje

ction:

Guic

e import com.google.inject.*

@ImplementedBy(CalculatorImpl)interface Calculator {

def add(a, b)}

@Singletonclass CalculatorImpl implements Calculator {

private total = 0def add(a, b) { total++; a + b }def getTotalCalculations() { 'Total Calculations: ' + total }String toString() { 'Calc: ' + hashCode()}

}

class Client {@Inject Calculator calc// ...

}

def injector = Guice.createInjector()// ...

Dependency Injection: Metaprogramming Style

class Calculator {def total = 0def add(a, b) { total++; a + b }

}

def INSTANCE = new Calculator()Calculator.metaClass.constructor = { -> INSTANCE }

def c1 = new Calculator()def c2 = new Calculator()

assert c1.add(1, 2) == 3assert c2.add(3, 4) == 7

assert c1.is(c2)assert [c1, c2].total == [2, 2]

CODE QUALITY

Covera

ge:

Cobert

ura

...

Ant

...C

overa

ge:

Cobert

ura

...

Maven

grails install-plugin code-coverage --globalGrails

Command-line

cobertura-instrumentjava –cp ...cobertura-reportcobertura-checkcobertura-merge

...C

overa

ge:

Cobert

ura

Rem

em

ber

100%

covera

ge r

ule

...

// 100%

• Necessary but not sufficient condition

...R

em

em

ber

100%

covera

ge r

ule

...

// 100%

• Necessary but not sufficient condition

// Fail

...R

em

em

ber

100%

covera

ge r

ule

• Necessary but not sufficient condition

Code style: CodeNarc ...

About 50 rules (1 broken?)

... Code style: CodeNarc ...

... Code style: CodeNarc

Code style: IntelliJ

Duplication: Simian ...

Simian fully supports the following languages:

• Java

• C#

• C++

• C

• Objective-C

• JavaScript (ECMAScript)

• COBOL, ABAP

• Ruby

• Lisp

• SQL

• Visual Basic

• Groovy

with partial support for:

• JSP

• ASP

• HTML

• XML

... Duplication: Simian ...

... Duplication: Simian ...

... Duplication: Simian

Similarity Analyser 2.2.23 -http://www.redhillconsulting.com.au/products/simian/index.html

Copyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved.

Simian is not free unless used solely for non-commercial or evaluation purposes.

{failOnDuplication=true, ignoreCharacterCase=true, ignoreCurlyBraces=true, ignoreIdentifierCase=true, ignoreModifiers=true, ignoreStringCase=true, threshold=6}

Found 6 duplicate lines in the following files:

Between lines 201 and 207 in /Users/haruki_zaemon/Projects/redhill/simian/build/dist/src/java/awt/image/WritableRaster.java

...

Found 66375 duplicate lines in 5949 blocks in 1260 files

Processed a total of 390309 significant (1196065 raw) lines in 4242 files

Processing time: 9.490sec

Documentation: GroovyDoc ...

<taskdef name="groovydoc"classname="org.codehaus.groovy.ant.Groovydoc">

<classpath><path path="${mainClassesDirectory}" /><path refid="compilePath" />

</classpath></taskdef>

<groovydoc destdir="${docsDirectory}/gapi"sourcepath="${mainSourceDirectory}"packagenames="**.*" use="true" windowtitle="${title} "doctitle="${title}" header="${title}" footer="${docFooter}"overview="src/main/overview.html" private="false">

<link packages="java.,org.xml.,javax.,org.xml."href="http://java.sun.com/j2se/1.5.0/docs/api" />

<link packages="org.apache.ant.,org.apache.tools.ant."href="http://www.dpml.net/api/ant/1.7.0" />

<link packages="org.junit.,junit.framework."href="http://junit.sourceforge.net/junit3.8.1/javadoc/" />

</groovydoc>

... Documentation: GroovyDoc

BUILDS

Builds: Groovy from Ant

• Need groovy jar on your Ant classpath

<taskdef name="groovy"classname="org.codehaus.groovy.ant.Groovy"classpathref="my.classpath"/>

<target name="printXmlFileNamesFromJar"><zipfileset id="found" src="foobar.jar"

includes="**/*.xml"/><groovy>

project.references.found.each {println it.name

}</groovy>

</target>

Builds: Ant from Groovy

• Built-in (need ant.jar on your Groovy classpath)

new AntBuilder().with {

echo(file:'Temp.java', '''

class Temp {

public static void main(String[] args) {

System.out.println("Hello");

}

}

''')

javac(srcdir:'.', includes:'Temp.java', fork:'true')

java(classpath:'.', classname:'Temp', fork:'true')

echo('Done')

}

// =>

// [javac] Compiling 1 source file

// [java] Hello

// [echo] Done

Builds: Gant

• lightweight façade on Groovy's AntBuilder

• target def‘ns, pre-defined ‗ant‘, operations on predefined objects

includeTargets << gant.targets.CleancleanPattern << [ '**/*~' , '**/*.bak' ]cleanDirectory << 'build'

target ( stuff : 'A target to do some stuff.' ) {println ( 'Stuff' )depends ( clean )echo ( message : 'A default message from Ant.' )otherStuff ( )

}

target ( otherStuff : 'A target to do some other stuff' ) {println ( 'OtherStuff' )echo ( message : 'Another message from Ant.' )clean ( )

}

Builds: GMaven

• Implementing Maven plugins has never been Groovier!

• Groovy Mojos

– A Simple Groovy Mojo

• Building Plugins

– Project Definition

– Mojo Parameters

• Putting More Groove into your Mojo

– Using ant, Using fail()

• gmaven-archetype-mojo Archetype

• gmaven-plugin Packaging

Builds: GMaven

<plugin><groupId>org.codehaus.mojo.groovy</groupId><artifactId>groovy-maven-plugin</artifactId><executions>

<execution><id>restart-weblogic</id><phase>pre-integration-test</phase><goals>

<goal>execute</goal></goals><configuration>

<source>${pom.basedir}/src/main/script/restartWeblogic.groovy</source>

</configuration></execution>

...

Builds: GMaven

def domainDir =project.properties['weblogic.domain.easyimage.dir']

stopWebLogic()copyFiles(domainDir)startWebLogic(domainDir)waitForWebLogicStartup()

def stopWebLogic() {weblogicServerDir = project.properties['weblogic.server.dir']adminUrl = project.properties['easyimage.weblogic.admin.t3']userName = 'weblogic'password = 'weblogic'ant.exec(executable: 'cmd', failonerror: 'false') {

arg(line: "/C ${wlsDir}/bin/setWLSEnv.cmd && java ..." ...}

...

Builds: Gradle ...

• A very flexible general purpose build tool like Ant

• Switchable, build-by-convention frameworks a la Maven. But we never lock you in!

• Powerful support for multi-project builds

• Powerful dependency management (Apache Ivy based)

• Full support for your existing Maven or Ivy repository infrastructure

• Support for transitive dependency management without the need for remote repositories or pom.xml/ivy.xml files

• Ant tasks as first class citizens

• Groovy build scripts

• A rich domain model for describing your build

... Builds: Gradle

Builds: Hudson

• Gant Plugin — This plugin allows Hudson to invoke Gant build script as the main build step

• Gradle Plugin — This plugin allows Hudson to invoke Gradle build script as the main build step

• Grails Plugin — This plugin allows Hudson to invoke Grails tasks as build steps

• Hudson CLI and GroovyShell Usage pattern?

Source: http://weblogs.java.net/blog/kohsuke/archive/2009/05/hudson_cli_and.html

Ant

Build F

ile..

.<project name="StringUtilsBuild" default="package"

xmlns:ivy="antlib:org.apache.ivy.ant" xmlns="antlib:org.apache.tools.ant">

<target name="clean">

<delete dir="target"/>

<delete dir="lib"/>

</target>

<target name="compile" depends="-init-ivy">

<mkdir dir="target/classes"/>

<javac srcdir="src/main"

destdir="target/classes"/></target>

<target name="compileTest" depends="compile">

<mkdir dir="target/test-classes"/>

<javac srcdir="src/test"

destdir="target/test-classes">

<classpath>

<pathelement location="target/classes"/>

<fileset dir="lib" includes="*.jar"/>

</classpath>

</javac></target>

...

...A

nt

Build F

ile

...

<target name="test" depends="compileTest">

<mkdir dir="target/test-reports"/>

<junit printsummary="yes" fork="yes" haltonfailure="yes">

<classpath>

<pathelement location="target/classes"/>

<pathelement location="target/test-classes"/>

<fileset dir="lib" includes="*.jar"/>

</classpath>

<formatter type="plain"/>

<formatter type="xml"/>

<batchtest fork="yes" todir="target/test-reports">

<fileset dir="target/test-classes"/>

</batchtest>

</junit>

</target>

<target name="package" depends="test">

<jar destfile="target/stringutils-1.0-SNAPSHOT.jar"

basedir="target/classes"/>

</target>

<target name="-init-ivy" depends="-download-ivy">

<taskdef resource="org/apache/ivy/ant/antlib.xml"

uri="antlib:org.apache.ivy.ant" classpath="lib/ivy.jar"/>

<ivy:settings file="ivysettings.xml"/>

<ivy:retrieve/>

</target>

<target name="-download-ivy">

<property name="ivy.version" value="2.1.0-rc2"/>

<mkdir dir="lib"/>

<get

src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.version}/ivy-

${ivy.version}.jar"

dest="lib/ivy.jar" usetimestamp="true"/>

</target>

</project>

Maven P

OM

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>org.groovycookbook.builds</groupId>

<artifactId>stringutils</artifactId>

<packaging>jar</packaging>

<version>1.0-SNAPSHOT</version>

<name>stringutils</name>

<url>http://maven.apache.org</url>

<dependencies>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

<scope>test</scope>

</dependency>

</dependencies>

<build>

<plugins>

<!-- this is a java 1.5 project -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.5</source>

<target>1.5</target>

</configuration>

</plugin>

</plugins>

</build>

</project>

build.g

roovy .

..import static groovy.xml.NamespaceBuilder.newInstance as namespace

ant = new AntBuilder()

clean()

doPackage()

def doPackage() {

test()

ant.jar destfile: 'target/stringutils-1.0-SNAPSHOT.jar',

basedir: 'target/classes'

}

private dependencies() {

def ivy_version = '2.1.0-rc2'

def repo = 'http://repo2.maven.org/maven2'

ant.mkdir dir: 'lib'

ant.get dest: 'lib/ivy.jar',

usetimestamp: 'true',

src: "$repo/org/apache/ivy/ivy/$ivy_version/ivy-${ivy_version}.jar"

ant.taskdef classpath: 'lib/ivy.jar',

uri: 'antlib:org.apache.ivy.ant',

resource: 'org/apache/ivy/ant/antlib.xml'

def ivy = namespace(ant, 'antlib:org.apache.ivy.ant')

ivy.settings file: 'ivysettings.xml'

ivy.retrieve()

}

def clean() {

ant.delete dir: 'target'

ant.delete dir: 'lib'

}

...

...

build.g

roovy

...

def compile() {

dependencies()

ant.mkdir dir: 'target/classes'

ant.javac destdir: 'target/classes', srcdir: 'src/main',

includeantruntime: false

}

def compileTest() {

compile()

ant.mkdir dir: 'target/test-classes'

ant.javac(destdir: 'target/test-classes', srcdir: 'src/test',

includeantruntime: false) {

classpath {

pathelement location: 'target/classes'

fileset dir: 'lib', includes: '*.jar'

}

}

}

def test() {

compileTest()

ant.mkdir dir: 'target/test-reports'

ant.junit(printsummary: 'yes', haltonfailure: 'yes', fork: 'yes') {

classpath {

pathelement location: 'target/classes'

pathelement location: 'target/test-classes'

fileset dir: 'lib', includes: '*.jar'

}

formatter type: 'plain'

formatter type: 'xml'

batchtest(todir: 'target/test-reports', fork: 'yes') {

fileset dir: 'target/test-classes'

}

}

}

build.g

ant

...

import static groovy.xml.NamespaceBuilder.newInstance as namespace

target('package': '') {

depends 'test'

jar destfile: 'target/stringutils-1.0-SNAPSHOT.jar',

basedir: 'target/classes'

}

target('-download-ivy': '') {

def ivy_version = '2.1.0-rc2'

def repo = 'http://repo2.maven.org/maven2'

mkdir dir: 'lib'

get dest: 'lib/ivy.jar', usetimestamp: 'true',

src: "$repo/org/apache/ivy/ivy/$ivy_version/ivy-${ivy_version}.jar"

}

target(clean: '') {

delete dir: 'target'

delete dir: 'lib'

}

target(compile: '') {

depends '-init-ivy'

mkdir dir: 'target/classes'

javac destdir: 'target/classes', srcdir: 'src/main'

}

...

...

build.g

ant

...

target('-init-ivy': '') {

depends '-download-ivy'

taskdef classpath: 'lib/ivy.jar', uri: 'antlib:org.apache.ivy.ant',

resource: 'org/apache/ivy/ant/antlib.xml'

def ivy = namespace(ant, 'antlib:org.apache.ivy.ant')

ivy.settings file: 'ivysettings.xml'

ivy.retrieve()

}

target(compileTest: '') {

depends 'compile'

mkdir dir: 'target/test-classes'

javac(destdir: 'target/test-classes', srcdir: 'src/test') {

classpath {

pathelement location: 'target/classes'

fileset dir: 'lib', includes: '*.jar'

}

}

}

target(test: '') {

depends 'compileTest'

mkdir dir: 'target/test-reports'

junit(printsummary: 'yes', haltonfailure: 'yes', fork: 'yes') {

classpath {

pathelement location: 'target/classes'

pathelement location: 'target/test-classes'

fileset dir: 'lib', includes: '*.jar'

}

formatter type: 'plain'

formatter type: 'xml'

batchtest(todir: 'target/test-reports', fork: 'yes') {

fileset dir: 'target/test-classes'

}

}

}

setDefaultTarget 'package'

build.g

radle

usePlugin 'java'

sourceCompatibility = 1.5

version = '1.0-SNAPSHOT'

repositories {

mavenCentral()

}

dependencies {

testCompile 'junit:junit:4.7'

}

MODULAR GROOVY

Modularisation: Grapes

// Google Collections exampleimport com.google.common.collect.HashBiMap

@Grab(group='com.google.collections',module='google-collections',version='1.0-rc1')

def getFruit() {[ grape:'purple',

lemon:'yellow',orange:'orange' ] as HashBiMap

}

assert fruit.lemon == 'yellow'assert fruit.inverse().yellow == 'lemon'

Modularisation: OSGi

This is Apache Sling in five bullets:

• REST based web framework

• Content-driven, using a JCR content repository

• Powered by OSGi

• Scripting inside, multiple languages

• Apache Open Source project

See also: Grails JCR plugin

See also: http://hamletdarcy.blogspot.com

PATTERNS

Better Design Patterns: Immutable...

• Java Immutable Class

– As per Joshua BlochEffective Java

AUG 2009 - 70

©

ASERT

2006-2009

public final class Punter {private final String first;private final String last;

public String getFirst() {return first;

}

public String getLast() {return last;

}

@Overridepublic int hashCode() {

final int prime = 31;int result = 1;result = prime * result + ((first == null)

? 0 : first.hashCode());result = prime * result + ((last == null)

? 0 : last.hashCode());return result;

}

public Punter(String first, String last) {this.first = first;this.last = last;

}// ...

// ...@Overridepublic boolean equals(Object obj) {

if (this == obj)return true;

if (obj == null)return false;

if (getClass() != obj.getClass())return false;

Punter other = (Punter) obj;if (first == null) {

if (other.first != null)return false;

} else if (!first.equals(other.first))return false;

if (last == null) {if (other.last != null)

return false;} else if (!last.equals(other.last))

return false;return true;

}

@Overridepublic String toString() {

return "Punter(first:" + first+ ", last:" + last + ")";

}

}

...Better Design Patterns: Immutable...

AUG 2009 - 71

©

ASERT

2006-2009

public final class Punter {private final String first;private final String last;

public String getFirst() {return first;

}

public String getLast() {return last;

}

@Overridepublic int hashCode() {

final int prime = 31;int result = 1;result = prime * result + ((first == null)

? 0 : first.hashCode());result = prime * result + ((last == null)

? 0 : last.hashCode());return result;

}

public Punter(String first, String last) {this.first = first;this.last = last;

}// ...

// ...@Overridepublic boolean equals(Object obj) {

if (this == obj)return true;

if (obj == null)return false;

if (getClass() != obj.getClass())return false;

Punter other = (Punter) obj;if (first == null) {

if (other.first != null)return false;

} else if (!first.equals(other.first))return false;

if (last == null) {if (other.last != null)

return false;} else if (!last.equals(other.last))

return false;return true;

}

@Overridepublic String toString() {

return "Punter(first:" + first+ ", last:" + last + ")";

}

}

boilerplate

• Java Immutable Class

...Better Design Patterns: Immutable

AUG 2009 - 72

©

ASERT

2006-2009

@Immutable class Punter {String first, last

}

AUG 2009 - 73

©

ASERT

2006-2009

Better Design Patterns: Singleton

class Calculator {def total = 0def add(a, b) { total++; a + b }

}

def INSTANCE = new Calculator()Calculator.metaClass.constructor = { -> INSTANCE }

def c1 = new Calculator()def c2 = new Calculator()

assert c1.add(1, 2) == 3assert c2.add(3, 4) == 7

assert c1.is(c2)assert [c1, c2].total == [2, 2]

@Singleton(lazy=true)class X {

def getHello () {"Hello, World!"

}}

println X.instance.hello

AUG 2009 - 74

©

ASERT

2006-2009

Better Design Patterns: Delegate…import java.util.Date;

public class Event {private String title;private String url;private Date when;

public String getUrl() {return url;

}

public void setUrl(String url) {this.url = url;

}

public String getTitle() {return title;

}

public void setTitle(String title) {this.title = title;

}// ...

public Date getWhen() {return when;

}

public void setWhen(Date when) {this.when = when;

}

public boolean before(Date other) {return when.before(other);

}

public void setTime(long time) {when.setTime(time);

}

public long getTime() {return when.getTime();

}

public boolean after(Date other) {return when.after(other);

}// ...

AUG 2009 - 75

©

ASERT

2006-2009

…Better Design Patterns: Delegate…

import java.util.Date;

public class Event {private String title;private String url;private Date when;

public String getUrl() {return url;

}

public void setUrl(String url) {this.url = url;

}

public String getTitle() {return title;

}

public void setTitle(String title) {

this.title = title;}// ...

public Date getWhen() {return when;

}

public void setWhen(Date when) {this.when = when;

}

public boolean before(Date other) {

return when.before(other);}

public void setTime(long time) {when.setTime(time);

}

public long getTime() {return when.getTime();

}

public boolean after(Date other) {return when.after(other);

}// ...

boilerplate

AUG 2009 - 76

©

ASERT

2006-2009

…Better Design Patterns: Delegate

class Event {String title, url@Delegate Date when

}

def gr8conf = new Event(title: "GR8 Conference",url: "http://www.gr8conf.org",when: Date.parse("yyyy/MM/dd", "2009/05/18"))

def javaOne = new Event(title: "JavaOne",url: "http://java.sun.com/javaone/",when: Date.parse("yyyy/MM/dd", "2009/06/02"))

assert gr8conf.before(javaOne.when)

FURTHER INFO

More Information: on the web

• Web sites– http://groovy.codehaus.org

– http://grails.codehaus.org

– http://pleac.sourceforge.net/pleac_groovy (many examples)

– http://www.asert.com.au/training/java/GV110.htm (workshop)

• Mailing list for users– user@groovy.codehaus.org

• Information portals– http://www.aboutgroovy.org

– http://www.groovyblogs.org

• Documentation (1000+ pages)– Getting Started Guide, User Guide, Developer Guide, Testing

Guide, Cookbook Examples, Advanced Usage Guide

AUG 2009 - 78

©

ASERT

2006-2009

More Information: Groovy in Action

AUG 2009 - 79

©

ASERT

2006-2009

Second edition of GinA, ‘ReGinA’ now under development