Groovy Tutorial

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

3 comments

Comments 1 - 3 of 3 previous next Post a comment

  • + guest8fe0df Rick Reumann 3 months ago
    Most Excellent. Thanks so much.
  • + jshingler jshingler 4 months ago
    Paul, Great presentation.
  • + hansamann Sven Haiges 4 months ago
    Hi Paul, thanx for mentioning the grails podcast - great slides, adding it to the shownotes for the next episode. Thx Sven
Post a comment
Embed Video
Edit your comment Cancel

13 Favorites

Groovy Tutorial - Presentation Transcript

  1. © ASERT 2006-2009 Groovy Tutorial Dr Paul King paulk@asert.com.au ASERT, Australia
  2. What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” @ ASERT 2006-2009 Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming JUL 2009 - 2
  3. Groovy Goodies Overview • Fully object oriented • Closures: reusable and assignable pieces of code • Operators can be • GPath: efficient overloaded @ ASERT 2006-2009 object navigation • Multimethods • GroovyBeans • Literal declaration for • grep and switch lists (arrays), maps, ranges and regular • Templates, builder, expressions swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks JUL 2009 - 3
  4. Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Making Java Groovy (soon) Now free
  5. … Growing Acceptance … © ASERT 2006-2009 JUL 2009 - 5
  6. … Growing Acceptance … © ASERT 2006-2009 Groovy and Grails downloads: 70-90K per month and growing JUL 2009 - 6
  7. … Growing Acceptance … © ASERT 2006-2009 Source: http://www.micropoll.com/akira/mpresult/501697-116746 Source: http://www.grailspodcast.com/ JUL 2009 - 7
  8. … Growing Acceptance … © ASERT 2006-2009 http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes http://www.java.net JUL 2009 - 8
  9. … Growing Acceptance © ASERT 2006-2009 JUL 2009 - 9
  10. The Landscape of JVM Languages optional static types @ ASERT 2006-2009 Dynamic features call for dynamic types Java bytecode calls for static types The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform. JUL 2009 - 10
  11. Groovy Starter System.out.println("Hello, World!"); // optional semicolon, println 'Hello, World!' // System.out, brackets, // main() method, class defn def name = 'Guillaume' // dynamic typing println "$name, I'll get the car." // GString String longer = """${name}, the car is in the next row.""" // multi-line string @ ASERT 2006-2009 // with static typing assert 0.5 == 1/2 // BigDecimal equals() def printSize(obj) { // optional duck typing print obj?.size() // safe dereferencing } def pets = ['ant', 'bee', 'cat'] // native list syntax pets.each { pet -> // closure support assert pet < 'dog' // overloading '<' on String } // or: for (pet in pets)... JUL 2009 - 11
  12. A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { This code List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { is valid String s = (String) strings.get(i); Java and if (s.length() <= length) { result.add(s); valid Groovy } } @ ASERT 2006-2009 return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Based on an Erase e = new Erase(); example by List shortNames = e.removeLongerThan(names, 3); Jim Weirich System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { & Ted Leung String s = (String) shortNames.get(i); System.out.println(s); } } } JUL 2009 - 12
  13. ...A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { Do the List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { semicolons String s = (String) strings.get(i); add anything? if (s.length() <= length) { result.add(s); And shouldn‟t } } we us more @ ASERT 2006-2009 } return result; modern list public static void main(String[] args) { notation? List names = new ArrayList(); names.add("Ted"); names.add("Fred"); Why not names.add("Jed"); names.add("Ned"); System.out.println(names); import common Erase e = new Erase(); libraries? List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } JUL 2009 - 13
  14. ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { @ ASERT 2006-2009 List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } } JUL 2009 - 14
  15. ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) Do we need } the static types? } return result Must we always } have a main public static void main(String[] args) { method and @ ASERT 2006-2009 List names = new ArrayList() names.add("Ted"); names.add("Fred") class definition? names.add("Jed"); names.add("Ned") System.out.println(names) How about Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) improved System.out.println(shortNames.size()) consistency? for (String s in shortNames) { System.out.println(s) } } } JUL 2009 - 15
  16. ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } @ ASERT 2006-2009 names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } JUL 2009 - 16
  17. ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) Shouldn‟t we } } have special return result notation for lists? } And special @ ASERT 2006-2009 names = new ArrayList() facilities for names.add("Ted") names.add("Fred") list processing? names.add("Jed") Is „return‟ names.add("Ned") System.out.println(names) needed at end? shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } JUL 2009 - 17
  18. ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) } @ ASERT 2006-2009 JUL 2009 - 18
  19. ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } Is the method names = ["Ted", "Fred", "Jed", "Ned"] now needed? System.out.println(names) shortNames = removeLongerThan(names, 3) Easier ways to System.out.println(shortNames.size()) use common shortNames.each{ System.out.println(s) } methods? @ ASERT 2006-2009 Are brackets required here? JUL 2009 - 19
  20. ...A Better Java... names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } @ ASERT 2006-2009 JUL 2009 - 20
  21. ...A Better Java names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } @ ASERT 2006-2009 ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned JUL 2009 - 21
  22. Better JavaBeans... import java.math.BigDecimal; ... public class BikeJavaBean { public void setModel(String model) { private String manufacturer; this.model = model; private String model; } private int frame; private String serialNo; public int getFrame() { private double weight; return frame; private String status; } private BigDecimal cost; public String getSerialNo() { public BikeJavaBean(String manufacturer, String model, return serialNo; int frame, String serialNo, } double weight, String status) { this.manufacturer = manufacturer; public void setSerialNo(String serialNo) { this.model = model; this.serialNo = serialNo; this.frame = frame; } this.serialNo = serialNo; @ ASERT 2006-2009 this.weight = weight; public double getWeight() { this.status = status; return weight; } } public String toString() { public void setWeight(double weight) { return "Bike:“ + this.weight = weight; “n manufacturer -- " + manufacturer + } "n model -- " + model + "n frame -- " + frame + public String getStatus() { "n serialNo -- " + serialNo + return status; "n"; } } public void setStatus(String status) { public String getManufacturer() { this.status = status; return manufacturer; } } public BigDecimal getCost() { public void setManufacturer(String manufacturer) { return cost; this.manufacturer = manufacturer; } } public void setCost(BigDecimal cost) { public String getModel() { this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP); return model; } } } ... Modified example from: http://www.ibm.com/developerworks/library/j-pg09196.html JUL 2009 - 22
  23. ...Better JavaBeans... import java.math.BigDecimal; ... public class BikeJavaBean { public void setModel(String model) { Auto private String manufacturer; this.model = model; private String model; private int frame; } getters? private String serialNo; private double weight; private String status; public int getFrame() { } return frame; Auto private BigDecimal cost; public String getSerialNo() { setters? public BikeJavaBean(String manufacturer, String model, return serialNo; int frame, String serialNo, double weight, String status) { } Auto this.manufacturer = manufacturer; this.model = model; this.frame = frame; } this.serialNo = serialNo; construction? public void setSerialNo(String serialNo) { this.serialNo = serialNo; @ ASERT 2006-2009 this.weight = weight; public double getWeight() { this.status = status; return weight; } } public String toString() { public void setWeight(double weight) { return "Bike:“ + this.weight = weight; “n manufacturer -- " + manufacturer + } "n model -- " + model + "n frame -- " + frame + public String getStatus() { "n serialNo -- " + serialNo + return status; "n"; } } public void setStatus(String status) { public String getManufacturer() { this.status = status; return manufacturer; } } public BigDecimal getCost() { public void setManufacturer(String manufacturer) { return cost; this.manufacturer = manufacturer; } } public void setCost(BigDecimal cost) { public String getModel() { this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP); return model; } } } ... JUL 2009 - 23
  24. ... Better JavaBeans ... class BikeGroovyBean { String manufacturer, model, serialNo, status final Integer frame Double weight BigDecimal cost public void setCost(BigDecimal newCost) { cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP) @ ASERT 2006-2009 } public String toString() { return """Bike: manufacturer -- $manufacturer model -- $model More complicated than frame -- $frame normal because we want: serialNo -- $serialNo * Read only frame """ * Custom setter for cost } * Custom toString() } JUL 2009 - 24
  25. ... Better JavaBeans class BikeGroovyBean { String manufacturer, model, serialNo, status Integer frame Double weight BigDecimal cost } @ ASERT 2006-2009 In more typical cases it is often as simple as this. JUL 2009 - 25
  26. Better Strings • Several forms – Single quotes for // normal strings simple strings def firstname = 'Kate' def surname= "Bush" – Double quotes for assert firstname * 2 == 'KateKate' GStrings which // GString support variable def fullname = "$firstname $surname" @ ASERT 2006-2009 expansion assert fullname == 'Kate Bush' – Slashy strings assert fullname - firstname == ' Bush' assert fullname.padLeft(10) == behave like ' Kate Bush' GStrings but preserve // indexing (including ranges) assert fullname[0..3] == firstname backslashes (great assert fullname[-4..-1] == surname for regex and assert fullname[5, 3..1] == 'Beta' directory names) – Multi-line versions JUL 2009 - 26
  27. …Better Strings… // slashy string: (almost) no escaping // Multi-line strings def path = /C:WindowsSystem32/ def twister = ''' She sells, sea shells def plain = 'nrtbf$' By the sea shore''' assert plain.size() == 7 def slashy = /nrtbf$/ def lines = assert slashy.size() == 14 twister.split('n') assert lines.size() == 2 // late binding trick with closures @ ASERT 2006-2009 fullname = "${-> firstname} $surname" def address = """ assert fullname == 'Kate Bush' $fullname firstname = 'George' 123 First Ave surname = 'Clooney' New York assert fullname == 'George Bush' """.trim() println """ def lines = ---------------------- address.split('n') | $fullname | assert lines.size() == 3 | 123 First Ave | | New York | ---------------------- """ JUL 2009 - 27
  28. …Better Strings // more substrings string = 'hippopotamus' assert string - 'hippo' - 'mus' + 'to' == 'potato' assert string.replace('ppopotam','bisc') == 'hibiscus' // processing characters assert 'apple'.toList() == ['a', 'p', 'p', 'l', 'e'] //also: 'apple' as String[], 'apple'.split('') @ ASERT 2006-2009 string = "an apple a day" assert string.toList().unique().sort().join() == ' adelnpy' // reversing chars/words assert 'string'.reverse() == 'gnirts' string = 'Yoda said, "can you see this?"' revwords = string.split(' ').toList().reverse().join(' ') assert revwords == 'this?" see you "can said, Yoda' JUL 2009 - 28
  29. Better Lists, Maps, Ranges • Lists – Special syntax for list literals – Additional common methods (operator overloading) def list = [3, new Date(), 'Jan'] assert list + list == list * 2 • Maps @ ASERT 2006-2009 – Special syntax for map literals – Additional common methods def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2 • Ranges – Special syntax for various kinds of ranges def letters = 'a'..'z' def numbers = 0..<10 JUL 2009 - 29
  30. Better Control Structures: Branching/Loops • Branching and loops • Loop-like Closures for (int i = 0; i < n; i++) { } myMap.each { key, value -> for (Type item : iterable) { } println "$key : $value" for (item in iterable) { } } // if (condition) ... // each, every, collect, any, // else if (condition) ... // inject, find, findAll, // else ... // upto, downto, times, grep, @ ASERT 2006-2009 // throw, try...catch...finally // reverseEach, eachMatch, // for, while // eachWithIndex, eachLine // d = a ? b : c // eachFile, eachFileMatch, // a = b ?: c // eachByte, combinations ... // d = a?.b?.c • Groovy Truth words = ['bob', 'alpha', if (1) // ... 'rotator', 'omega', 'reviver'] if (object) // ... bigPalindromes = words.findAll{ w -> if (collection) // ... w == w.reverse() && w.size() > 5} if (map) // ... assert bigPalindromes == if (matcher) // ... ['rotator', 'reviver'] JUL 2009 - 30
  31. Better Control Structures: Switch switch (10) { case 0 : assert false ; break case 0..9 : assert false ; break case [8,9,11] : assert false ; break case Float : assert false ; break case {it%3 == 0} : assert false ; break case ~/../ : assert true ; break @ ASERT 2006-2009 default : assert false ; break } • Extensible – Implement your own isCase() method JUL 2009 - 31
  32. Better Control Structures: Switch – Custom isCase WARNING: class Color { Advanced def name Topic! static yellow = new Color(name:'Yellow') static orange = new Color(name:'Orange') def isCase(candidate) { candidate.color == name Found something Yellow } Found something Orange } Unknown color @ ASERT 2006-2009 import static Color.* class Banana { def color = 'Yellow' } class Orange { def color = 'Orange' } class Grape { def color = 'Purple' } fruits = [new Banana(), new Orange(), new Grape()] fruits.each { println inspectFruit(it) } def inspectFruit(f) { switch (f) { case yellow: return 'Found something Yellow' case orange: return 'Found something Orange' default: return 'Unknown color' } } JUL 2009 - 32
  33. Better Control Structures: Switch Poker… hand1 hand2 8C TS KC 9H 4S 7D 2S 5D 3S AC suites = 'SHDC' ranks = '23456789TJQKA' suite = { String card -> suites.indexOf(card[1]) } rank = { String card -> ranks.indexOf(card[0]) } rankSizes = { List cards -> @ ASERT 2006-2009 cards.groupBy(rank).collect{ k, v -> v.size() }.sort() } rankValues = { List cards -> cards.collect{ rank(it) }.sort() } // ... println rankSizes(["7S", "7H", "2H", "7D", "AH"]) // => [1, 1, 3] JUL 2009 - 33
  34. …Better Control Structures: Switch Poker… // ... flush = { List cards -> cards.groupBy(suite).size() == 1 } straight = { def v = rankValues(it); v == v[0]..v[0]+4 } straightFlush = { List cards -> straight(cards) && flush(cards) } @ ASERT 2006-2009 fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] } fullHouse = { List cards -> rankSizes(cards) == [2, 3] } threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] } twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] } pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] } // ... JUL 2009 - 34
  35. … Better Control Structures: Switch Poker // ... def rankHand(List cards) { switch (cards) { case straightFlush : return 9 case fourOfAKind : return 8 case fullHouse : return 7 case flush : return 6 @ ASERT 2006-2009 case straight : return 5 case threeOfAKind : return 4 case twoPair : return 3 case pair : return 2 default : return 1 } } // ... JUL 2009 - 35
  36. Using Closures... • Traditional mainstream languages – Data can be stored in variables, passed around, combined in structured ways to form more complex data; code stays put where it is defined • Languages supporting closures – Data and code can be stored in variables, passed @ ASERT 2006-2009 around, combined in structured ways to form more complex algorithms and data doubleNum = { num -> num * 2 } println doubleNum(3) // => 6 processThenPrint = { num, closure -> num = closure(num); println "num is $num" } processThenPrint(3, doubleNum) // => num is 6 processThenPrint(10) { it / 2 } // => num is 5 JUL 2009 - 36
  37. ...Using Closures... import static Math.* Algorithm piE differs by 1.0206946399193839E-11 Algorithm piF differs by 1.0070735356748628E-9 Algorithm piC differs by 2.668102068170697E-7 piA = { 22 / 7 } Algorithm piD differs by 4.813291008076703E-5 piB = { 333/106 } Algorithm piB differs by 8.321958979307098E-5 piC = { 355/113 } Algorithm piA differs by 0.001264489310206951 piD = { 0.6 * (3 + sqrt(5)) } piE = { 22/17 + 37/47 + 88/83 } piF = { sqrt(sqrt(2143/22)) } @ ASERT 2006-2009 howCloseToPI = { abs(it.value() - PI) } algorithms = [piA:piA, piB:piB, piC:piC, piD:piD, piE:piE, piF:piF] findBestPI(algorithms) def findBestPI(map) { map.entrySet().sort(howCloseToPI).each { entry -> def diff = howCloseToPI(entry) println "Algorithm $entry.key differs by $diff" } } JUL 2009 - 37
  38. ...Using Closures... • Used for many things in Groovy: • Iterators new File('/x.txt').eachLine { println it • Callbacks } • Higher-order functions • Specialized control structures • Dynamic method definition @ ASERT 2006-2009 • Resource allocation 3.times { println 'Hi' } • Threads [0, 1, 2].each { number -> • Continuations println number } def houston(Closure doit) { (10..1).each { count -> [0, 1, 2].each { println it} doit(count) } def printit = { println it } } [0, 1, 2].each printit houston { println it } JUL 2009 - 38
  39. ...Using Closures... map = ['a': 1, 'b': 2] map.each {key, value -> map[key] = value * 2} assert map == ['a': 2, 'b': 4] doubler = {key, value -> map[key] = value * 2} map.each(doubler) @ ASERT 2006-2009 assert map == ['a': 4, 'b': 8] def doubleMethod(entry) { map[entry.key] = entry.value * 2 } doubler = this.&doubleMethod map.each(doubler) assert map == ['a': 8, 'b': 16] JUL 2009 - 39
  40. ...Using Closures... assert [1, 2, 3].grep{ it < 3 } == [1, 2] assert [1, 2, 3].any{ it % 2 == 0 } assert [1, 2, 3].every{ it < 4 } assert (1..9).collect{it}.join() == '123456789' assert (1..4).collect{it * 2}.join() == '2468' def add = { x, y -> x + y } @ ASERT 2006-2009 def mult = { x, y -> x * y } assert add(1, 3) == 4 assert mult(1, 3) == 3 def min = { x, y -> [x, y].min() } def max = { x, y -> [x, y].max() } def triple = mult.curry(3); assert triple(2) == 6 def atLeastTen = max.curry(10) assert atLeastTen(5) == 10 assert atLeastTen(15) == 15 JUL 2009 - 40
  41. ...Using Closures def pairWise(list, Closure invoke) { if (list.size() < 2) return [] def next = invoke(list[0], list[1]) return [next] + pairWise(list[1..-1], invoke) } @ ASERT 2006-2009 // using min, max, etc. From previous slide assert pairWise(1..5, add) == [3, 5, 7, 9] assert pairWise(1..5, mult) == [2, 6, 12, 20] assert pairWise(1..5, min) == [1, 2, 3, 4] assert pairWise(1..5, max) == [2, 3, 4, 5] assert 'cbaxabc' == ['a', 'b', 'c'].inject('x') { result, item -> item + result + item } JUL 2009 - 41
  42. Better Regular Expressions assert "Hello World!" =~ /Hello/ // Find operator assert "Hello World!" ==~ /Hellob.*/ // Match operator def p = ~/Hellob.*/ // Pattern operator assert p.class.name == 'java.util.regex.Pattern' def input = "Voting is open between 01 Nov 2008 and 04 Nov 2008" @ ASERT 2006-2009 def matches = (input =~ /dd? ... d{4}/) matches.each { m -> 01 Nov 2008 println m 04 Nov 2008 } def matchingGroups = (input =~ /(dd?) (...) (d{4})/) matchingGroups.each { all, d, m, y -> println "$d/$m/$y" 01/Nov/2008 } 04/Nov/2008 JUL 2009 - 42
  43. Better Dates... • Similar to Java – Use java.util.Date, java.util.Calendar, java.sql.Date, ... – Can use static imports to help – Or 3rd party package like Joda Time • Special support for changing times: @ ASERT 2006-2009 date2 = date1 + 1 // day date2 = date1 + 1.week – 3.days + 6.hours • Utility methods for date input and output – Date.parse(formatString, dateString) – date.format(formatString) – date.timeString, date.dateString, date.dateTimeString • Potentially will change with JSR 310 JUL 2009 - 43
  44. ...Better Dates... import static java.util.Calendar.getInstance as now import java.util.GregorianCalendar as D import org.codehaus.groovy.runtime.TimeCategory println new D(2007,11,25).time println now().time Tue Dec 25 00:00:00 EST 2007 Thu Jun 28 10:10:34 EST 2007 def date = new Date() + 1 Fri Jun 29 10:10:35 EST 2007 @ ASERT 2006-2009 println date Tue Jul 17 11:10:35 EST 2007 Date was Jun/03/1998 use(TimeCategory) { println new Date() + 1.hour + 3.weeks - 2.days } dateStr = "1998-06-03" date = Date.parse("yyyy-MM-dd", dateStr) println 'Date was ' + date.format("MMM/dd/yyyy") def holidays = boxingDay..newYearsEve JUL 2009 - 44
  45. ...Better Dates import static java.util.Calendar.* def nowCal = Calendar.instance y = nowCal.get(YEAR) Date nowDate = nowCal.time def (m, d) = [nowDate[MONTH] + 1, nowDate[DATE]] println "Today is $d $m $y" nowCal.set DATE, 1 nowCal.set MONTH, FEBRUARY println "Changed to $nowCal.time" @ ASERT 2006-2009 Today is 26 9 2008 Changed to Fri Feb 01 23:05:20 EST 2008 cal = Calendar.instance cal.set 1988, APRIL, 4, 0, 0, 0 date = cal.time def (doy, woy, y) = [DAY_OF_YEAR, WEEK_OF_YEAR, YEAR].collect{ date[it] } println "$date is day $doy and week $woy of year $y" Mon Apr 04 00:00:00 EST 1988 is day 95 and week 15 of year 1988 JUL 2009 - 45
  46. Better Design Patterns: Builder • Markup Builder <html> import groovy.xml.* <head> def page = new MarkupBuilder() <title>Hello</title> page.html { </head> head { title 'Hello' } <body> @ ASERT 2006-2009 body { <ul> ul { <li>world 1</li> for (count in 1..5) { <li>world 2</li> li "world $count" <li>world 3</li> } } } } <li>world 4</li> <li>world 5</li> </ul> </body> </html> JUL 2009 - 46
  47. SwingBuilder import java.awt.FlowLayout builder = new groovy.swing.SwingBuilder() langs = ["Groovy", "Ruby", "Python", "Pnuts"] gui = builder.frame(size: [290, 100], title: 'Swinging with Groovy!') { panel(layout: new FlowLayout()) { panel(layout: new FlowLayout()) { for (lang in langs) { @ ASERT 2006-2009 checkBox(text: lang) } } button(text: 'Groovy Button', actionPerformed: { builder.optionPane(message: 'Indubitably Groovy!'). createDialog(null, 'Zen Message').show() }) button(text: 'Groovy Quit', actionPerformed: {System.exit(0)}) } } gui.show() Source: http://www.ibm.com/developerworks/java/library/j-pg04125/ JUL 2009 - 47
  48. SwingXBuilder import groovy.swing.SwingXBuilder @ ASERT 2006-2009 import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() JUL 2009 - 48
  49. GraphicsBuilder def colors = ['red','darkOrange','blue','darkGreen'] (0..3).each { index -> star( cx: 50 + (index*110), cy: 50, or: 40, ir: 15, borderColor: 'black', count: 2+index, fill: colors[index] ) star( cx: 50 + (index*110), cy: 140, or: 40, ir: 15, borderColor: 'black‘, count: 7+index, fill: colors[index] ) } @ ASERT 2006-2009 JUL 2009 - 49
  50. AntBuilder... def ant = new AntBuilder() ant.echo("hello") // let's just call one task // create a block of Ant using the builder pattern ant.sequential { myDir = "target/AntTest/" mkdir(dir: myDir) @ ASERT 2006-2009 copy(todir: myDir) { fileset(dir: "src/test") { include(name: "**/*.groovy") } } echo("done") } // now let's do some normal Groovy again file = new File("target/test/AntTest.groovy") assert file.exists() JUL 2009 - 50
  51. ...AntBuilder def ant = new AntBuilder() ant.echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println("Hello"); } } @ ASERT 2006-2009 ''') ant.with { javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done JUL 2009 - 51
  52. Using AntLibs: Maven Ant Tasks & AntUnit import static groovy.xml.NamespaceBuilder.newInstance as namespace def ant = new AntBuilder() def mvn = namespace(ant, 'antlib:org.apache.maven.artifact.ant') def antunit = namespace(ant, 'antlib:org.apache.ant.antunit') direct = [groupId:'jfree', artifactId:'jfreechart', version:'1.0.9'] indirect = [groupId:'jfree', artifactId:'jcommon', version:'1.0.12'] // download artifacts @ ASERT 2006-2009 mvn.dependencies(filesetId:'artifacts') { dependency(direct) } // print out what we downloaded ant.fileScanner { fileset(refid:'artifacts') }.each { println it } // use AntUnit to confirm expected files were downloaded def prefix = System.properties.'user.home' + '/.m2/repository' [direct, indirect].each { item -> def (g, a, v) = [item.groupId, item.artifactId, item.version] antunit.assertFileExists(file:"$prefix/$g/$a/$v/$a-${v}.jar") } C:Userspaulk.m2repositoryjfreejcommon1.0.12jcommon-1.0.12.jar C:Userspaulk.m2repositoryjfreejfreechart1.0.9jfreechart-1.0.9.jar JUL 2009 - 52
  53. Better File Manipulation... import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.io.File; // ... import java.io.FileOutputStream; private static void processFile(File file, PrintWriter out) { import java.io.PrintWriter; FileInputStream fis = null; import java.io.FileNotFoundException; InputStreamReader isr = null; import java.io.IOException; BufferedReader reader = null; import java.io.BufferedReader; try { import java.io.InputStreamReader; fis = new FileInputStream(file); import java.io.FileInputStream; isr = new InputStreamReader(fis); reader = new BufferedReader(isr); public class PrintJavaSourceFileLinesThatContainTheWordJava { String nextline; public static void main(String[] args) { int count = 0; File outfile = new File("result.txt"); while ((nextline = reader.readLine()) != null) { outfile.delete(); count++; File basedir = new File(".."); if (nextline.toLowerCase().contains("java")) { List<File> files = new ArrayList<File>(); out.println("File '" + file + files.add(basedir); "' on line " + count); © ASERT 2006-2009 FileOutputStream fos = null; out.println(nextline); PrintWriter out = null; out.println(); try { } fos = new FileOutputStream(outfile); } out = new PrintWriter(fos); } catch (FileNotFoundException e) { while (!files.isEmpty()) { e.printStackTrace(); File file = files.remove(0); } catch (IOException e) { if (file.isDirectory()) { e.printStackTrace(); files.addAll(Arrays.asList(file.listFiles())); } finally { } else { if (reader != null) { if (file.getName().endsWith(".java")) { try { reader.close(); } processFile(file, out); catch (IOException e) { e.printStackTrace(); } } } } if (isr != null) { } try { isr.close(); } } catch (FileNotFoundException e) { catch (IOException e) { e.printStackTrace(); } e.printStackTrace(); } } finally { if (fis != null) { if (out != null) { out.close(); } try { fis.close(); } if (fos != null) { catch (IOException e) { e.printStackTrace(); } try { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } } } } } // ... JUL 2009 - 53
  54. ...Better File Manipulation... import java.util.List; import import java.util.ArrayList; java.util.Arrays; boilerplate import java.io.File; // ... import java.io.FileOutputStream; private static void processFile(File file, PrintWriter out) { import java.io.PrintWriter; FileInputStream fis = null; import java.io.FileNotFoundException; InputStreamReader isr = null; import java.io.IOException; BufferedReader reader = null; import java.io.BufferedReader; try { import java.io.InputStreamReader; fis = new FileInputStream(file); import java.io.FileInputStream; isr = new InputStreamReader(fis); reader = new BufferedReader(isr); public class PrintJavaSourceFileLinesThatContainTheWordJava { String nextline; public static void main(String[] args) { int count = 0; File outfile = new File("result.txt"); while ((nextline = reader.readLine()) != null) { outfile.delete(); count++; File basedir = new File(".."); if (nextline.toLowerCase().contains("java")) { List<File> files = new ArrayList<File>(); out.println("File '" + file + files.add(basedir); "' on line " + count); © ASERT 2006-2009 FileOutputStream fos = null; out.println(nextline); PrintWriter out = null; out.println(); try { } fos = new FileOutputStream(outfile); } out = new PrintWriter(fos); } catch (FileNotFoundException e) { while (!files.isEmpty()) { e.printStackTrace(); File file = files.remove(0); } catch (IOException e) { if (file.isDirectory()) { e.printStackTrace(); files.addAll(Arrays.asList(file.listFiles())); } finally { } else { if (reader != null) { if (file.getName().endsWith(".java")) { try { reader.close(); } processFile(file, out); catch (IOException e) { e.printStackTrace(); } } } } if (isr != null) { } try { isr.close(); } } catch (FileNotFoundException e) { catch (IOException e) { e.printStackTrace(); } e.printStackTrace(); } } finally { if (fis != null) { if (out != null) { out.close(); } try { fis.close(); } if (fos != null) { catch (IOException e) { e.printStackTrace(); } try { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } } } } } // ... JUL 2009 - 54
  55. ...Better File Manipulation def out = new File('result.txt') out.delete() new File('..').eachFileRecurse {file -> if (file.name.endsWith('.groovy')) { file.eachLine { line, num -> if (line.toLowerCase().contains('groovy')) out << © ASERT 2006-2009 "File '$file' on line $numn$linenn" } } } File '..filessrcPrintGroovySourceLines.groovy' on line 4 if (file.name.endsWith('.groovy')) { File '..filessrcPrintGroovySourceLines.groovy' on line 6 if (line.toLowerCase().contains('groovy')) File '..jdbcsrcJdbcGroovy.groovy' on line 1 import groovy.sql.Sql … JUL 2009 - 55
  56. Better Process Management... // windows version (minimal error/output) def p = "cmd /c dir *.xml".execute() p.waitFor() if (p.exitValue()) println p.err.text else println p.text ----- stdout ----- Volume in drive D is DATA Volume Serial Number is FC5C-B39D © ASERT 2006-2009 Directory of D:svntrunk-groovygroovy-core // unix version 27/06/2009 10:13 AM 38,478 build.xml def p = "ls *.xml".execute() 29/06/2009 06:53 PM 27,617 pom.xml def err = new StringBuffer() 2 File(s) 66,095 bytes 0 Dir(s) 15,503,597,568 bytes free def out = new StringBuffer() // could ignore/use Stream below ----- stderr ----- Return Code: 1 p.consumeProcessOutput(out, err) ls: *.xml: No such file or directory def exitValue = p.waitFor() if (err) println """----- stderr ----- Return Code: $exitValue $err""" if (out) println "----- stdout -----n$out" JUL 2009 - 56
  57. ...Better Process Management sout: tstfl.grvy serr: def sout = new StringBuffer() def serr = new StringBuffer() proc1 = 'tr -d o'.execute() proc2 = 'tr -d e'.execute() © ASERT 2006-2009 proc3 = 'tr -d i'.execute() proc3.consumeProcessOutput(sout, serr) proc1 | proc2 | proc3 [proc1, proc2].each{ it.consumeProcessErrorStream(serr) } proc1.withWriter { writer -> writer << 'testfile.groovy' } proc3.waitForOrKill(1000) println 'sout: ' + sout println 'serr: ' + serr JUL 2009 - 57
  58. Better Thread Management Thread.start{ Hello from thread 2 4.times { Hello from thread 1 println 'Hello from thread 1' Hello from main thread sleep 400 Hello from main thread } Hello from thread 2 } Hello from thread 1 Thread.start{ Hello from main thread © ASERT 2006-2009 5.times { Hello from thread 2 println 'Hello from thread 2' Hello from main thread sleep 300 Hello from thread 1 } Hello from main thread } Hello from thread 2 7.times { Hello from main thread println 'Hello from main thread' Hello from thread 2 sleep 200 Hello from thread 1 } Hello from main thread JUL 2009 - 58
  59. Better SQL Manipulation... import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; // ... import java.sql.ResultSet; finally { import java.sql.SQLException; // release database resources close(rs); // close the ResultSet public class JdbcJava { close(stmt); // close the Statement public static Connection getConnection() throws Exception { close(conn); // close the Connection String driver = "org.hsqldb.jdbcDriver"; } String url = "jdbc:hsqldb:file:EMPDB"; } String username = "sa"; String password = ""; Class.forName(driver); private static void close(ResultSet rs) { return DriverManager.getConnection(url, username, password); try { } if (rs != null) rs.close(); } catch (SQLException e) { © ASERT 2006-2009 public static void main(String[] args) { Connection conn = null; e.printStackTrace(); Statement stmt = null; } } ResultSet rs = null; try { conn = getConnection(); private static void close(Statement st) { stmt = conn.createStatement(); try { String query = if (st != null) st.close(); "select id, lastname, firstname from Employees"; } catch (SQLException e) { e.printStackTrace(); rs = stmt.executeQuery(query); while (rs.next()) { } System.out.println(rs.getString("id") + } ": " + rs.getString("firstname") + private static void close(Connection cn) { " " + rs.getString("lastname")); } try { if (cn != null) cn.close(); } catch (Exception e) { } catch (SQLException e) { // handle the exception e.printStackTrace(); e.printStackTrace(); } } System.err.println(e.getMessage()); } } // ... JUL 2009 - 59
  60. ...Better SQL Manipulation... import import java.sql.Connection; java.sql.DriverManager; boilerplate import java.sql.Statement; // ... import java.sql.ResultSet; finally { import java.sql.SQLException; // release database resources close(rs); // close the ResultSet public class JdbcJava { close(stmt); // close the Statement public static Connection getConnection() throws Exception { close(conn); // close the Connection String driver = "org.hsqldb.jdbcDriver"; } String url = "jdbc:hsqldb:file:EMPDB"; } String username = "sa"; String password = ""; Class.forName(driver); private static void close(ResultSet rs) { return DriverManager.getConnection(url, username, password); try { } if (rs != null) rs.close(); } catch (SQLException e) { © ASERT 2006-2009 public static void main(String[] args) { Connection conn = null; e.printStackTrace(); Statement stmt = null; } } ResultSet rs = null; try { conn = getConnection(); private static void close(Statement st) { stmt = conn.createStatement(); try { String query = if (st != null) st.close(); "select id, lastname, firstname from Employees"; } catch (SQLException e) { e.printStackTrace(); rs = stmt.executeQuery(query); while (rs.next()) { } System.out.println(rs.getString("id") + } ": " + rs.getString("firstname") + private static void close(Connection cn) { " " + rs.getString("lastname")); } try { if (cn != null) cn.close(); } catch (Exception e) { } catch (SQLException e) { // handle the exception e.printStackTrace(); e.printStackTrace(); } } System.err.println(e.getMessage()); } } // ... JUL 2009 - 60
  61. ...Better SQL Manipulation import groovy.sql.Sql def url = 'jdbc:hsqldb:file:EMPDB' def username = 'sa' def password = '' © ASERT 2006-2009 def driver = 'org.hsqldb.jdbcDriver' def db = Sql.newInstance(url, username, password, driver) db.eachRow("SELECT id, firstname, lastname FROM Employees") { println "$it.id: $it.firstname $it.lastname" } JUL 2009 - 61
  62. More Details: Working with Databases • Using standard SQL statements import groovy.sql.Sql def foo = 'cheese' def db = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user", "pswd", "com.mysql.jdbc.Driver") db.eachRow("select * from FOOD where type=${foo}") { println "Gromit likes ${it.name}" @ ASERT 2006-2009 } • Using DataSets import groovy.sql.Sql def db = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user", "pswd", "com.mysql.jdbc.Driver") def food = db.dataSet('FOOD') def cheese = food.findAll { it.type == 'cheese' } cheese.each { println "Gromit likes ${it.name}" } JUL 2009 - 62
  63. DataSets and Lazy Evaluation athleteSet = db.dataSet('Athlete') youngsters = athleteSet.findAll{ it.dateOfBirth > '1970-1-1'} paula = youngsters.findAll{ it.firstname == 'Paula'} println paula.sql // => @ ASERT 2006-2009 // select * from Athlete where dateOfBirth > ? and firstname = ? println paula.parameters // => // [1970-1-1, Paula] paula.each { println it.lastname } // database called here // => // Radcliffe JUL 2009 - 63
  64. Better XML Manipulation... <records> records.xml <car name='HSV Maloo' make='Holden' year='2006'> <country>Australia</country> <record type='speed'>Production Pickup Truck with speed of 271kph</record> </car> © ASERT 2006-2009 <car name='P50' make='Peel' year='1962'> <country>Isle of Man</country> <record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg weight</record> </car> <car name='Royale' make='Bugatti' year='1931'> <country>France</country> <record type='price'>Most Valuable Car at $15 million</record> </car> </records> JUL 2009 - 64
  65. ...Better XML Manipulation... import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; public class FindYearsJava { public static void main(String[] args) { © ASERT 2006-2009 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document document = builder.parse(new File("records.xml")); NodeList list = document.getElementsByTagName("car"); for (int i = 0; i < list.getLength(); i++) { Node n = list.item(i); Node year = n.getAttributes().getNamedItem("year"); System.out.println("year = " + year.getTextContent()); } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } JUL 2009 - 65
  66. ...Better XML Manipulation... import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Node; boilerplate import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; public class FindYearsJava { public static void main(String[] args) { © ASERT 2006-2009 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document document = builder.parse(new File("records.xml")); NodeList list = document.getElementsByTagName("car"); for (int i = 0; i < list.getLength(); i++) { Node n = list.item(i); Node year = n.getAttributes().getNamedItem("year"); System.out.println("year = " + year.getTextContent()); } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } JUL 2009 - 66
  67. ...Better XML Manipulation def records = new XmlParser().parse("records.xml") records.car.each { © ASERT 2006-2009 println "year = ${it.@year}" } year = 2006 year = 1962 year = 1931 JUL 2009 - 67
  68. XML Processing Options • Reading XML – Special Groovy support: XmlParser, XmlSlurper, DOMCategory • All support GPath, XmlSlurper features Lazy loading – Or Groovy sugar for your current favorites: DOM, SAX, StAX, DOM4J, JDom, XOM, XPath, XSLT, XQuery, etc. • Creating XML – Special Groovy support: MarkupBuilder and © ASERT 2006-2009 StreamingMarkupBuilder – Or again, enhanced syntax for your current favorites • Updating XML – Using above: read followed by create – Can be done with XmlParser, XmlSlurper, DOMCategory – Or with your Java favorites • Verifying XML – Also DTD, W3C XML Schema, Relax NG in a similar fashion to Java mechanisms for these features JUL 2009 - 68
  69. More Details: XmlSlurper def records = new XmlSlurper().parse("records.xml") assert 3 == records.car.size() // 3 records in total assert 10 == records.depthFirst().collect{ it }.size() // 10 nested nodes // test properties of the first record def firstRecord = records.car[0] assert 'car' == firstRecord.name() assert 'Holden' == firstRecord.@make.toString() && 'Australia' == firstRecord.country.text() // 2 cars have an 'e' in the make assert 2 == records.car.findAll{ it.@make.toString().contains('e') }.size() @ ASERT 2006-2009 // 2 cars have an 'e' in the make assert 2 == records.car.findAll{ it.@make =~ '.*e.*' }.size() // makes of cars that have an 's' followed by an 'a' in the country assert ['Holden', 'Peel'] == records.car.findAll{ it.country =~ '.*s.*a.*' }.@make.collect{ it.toString() } // types of records assert ['speed', 'size', 'price'] == records.depthFirst().grep{ it.@type != '' }.'@type'*.toString() assert ['speed', 'size', 'price'] == records.'**'.grep{ it.@type != '' }.'@type'*.toString() // check parent() operator def countryOne = records.car[1].country assert 'Peel' == countryOne.parent().@make.toString() && 'Peel' == countryOne.'..'.@make.toString() // names of cars with records sorted by year def names = records.car.list().sort{ it.@year.toInteger() }.'@name'*.toString() assert ['Royale', 'P50', 'HSV Maloo'] == names JUL 2009 - 69
  70. Uniform Data Access • Not just better SQL processing and better XML processing but Uniform Data Access – i.e. Data access to Objects, XML and SQL Databases has more similarities than differences – Fluent API regardless of data source – Differences in detail about how to set up structures @ ASERT 2006-2009 – Similarities in how to process data within resulting structures – Equivalent to LINQ API in .Net JUL 2009 - 70
  71. Object Data Access… import java.text.SimpleDateFormat class Athlete { def firstname, lastname, gender, country, dateOfBirth } def asDate(dateStr) { new SimpleDateFormat("yyyy-MM-dd").parse(dateStr) } @ ASERT 2006-2009 def athletes = [ new Athlete(firstname: 'Paul', lastname: 'Tergat', dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'), new Athlete(firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'), new Athlete(firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR') ] JUL 2009 - 71
  72. …Object Data Access def bornSince70 = { asDate(it.dateOfBirth) > asDate('1970-1-1') } def excludingKh = { it.lastname <= 'Kg' || it.lastname >= 'Ki' } def byGenderDescThenByCountry = { a, b -> a.gender == b.gender ? a.country <=> b.country : @ ASERT 2006-2009 b.gender <=> a.gender } def someYoungsters = athletes. findAll(bornSince70). findAll(excludingKh). sort(byGenderDescThenByCountry) someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" } JUL 2009 - 72
  73. Xml Data Access… import java.text.SimpleDateFormat import com.thoughtworks.xstream.XStream class Athlete { String firstname, lastname, gender, country, dateOfBirth } def asDate(dateStr) { new SimpleDateFormat("yyyy-MM-dd").parse(dateStr) } def athleteList = [ @ ASERT 2006-2009 new Athlete(firstname: 'Paul', lastname: 'Tergat', dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'), new Athlete(firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'), new Athlete(firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR') ] // create XML as input def input = new XStream().toXML(athleteList) JUL 2009 - 73
  74. …Xml Data Access def athletes = new XmlSlurper().parseText(input).Athlete def bornSince70 = { asDate(it.dateOfBirth.text()) > asDate('1970-1-1') } def excludingKh = { it.lastname.text() <= 'Kg' || it.lastname.text() >= 'Ki' } def byGenderDescThenByCountry = { a, b -> a.gender.text() == b.gender.text() ? @ ASERT 2006-2009 a.country.text() <=> b.country.text() : b.gender.text() <=> a.gender.text() } def someYoungsters = athletes. findAll(bornSince70).findAll(excludingKh).list(). sort(byGenderDescThenByCountry) someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" } JUL 2009 - 74
  75. Sql Data Access… import groovy.sql.Sql dbHandle = null def getDb() { if (dbHandle) return dbHandle def source = new org.hsqldb.jdbc.jdbcDataSource() source.database = 'jdbc:hsqldb:mem:GIA' source.user = 'sa' source.password = '' dbHandle = new Sql(source) @ ASERT 2006-2009 return dbHandle } db.execute ''' DROP INDEX athleteIdx IF EXISTS; DROP TABLE Athlete IF EXISTS; CREATE TABLE Athlete ( athleteId INTEGER GENERATED BY DEFAULT AS IDENTITY, firstname VARCHAR(64), lastname VARCHAR(64), country VARCHAR(3), gender CHAR(1), dateOfBirth DATE ); CREATE INDEX athleteIdx ON Athlete (athleteId); ''' JUL 2009 - 75
  76. …Sql Data Access… def athleteList = [ [firstname: 'Paul', lastname: 'Tergat', dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'], [firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'], [firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'], @ ASERT 2006-2009 [firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'], [firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR'] ] def athletes = db.dataSet('Athlete') athleteList.each {a -> athletes.add(a)} JUL 2009 - 76
  77. …Sql Data Access def bornSince70 = { it.dateOfBirth > '1970-1-1' } def excludingKh = { it.lastname <= 'Kg' || it.lastname >= 'Ki' } def someYoungsters = athletes. findAll(bornSince70). findAll(excludingKh). sort { it.gender }.reverse(). sort { it.country } @ ASERT 2006-2009 println someYoungsters.sql + 'n' + someYoungsters.parameters someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" } /* => select * from Athlete where dateOfBirth > ? and (lastname <= ? or lastname >= ?) order by gender DESC, country ["1970-1-1", "Kg", "Ki"] Ronaldo da Costa (M) BRA 1970-06-07 Sammy Korir (M) KEN 1971-12-12 Paula Radcliffe (F) GBR 1973-12-17 */ JUL 2009 - 77
  78. Metaprogramming • Runtime Metaprogramming – Categories including DGM – invokeMethod, methodMissing, getProperty – GroovyInterceptable – ExpandoMetaClass © ASERT 2006-2009 • Compile-time Metaprogramming – AST Macros – Local and Global flavors based around annotations – Reduced runtime overhead – Several built-in annotations: • @Immutable • @Delegate • @Singleton JUL 2009 - 78
  79. ExpandoMetaClass… import static java.lang.Character.isUpperCase String.metaClass.swapCase = { delegate.collect { ch -> isUpperCase(ch as char) ? ch.toLowerCase() : ch.toUpperCase() }.join() @ ASERT 2006-2009 } println new Date().toString().swapCase() // => sUN nOV 09 17:30:06 est 2008 List.metaClass.sizeDoubled = {-> delegate.size() * 2 } LinkedList list = [] list << 1 list << 2 assert 4 == list.sizeDoubled() JUL 2009 - 79
  80. …ExpandoMetaClass class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } @ ASERT 2006-2009 } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse() JUL 2009 - 80
  81. Grapes / Grab // Google Collections example import com.google.common.collect.HashBiMap @Grab(group='com.google.collections', module='google-collections', version='1.0-rc2') © ASERT 2006-2009 def getFruit() { [ grape:'purple', lemon:'yellow', orange:'orange' ] as HashBiMap } assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon' JUL 2009 - 81
  82. Better Design Patterns: Immutable... • Java Immutable Class – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { @ ASERT 2006-2009 if (other.first != null) public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... JUL 2009 - 82
  83. ...Better Design Patterns: Immutable... • Java Immutable Class boilerplate – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { @ ASERT 2006-2009 if (other.first != null) public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... JUL 2009 - 83
  84. ...Better Design Patterns: Immutable @Immutable class Punter { String first, last @ ASERT 2006-2009 } JUL 2009 - 84
  85. Better Design Patterns: Singleton class Calculator { def total = 0 def add(a, b) { total++; a + b } } def INSTANCE = new Calculator() Calculator.metaClass.constructor = { -> INSTANCE } @ ASERT 2006-2009 def c1 = new Calculator() def c2 = new Calculator() @Singleton(lazy=true) assert c1.add(1, 2) == 3 class X { assert c2.add(3, 4) == 7 def getHello () { "Hello, World!" assert c1.is(c2) } assert [c1, c2].total == [2, 2] } println X.instance.hello JUL 2009 - 85
  86. Better Design Patterns: Delegate… public Date getWhen() { import java.util.Date; return when; } public class Event { private String title; public void setWhen(Date when) { private String url; this.when = when; private Date when; } public String getUrl() { public boolean before(Date other) { return url; return when.before(other); } @ ASERT 2006-2009 } public void setUrl(String url) { public void setTime(long time) { this.url = url; when.setTime(time); } } public String getTitle() { public long getTime() { return title; return when.getTime(); } } public void setTitle(String title) { public boolean after(Date other) { this.title = title; return when.after(other); } } // ... // ... JUL 2009 - 86
  87. …Better Design Patterns: Delegate… public Date getWhen() { import java.util.Date; return when; boilerplate } public class Event { private String title; public void setWhen(Date when) { private String url; this.when = when; private Date when; } public String getUrl() { public boolean before(Date other) { return url; return when.before(other); } @ ASERT 2006-2009 } public void setUrl(String url) { public void setTime(long time) { this.url = url; when.setTime(time); } } public String getTitle() { public long getTime() { return title; return when.getTime(); } } public void setTitle(String title) { public boolean after(Date other) { this.title = title; return when.after(other); } } // ... // ... JUL 2009 - 87
  88. …Better Design Patterns: Delegate class Event { String title, url @Delegate Date when } @ ASERT 2006-2009 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) JUL 2009 - 88
  89. Functional Programming in Groovy • Java functional programming goodness – Normal OO methods – Ability to have immutable types – Some concurrency building blocks • Closures for greater flexibility @ ASERT 2006-2009 – Enabler for concurrency • Lazy evaluation with Closures – Built in to some libraries, e.g. data access • Metaprogramming to enhance existing libraries • Third party Java libraries – Functional Java, FunctionalJ, Commons Functor, Scala JUL 2009 - 89
  90. Functional Programming… def fac(n) { n == 0 ? 1 : n * fac(n - 1) } assert 24 == fac(4) // define and use infinite streams @ ASERT 2006-2009 def integers(n) { cons(n, { integers(n+1) }) } def naturalnumbers = integers(1) assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.take(10).join(' ') def evennumbers = naturalnumbers.filter{ it % 2 == 0 } assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.take(10).join(' ') JUL 2009 - 90
  91. …Functional Programming… import fj.data.Stream //some metaprogramming to make fj mesh well with Groovy Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) } Stream.metaClass.getAt = { n -> delegate.index(n) } @ ASERT 2006-2009 Stream.metaClass.getAt = { Range r -> r.collect{ delegate.index(it)}} Stream.metaClass.asList = { -> delegate.toCollection().asList() } def evens = Stream.range(0).filter{ it % 2 == 0 } assert evens.take(5).asList() == [0, 2, 4, 6, 8] assert (8..12).collect{ evens[it] } == [16, 18, 20, 22, 24] assert evens[3..6] == [6, 8, 10, 12] JUL 2009 - 91
  92. …Functional Programming import fj.data.Stream import static fj.data.Stream.cons // some metaprogramming to make Closures mesh well with // fj functions and products Stream.metaClass.filterTail = { Closure c -> delegate.tail()._1().filter(c as fj.F) } Stream.metaClass.static.cons = { h, Closure c -> @ ASERT 2006-2009 delegate.cons(h, ['_1':c] as fj.P1) } def sieve(nums) { cons nums.head(), { sieve nums.filterTail{ n -> n % nums.head() != 0 } } } println sieve(Stream.range(2)).index(100) // => 547 JUL 2009 - 92
  93. Enabling a functional style … • Consider the Maximum Segment Sum (MSS) problem – Take a list of integers; the MSS is the maximum of the sums of any number of adjacent integers • Imperative solution: @ ASERT 2006-2009 def numbers = [31,-41,59,26,-53,58,97,-93,-23,84] def size = numbers.size() def max = null (0..<size).each { from -> (from..<size).each { to -> def sum = numbers[from..to].sum() if (max == null || sum > max) max = sum } } println "Maximum Segment Sum of $numbers is $max"
  94. … Enabling a functional style … • A first attempt at a more functional style: def numbers = [31,-41,59,26,-53,58,97,-93,-23,84] @ ASERT 2006-2009 def size = numbers.size() def max = [0..<size, 0..<size].combinations().collect{ numbers[it[0]..it[1]].sum() }.max() println "Maximum Segment Sum of $numbers is $max"
  95. … Enabling a functional style … • An even more functional style – A known solution using functional composition: mss = max º sum* º (flatten º tails* º inits) – Where inits and tails are defined as follows: @ ASERT 2006-2009 letters = ['a', 'b', 'c', 'd'] assert letters.inits() == [ assert letters.tails() == [ ['a'], ['d'], ['a', 'b'], ['c', 'd'], ['a', 'b', 'c'], ['b', 'c', 'd'], ['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd'] ] ]
  96. … Enabling a functional style • An even more functional style mss = max º sum* º (flatten º tails* º inits) def segs = { it.inits()*.tails().sum() } def solve = { segs(it)*.sum().max() } def numbers = [31,-41,59,26,-53,58,97,-93,-23,84] @ ASERT 2006-2009 println "Maximum Segment Sum of $numbers is ${solve numbers}" Notes: – sum() is one-level flatten in Groovy, flatten() is recursive – Metaprogramming allowed us to enhance all Lists List.metaClass { inits{ (0..<delegate.size()).collect{ delegate[0..it] } } tails{ delegate.reverse().inits() } } Source: http://hamletdarcy.blogspot.com/2008/07/groovy-vs-f-showdown-side-by-side.html
  97. Concurrent Programming in Groovy • Java concurrent programming goodness – Normal OO methods – Ability to have immutable types – Some concurrency building blocks • Process Enhancements – AntBuilder and GDK methods @ ASERT 2006-2009 • Closures for greater flexibility – Enabler for concurrency – Closure is Runnable and Callable • Third party Java libraries – Functional Java (Actors) – Cascading.groovy subproject for Hadoop clusters – Jetlang – GridGain – Groovy actors JUL 2009 - 97
  98. Fibonacci… START = 8 END = 16 fib = {n -> n < 2 ? n : fib(n - 1) + fib(n - 2) } (START..END).each {num -> println "n:$num => ${fib(num)}" } @ ASERT 2006-2009 JUL 2009 - 98
  99. …Fibonacci… import java.util.concurrent.* CUTOFF = 12 // not worth parallelizing for small n THREADS = 100 println "Calculating Fibonacci sequence in parallel..." serialFib = {n -> (n < 2) ? n : serialFib(n - 1) + serialFib(n - 2) } pool = Executors.newFixedThreadPool(THREADS) defer = {c -> pool.submit(c as Callable) } fib = {n -> @ ASERT 2006-2009 if (n < CUTOFF) return serialFib(n) def left = defer { fib(n - 1) } def right = defer { fib(n - 2) } left.get() + right.get() } (8..16).each {n -> println "n=$n => ${fib(n)}" } pool.shutdown() JUL 2009 - 99
  100. GParallelizer... import static org.gparallelizer.actors.pooledActors.PooledActors.* // create a new actor that prints out received messages def console = actor { loop { react {message -> println message } © ASERT 2006-2009 } } // start the actor and send it a message console.start() console.send('Message') JUL 2009 - 100
  101. ...GParallelizer... import java.util.concurrent.Future // multiply numbers asynchronously Asynchronizer.withAsynchronizer(5) { Collection<Future> result = [1, 2, 3, 4, 5].collectAsync{it * 10} assertEquals(new HashSet([10, 20, 30, 40, 50]), © ASERT 2006-2009 new HashSet((Collection) result*.get())) } // run multiple closures in parallel Asynchronizer.withAsynchronizer { assert AsyncInvokerUtil.doInParallel( {calculateA()}, {calculateB()} ) == [10, 20] } JUL 2009 - 101
  102. ...GParallelizer import static org.gparallelizer.dataflow.DataFlow.thread final x = new DataFlowVariable() final y = new DataFlowVariable() final z = new DataFlowVariable() thread { z << ~x + ~y © ASERT 2006-2009 println "Result: ${~z}" } thread { x << 10 } thread { y << 5 } JUL 2009 - 102
  103. Better Testing • Built-in support for: JUnit 3 & 4, TestNG – A test is a first class citizen • Built-in mocking • Useful and Emerging Frameworks – Easyb © ASERT 2006-2009 – WebTest – Selenium – DbUnit – FEST – Spock – GMock JUL 2009 - 103
  104. Built-in JUnit • Groovy distribution includes JUnit • Automatically invokes text runner • Has some useful extensions class GroovyMultiplierJUnit3Test extends GroovyTestCase { .. void testPositives() { @ ASERT 2006-2009 Time: 0.092 def testee = new GroovyMultiplier() OK (2 tests) assertEquals "+ve multiplier error", 9, testee.triple(3) assertEquals "+ve multiplier error", 12, testee.triple(4) } void testNegatives() { def testee = new GroovyMultiplier() assertEquals "-ve multiplier error", -12, testee.triple(-4) } } JUL 2009 - 104
  105. JUnit 4.5+ import org.junit.Test import static org.junit.Assert.assertEquals class ArithmeticTest { @Test void additionIsWorking() { @ ASERT 2006-2009 assertEquals 4, 2+2 } @Test(expected=ArithmeticException) void divideByZero() { println 1/0 } } JUL 2009 - 105
  106. JUnit 4.5+ Parameterized Tests import org.junit.Test import org.junit.Before import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.* @RunWith(Parameterized) class GroovyMultiplierJUnit4Test { @Test void positivesFixed() { def testee assert testee.triple(1) ==3 def param } def expected @ ASERT 2006-2009 @Test void positivesParameterized() { @Parameters static data() { assert testee.triple(param) == expected return (2..4).collect{ } [it, it * 3] as Integer[] } @Test void negativesParameterized() { } assert testee.triple(-param) == -expected } GroovyMultiplierJUnit4Test(a, b) { } param = a expected = b } @Before void setUp() { testee = new GroovyMultiplier() } JUL 2009 - 106
  107. JUnit 4.5+ Theories … import org.junit.runner.* import org.junit.experimental.theories.* import static org.junit.Assume.assumeTrue @RunWith(Theories) class LanguageTheoryTest { @DataPoint public static String java = 'Java' @DataPoint public static String ruby = 'JRuby' @DataPoint public static String python = 'Jython' @ ASERT 2006-2009 @DataPoint public static String javascript = 'Rhino' @DataPoint public static String groovy = 'Groovy' @DataPoint public static String scala = 'Scala' @DataPoint public static String csharp = 'C#' def jvmLanguages = [java, ruby, python, groovy, scala, javascript] def teamSkills = [ tom: [java, groovy, ruby], dick: [csharp, scala, java, python], harry: [javascript, groovy, java] ] ... JUL 2009 - 107
  108. … JUnit 4.5+ Theories ... @Theory void everyoneKnowsJava() { teamSkills.each { developer, skills -> assert java in skills } } @ ASERT 2006-2009 @Theory void teamKnowsEachJvmLanguage(String language) { assumeTrue language in jvmLanguages assert teamSkills.any { developer, skills -> language in skills } } } JUnitCore.main(LanguageTheoryTest.name) JUL 2009 - 108
  109. Better Testing: TestNG import ... public class SimpleTest { @BeforeClass public void setUp() { // code invoked when test is created } © ASERT 2006-2009 @Test(groups = [ "fast" ]) public void aFastTest() { System.out.println("Fast test"); } @Test(groups = [ "slow" ]) public void aSlowTest() { System.out.println("Slow test"); } } JUL 2009 - 109
  110. Better Testing: EasyB • BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' @ ASERT 2006-2009 when 'that flyer completes a segment worth 500 points' then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } JUL 2009 - 110
  111. Better Testing: Spock ... © ASERT 2006-2009 JUL 2009 - 111
  112. ...Better 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 © ASERT 2006-2009 when: pub.send("event") then: 1 * sub1.receive("event") 1 * sub2.receive("event") } } JUL 2009 - 112
  113. Built-in Mocks for Groovy… • Handle statics, explicit constructors, etc. 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 @ ASERT 2006-2009 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 for strict expectation here JUL 2009 - 113
  114. …Built-in Mocks for Groovy import groovy.mock.interceptor.MockFor class MockingTest extends GroovyTestCase { def mock1 = new MockFor(Collaborator1) class Mocking { def mock2 = new MockFor(Collaborator2) def method() { def mock3 = new MockFor(Collaborator3) try { private static final Closure DONTCARE = null new Collaborator1().method1() private static final Closure PASS = {} new Collaborator2().method2() private static final Closure FAIL = { } catch (Exception e) { throw new RuntimeException() } new Collaborator3().method3() void testSuccess() { } check(PASS, PASS, DONTCARE) } @ ASERT 2006-2009 } } void testCollaborator1Fails() { class Collaborator1 {} check(FAIL, DONTCARE, PASS) class Collaborator2 {} } class Collaborator3 {} void testCollaborator2Fails() { check(PASS, FAIL, PASS) } private check(expected1, expected2, expected3){ if (expected1) mock1.demand.method1(expected1) if (expected2) mock2.demand.method2(expected2) if (expected3) mock3.demand.method3(expected3) mock1.use { mock2.use { mock3.use { new Mocking().method() }}} } } JUL 2009 - 114
  115. Better Testing: Mocking with 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, © ASERT 2006-2009 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 JUL 2009 - 115
  116. ...Better Testing: Mocking with Gmock import org.gmock.GMockTestCase class LoaderTest extends GMockTestCase { void testLoader(){ def mockLoader = mock() © ASERT 2006-2009 mockLoader.load('key').returns('value') play { assertEquals "value", mockLoader.load('key') } } } JUL 2009 - 116
  117. WebTest testing Web Sites def ant = new AntBuilder() def webtest_home = System.properties.'webtest.home' ant.taskdef(resource:'webtest.taskdef') { classpath { pathelement(location:"$webtest_home/lib") fileset(dir:"$webtest_home/lib", includes:"**/*.jar") } } @ ASERT 2006-2009 def config_map = [:] ['protocol','host','port','basepath','resultfile', 'resultpath', 'summary', 'saveresponse','defaultpropertytype'].each { config_map[it] = System.properties['webtest.'+it] } ant.testSpec(name:'groovy: Test Groovy Scripting at creation time') { config(config_map) steps { invoke(url:'linkpage.html') for (i in 1..10) { verifyText(description:"verify number ${i} is on pages", text:"${i}") } } } JUL 2009 - 117
  118. WebTest testing Emails def ant = new AntBuilder() def webtest_home = System.properties.'webtest.home' ant.taskdef(resource:'webtest.taskdef'){ classpath(){ pathelement(location:"$webtest_home/lib") fileset(dir:"$webtest_home/lib", includes:"**/*.jar") } } @ ASERT 2006-2009 ant.testSpec(name:'Email Test'){ steps { emailSetConfig(server:'localhost', password:'password', username:'devteam@mycompany.org', type:'pop3') emailStoreMessageId(subject:'/Build notification/', property:'msg') emailStoreHeader(property:'subject', messageId:'#{msg}', headerName:'Subject') groovy('''def subject = step.webtestProperties.subject assert subject.startsWith('Build notification')''') emailMessageContentFilter(messageId:'#{msg}') verifyText(text:'Failed build') } } JUL 2009 - 118
  119. SOAP Client and Server class MathService { double add(double a, double b) { a + b } double square(double c) { c * c } } @ ASERT 2006-2009 import groovy.net.soap.SoapServer def server = new SoapServer('localhost', 6789) server.setNode('MathService') server.start() import groovy.net.soap.SoapClient def math = new SoapClient('http://localhost:6789/MathServiceInterface?wsdl') assert math.add(1.0, 2.0) == 3.0 assert math.square(3.0) == 9.0 JUL 2009 - 119
  120. Better Testing: SoapUI • Tool for testing Web Services has a built- in Groovy editor for custom steps @ ASERT 2006-2009 JUL 2009 - 120
  121. Integration with Spring... // traditional approach using a beans xml file import org.springframework.context.support.ClassPathXmlApplicationContext def ctx = new ClassPathXmlApplicationContext('calcbeans.xml') def calc = ctx.getBean('calcBean') println calc.doAdd(3, 4) // => 7 @ ASERT 2006-2009 // using BeanBuilder def bb = new grails.spring.BeanBuilder() bb.beans { adder(AdderImpl) calcBean(CalcImpl2) { adder = adder } } def ctx = bb.createApplicationContext() def calc = ctx.getBean('calcBean') println calc.doAdd(3, 4) // => 7 JUL 2009 - 121
  122. ...Integration with Spring // using annotations 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 @ ASERT 2006-2009 @Component class CalcImpl3 { @Autowired private AdderImpl adder def doAdd(x, y) { adder.add(x, y) } } import org.springframework.context.support.GenericApplicationContext import org.springframework.context.annotation.ClassPathBeanDefinitionScanner def ctx = new GenericApplicationContext() new ClassPathBeanDefinitionScanner(ctx).scan('') // scan root package for components ctx.refresh() def calc = ctx.getBean('calcImpl3') println calc.doAdd(3, 4) // => 7 JUL 2009 - 122
  123. Dependency Injection: Guice import com.google.inject.* @ImplementedBy(CalculatorImpl) interface Calculator { def add(a, b) } @Singleton class CalculatorImpl implements Calculator { private total = 0 © ASERT 2006-2009 def add(a, b) { total++; a + b } def getTotalCalculations() { 'Total Calculations: ' + total } String toString() { 'Calc: ' + hashCode()} } class Client { @Inject Calculator calc // ... } def injector = Guice.createInjector() // ... JUL 2009 - 123
  124. A taste of Grails • Grails has three properties that increase developer productivity significantly when compared to traditional Java web frameworks: – No XML configuration – Ready-to-use development environment – Functionality available through mixins @ ASERT 2006-2009 JUL 2009 - 124
  125. JUL 2009 - 125 RDBMS, Messaging, Web Services, Security … Grails Model Radeox/FCK POJOs POGOs Wicket GORM JMS/Acegi Captcha Architecture Xfire/REST Grails Controller Java Runtime DWR/GWT Plugins Controllers OpenLaszlo … Groovy Quartz Hibernate JSP Page Java Libraries Grails Views Spring GSP Page Groovlet JDBC/HSQL Jetty HTML/Ajax Browser … Textmate JDK Ant Netbeans Eclipse IntelliJ @ ASERT 2006-2009
  126. Web framework • Controllers class BookController { def list = { [ books: Book.findAll() ] } } grails create-controller @ ASERT 2006-2009 • Views <html> <head> <title>Our books</title> </head> <body> <ul> <g:each it="books"> <li>${it.title} (${it.author.name})</li> </g:each> </ul> </body> </html> JUL 2009 - 126
  127. Persistence • Model grails create-domain-class class Book { Long id Long version String title @ ASERT 2006-2009 Person author } • Methods def book = new Book(title:"The Da Vinci Code", author:Author.findByName("Dan Brown")) book.save() def book = Book.find("from Book b where b.title = ?", [ 'The Da Vinci Code' ]) def books = Book.findAll() def book = Book.findByTitleLike("%Da Vinci%") def book = Book.findWhere(title:"The Da Vinci Code") JUL 2009 - 127
  128. Project Structure + PROJECT_HOME + grails-app Main Grails resources + conf + controllers + domain + i18n @ ASERT 2006-2009 Jar archive libraries + services + taglib + views Additional Spring configuration + lib + spring + hibernate Additional Hibernate mapping + src Java sources + web-app Web resources e.g. CSS, JavaScript etc. JUL 2009 - 128
  129. 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 @ ASERT 2006-2009 – 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 • Books – Several to choose from ... JUL 2009 - 129
  130. More Information: Groovy in Action @ ASERT 2006-2009 Second edition of GinA, ‘ReGinA’ now under development JUL 2009 - 130

+ Paul KingPaul King, 4 months ago

custom

1107 views, 13 favs, 0 embeds more stats

Paul King's latest Groovy Tutorial. Feel free to us more

More info about this document

© All Rights Reserved

Go to text version

  • Total Views 1107
    • 1107 on SlideShare
    • 0 from embeds
  • Comments 3
  • Favorites 13
  • Downloads 68
Most viewed embeds

more

All embeds

less

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

Cancel
File a copyright complaint
Having problems? Go to our helpdesk?

Categories