How Groovy Helps

11,145 views
11,082 views

Published on

An introduction to Groovy for Java developers, showing how a few small problems were solved rather than presenting an exhaustive list of features.

Published in: Business, Technology
0 Comments
16 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
11,145
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
392
Comments
0
Likes
16
Embeds 0
No embeds

No notes for slide

How Groovy Helps

  1. 1. How Groovy Helps Ken Kousen Kousen IT, Inc. http://www.kousenit.com [email_address] http://kousenit.wordpress.com
  2. 2. What is Groovy? <ul><li>From Scott Davis, Groovy Recipes: </li></ul><ul><ul><li>Groovy is what Java would look like had it been written in the 21 st century. </li></ul></ul><ul><ul><li>Groovy is a new breed of language. It doesn’t replace old technology as much as enhances it … this isn’t a “Hey guys, let’s rewrite our application from the ground up” … this is a “Let’s use a language that seamlessly integrates with our existing codebase” approach </li></ul></ul>
  3. 3. Groovy <ul><li>Programming language with new features </li></ul><ul><ul><li>Optional typing </li></ul></ul><ul><ul><li>Closures </li></ul></ul><ul><ul><li>Builders </li></ul></ul><ul><li>Compiles to the JVM </li></ul><ul><ul><li>Compiled, not interpreted </li></ul></ul><ul><ul><li>Interacts transparently with Java </li></ul></ul><ul><ul><ul><li>If you don’t know the Groovy way, use Java </li></ul></ul></ul><ul><ul><li>Scripts as well as classes </li></ul></ul><ul><li>Open source project </li></ul>
  4. 4. Groovy Features <ul><li>Additions and simplifications to Java </li></ul><ul><li>Special features of its own </li></ul><ul><ul><li>Lists and Maps as part of the language </li></ul></ul><ul><ul><li>Everything is an object </li></ul></ul><ul><ul><li>Metaprogramming makes it easy to create Domain Specific Languages </li></ul></ul><ul><ul><ul><li>Like Grails  </li></ul></ul></ul>
  5. 5. Installing Groovy <ul><li>Need a JVM first </li></ul><ul><ul><li>JDK 1.5+ preferred, others still work </li></ul></ul><ul><li>Download Groovy from http://groovy.codehaus.org </li></ul><ul><li>Run installer (Windows) or just unzip and set GROOVY_HOME variable </li></ul><ul><ul><li>Add the bin directory to your path </li></ul></ul><ul><ul><li>That’s all there is to it </li></ul></ul>
  6. 6. Groovy Editors <ul><li>Groovy comes with </li></ul><ul><ul><li>groovysh command line client </li></ul></ul><ul><ul><li>groovyconsole Swing GUI </li></ul></ul><ul><li>Plug-ins for most major IDEs </li></ul><ul><ul><li>Eclipse </li></ul></ul><ul><ul><li>Netbeans </li></ul></ul><ul><ul><li>JetGroovy plugin for IntelliJ IDEA </li></ul></ul><ul><ul><ul><li>Favored by most of the core team members </li></ul></ul></ul>
  7. 7. Three Sample Problems <ul><li>Google Chart is a web service that generates plots based on encoded data </li></ul><ul><ul><li>RESTful web service </li></ul></ul><ul><li>Major League Baseball provides game information online </li></ul><ul><ul><li>XML processing and networking </li></ul></ul><ul><li>Analyze training days data </li></ul><ul><ul><li>Database access, closures, and ranges </li></ul></ul>
  8. 8. Google Chart API <ul><li>The Google Chart API is a web service located at http://code.google.com/apis/chart/ </li></ul><ul><li>So-called “RESTful” web service </li></ul><ul><ul><li>URL with query string to generate response </li></ul></ul><ul><li>Lots of different chart types can be generated </li></ul><ul><li>Data must be “encoded” </li></ul>
  9. 9. Google Chart API <ul><li>Three types of encoding </li></ul><ul><ul><li>Simple encoding uses alphanumeric characters A to Z, a to z, 0 to 9 </li></ul></ul><ul><ul><ul><li>62 unique values </li></ul></ul></ul><ul><ul><li>Text encoding uses floating point numbers 0.0 to 100.0 with a single decimal place </li></ul></ul><ul><ul><ul><li>1000 unique values </li></ul></ul></ul><ul><ul><li>Extended encoding uses pairs of alphanumeric characters </li></ul></ul><ul><ul><ul><li>4096 unique values </li></ul></ul></ul>
  10. 10. Google Chart API <ul><li>Typical request: </li></ul><ul><ul><li>http://chart.apis.google.com/chart? chs=200x125 &chd=s:helloWorld &cht=lc &chxt=x,y &chxl=0:|Mar|Apr|May|June|July|1:||50+Kb </li></ul></ul>
  11. 11. My Application <ul><li>Create pie chart of categories of training classes delivered in 2007 </li></ul><ul><li>To do so </li></ul><ul><ul><li>Iterate over list of courses </li></ul></ul><ul><ul><li>For each course, choose a category and increment the number of courses in that category </li></ul></ul><ul><ul><li>Encode the map into a string required by Google Chart </li></ul></ul><ul><ul><li>Create the required URL and access Google Chart </li></ul></ul>
  12. 12. My Application <ul><li>More important, illustrate various aspects of Groovy programming </li></ul><ul><ul><li>Groovy scripts and classes </li></ul></ul><ul><ul><li>Lists and maps as native data structures </li></ul></ul><ul><ul><li>Regular expressions </li></ul></ul><ul><ul><li>Groovy Strings </li></ul></ul><ul><ul><li>Ranges </li></ul></ul><ul><ul><li>URL creation and access </li></ul></ul><ul><ul><li>Groovy bean properties </li></ul></ul>
  13. 13. Groovy Scripts and Classes <ul><li>Groovy uses classes just as Java does </li></ul><ul><ul><li>Classes assumed to be public </li></ul></ul><ul><li>Groovy also uses scripts </li></ul><ul><ul><li>Scripts become classes internally </li></ul></ul><ul><li>Can mix script code and classes in the same source code file </li></ul>
  14. 14. google_chart.groovy package chart class Chart { def base = 'http://chart.apis.google.com/chart' def url = base + '?cht=p3&chs=500x150' def imageTag // … more code to come … } def chart = new Chart() chart.encodeMap() println chart.imageTag
  15. 15. Interesting Features <ul><li>No semicolons! </li></ul><ul><ul><li>Actually, they’re optional </li></ul></ul><ul><li>Groovy code organized in packages </li></ul><ul><li>Class is public by default </li></ul><ul><ul><li>Fields are private by default </li></ul></ul><ul><ul><li>Methods are public by default </li></ul></ul><ul><li>Optional data typing </li></ul><ul><ul><li>def means untyped </li></ul></ul><ul><ul><li>Can declare a type if you want </li></ul></ul>
  16. 16. Interesting Features <ul><li>Strings can use either single or double quotes </li></ul><ul><ul><li>Single quotes imply regular Strings </li></ul></ul><ul><ul><li>Double quotes are “GStrings” and can do variable replacement </li></ul></ul><ul><li>Script code shown in same file </li></ul><ul><li>Accesses the imageTag property </li></ul><ul><ul><li>Define a property, automatically get a public getter and a public setter method </li></ul></ul>
  17. 17. More Fields def data = [ 'Spring and Hibernate', 'EJB', 'J2EE', 'J2EE', 'Spring', 'XML', 'Spring', 'Spring', 'XML', 'XML', 'J2EE', 'WS', 'Struts', 'Spring', 'JS', 'WS', 'Ajax', 'WS', 'Portlets', 'Hibernate and EJB3', 'J2EE', 'OOAD', 'Java', 'Ajax', 'Ajax', 'Spring', 'Struts 2', 'XML and Java', 'Ajax and Java', 'Securing WS', 'JSF', 'Ajax', 'Ajax and Java', 'Ajax', 'J2EE', 'WS'] def map = [:]
  18. 18. Lists and Maps <ul><li>Lists and Maps are native data structures </li></ul><ul><li>Lists declared using array syntax </li></ul><ul><ul><li>Default implementation is an ArrayList </li></ul></ul><ul><li>Maps are defined using [key:value] </li></ul><ul><ul><li>Default implementation is a HashMap </li></ul></ul>
  19. 19. Constructor Code Chart() { data. each { name -> if (name =~ (/Spring|Hibernate|Struts/)) { addToMap('Open Source') } if (name =~ (/Ajax|JS/)) { addToMap('Ajax') } // … other categories … } }
  20. 20. Constructor Code <ul><li>List’s each method takes a Closure as an argument </li></ul><ul><ul><li>Each element of the list is passed into the closure as the variable name </li></ul></ul><ul><li>If closure is the last argument of a method, can be placed after the method </li></ul><ul><li>Closure is a block of code and the context in which it runs </li></ul><ul><ul><li>Can be assigned to variables, passed into methods, and so on </li></ul></ul>
  21. 21. Constructor Code <ul><li>Regular expression declared inside forward slashes </li></ul><ul><ul><li>Known as “slashy” syntax </li></ul></ul><ul><ul><li>Any string can be defined that way </li></ul></ul><ul><li>The =~ is the “find” operator </li></ul><ul><ul><li>Operator ==~ is the “match” operator </li></ul></ul><ul><ul><li>Operator ~ is the pattern operator </li></ul></ul>
  22. 22. addToMap(label) def addToMap(label) { map[label] = map.get(label,0) + 1 }
  23. 23. addToMap(label) <ul><li>Method signature uses def as return type </li></ul><ul><ul><li>Can return a value, or not </li></ul></ul><ul><li>Dummy argument doesn’t require a type definition </li></ul><ul><li>get(label,0) </li></ul><ul><ul><li>Search for the value under the key label </li></ul></ul><ul><ul><li>Return it if it exists, assign it with the default value 0 if not </li></ul></ul><ul><ul><li>Either way, increment the value and assign it back into the map under the label key </li></ul></ul><ul><li>Can access a value using get method or using array syntax map[label] </li></ul>
  24. 24. addToMap(label) <ul><li>Resulting map of categories is </li></ul><ul><ul><li>[“Open Source”:9, “J2EE”:12, “Web Services”:9, “Ajax”:9, “Other”:1] </li></ul></ul><ul><ul><li>(some courses appear in more than one category) </li></ul></ul>
  25. 25. Encoding <ul><li>Data values are all integers less than 64 </li></ul><ul><ul><li>Can always scale the data if not </li></ul></ul><ul><li>Use the Google Chart simple encoding </li></ul><ul><ul><li>Values from 0 to 25 are the letters A to Z </li></ul></ul><ul><ul><li>Values from 26 to 51 are the letters a to z </li></ul></ul><ul><ul><li>Values from 52 to 61 are the numbers 0 to 9 </li></ul></ul><ul><li>Encode each value, then concatenate the results into a string which will be added as a query parameter </li></ul>
  26. 26. encodeMap() def encodeMap() { List list = (('A'..'Z')+('a'..'z')+(0..9)) as List String s = &quot;&chd=s:&quot; List keys = [] map. each { k,v -> keys << k s += list[v] } // … more to come …
  27. 27. encodeMap() <ul><li>Values separated by two dots are Ranges </li></ul><ul><li>Each range concatenated together </li></ul><ul><li>The as operator “coerces” the Range into a List </li></ul><ul><ul><li>Could also use the toList() method </li></ul></ul><ul><li>The each method iterates over map entries and passes each to the closure </li></ul><ul><ul><li>Closure declares keys as k , values as v </li></ul></ul><ul><ul><li>Each k is appended to the keys list, using the << operator </li></ul></ul><ul><ul><li>Values are used as an index to the range list, which returns the proper encoded value </li></ul></ul>
  28. 28. encodeMap() String labels = &quot;&chl=&quot; + keys. collect { URLEncoder.encode(it,&quot;UTF-8&quot;) }. join ('|') url += s // the query string, defined in the code above url += labels println url // might as well see it String urle = url.replaceAll(/&/,'&amp;') imageTag = &quot;<img src='${urle}' alt='Course distribution'/>&quot;
  29. 29. encodeMap() <ul><li>Lots going on here  </li></ul><ul><li>Need to get the labels for the pie chart </li></ul><ul><ul><li>The labels will be the keys from the map </li></ul></ul><ul><ul><li>Encoding requires the labels to be concatenated into a single string using | as a separator </li></ul></ul><ul><li>The collect method passes each element of a collection through the closure </li></ul><ul><ul><li>Since no dummy variable was defined for the closure this time, the default value “ it ” is used </li></ul></ul><ul><li>The src attribute set as a GString between ${…} </li></ul>
  30. 30. encodeMap() <ul><li>Each label is passed through the standard Java URLEncoder.encode() method </li></ul><ul><ul><li>If you don’t have a specifically Groovy way to do something, use Java </li></ul></ul><ul><li>Total URL is fine, but if it is the argument to the src attribute of an img tag, it can’t include & </li></ul><ul><ul><li>Have to encode & using the entity reference &amp; </li></ul></ul><ul><ul><li>The replaceAll method replaces each string that matches the regular expression with the supplied value </li></ul></ul>
  31. 31. Result <ul><li><img src='http://chart.apis.google.com/chart?cht=p3 &amp;chs=500x150 &amp;chd=s:JMJJB &amp;chl=Open+Source|J2EE|Web+Services|Ajax|Other' alt='Course distribution'/> </li></ul><ul><li>The chart type is p3 (a 3D pie chart) </li></ul><ul><li>The chart size ( chs ) is 500x150 pixels </li></ul><ul><li>The encoded chart data was JMJJB (9, 12, 9, 9, 1) </li></ul><ul><ul><li>The s: indicates the simple encoding scheme </li></ul></ul><ul><li>The chart labels are in the URL encoded string after chl </li></ul>
  32. 32. Result
  33. 33. Google Chart Builder <ul><li>Zan Thrash has created a full builder called the Google Chart Builder </li></ul><ul><li>See his blog posting at http://zanthrash.blogspot.com/2008/01/groovy-chart-builder.html </li></ul><ul><ul><li>Source code is available and instructive </li></ul></ul><ul><li>Grails has a Google Chart plugin at http://grails.org/Google+Chart+Plugin </li></ul><ul><li>Either of those are excellent for real work  </li></ul>
  34. 34. Baseball Data <ul><li>Publicly accessible in XML format </li></ul><ul><li>Base URL is http://gd2.mlb.com/components/game/mlb </li></ul><ul><li>Games are filed by year, month, and day </li></ul><ul><li>Game directory uses team abbreviations </li></ul><ul><ul><li>2007 World Series Game 4: </li></ul></ul><ul><ul><ul><li>year_2007/month_10/day_28/gid_2007_10_28_bosmlb_colmlb_1/boxscore.xml </li></ul></ul></ul>
  35. 35. boxscore.xml <ul><li>Typical boxscore includes </li></ul><ul><ul><li>Root element is <boxscore> </li></ul></ul><ul><ul><li>Child elements </li></ul></ul><ul><ul><ul><li><linescore> </li></ul></ul></ul><ul><ul><ul><li><pitching> for home and away teams </li></ul></ul></ul><ul><ul><ul><ul><li><pitcher> element for each pitcher </li></ul></ul></ul></ul><ul><ul><ul><li><batting> for home and away teams </li></ul></ul></ul><ul><ul><ul><ul><li><batter> element for each batter </li></ul></ul></ul></ul><ul><ul><li>Assorted <note>, <text_data>, <game_info> elements </li></ul></ul>
  36. 36. Java Approach <ul><li>Consider the Java code to access the data </li></ul><ul><ul><li>Open a URL and a URLConnection </li></ul></ul><ul><ul><li>Get a DocumentBuilderFactory </li></ul></ul><ul><ul><li>Get a DocumentBuilder </li></ul></ul><ul><ul><li>Invoke the parse method using an InputStream from the URLConnection </li></ul></ul><ul><ul><li>Either traverse the tree or call getElementByTagName to retrieve the data </li></ul></ul><ul><ul><li>Don’t forget all the try/catch blocks </li></ul></ul>
  37. 37. Groovy Approach <ul><li>Use XmlParser or XmlSlurper </li></ul><ul><ul><li>Very similar classes with mostly the same methods </li></ul></ul><ul><ul><li>Parser returns a list of nodes to navigate </li></ul></ul><ul><ul><li>Slurper returns GPathResults , which are like XPath but groovier </li></ul></ul><ul><li>Just to keep things simple, since the data isn’t massive, we’ll use the parser </li></ul>
  38. 38. GetGameData.groovy class GetGameData { def day def month def year def base = 'http://gd2.mlb.com/components/game/mlb/' // all teams in actual file def abbrevs = [ ana:&quot;Los Angeles (A)&quot;,ari:&quot;Arizona&quot;,atl:&quot;Atlanta&quot;, bal:&quot;Baltimore&quot;,bos:&quot;Boston&quot;,cha:&quot;Chicago (A)&quot;, chn:&quot;Chicago (N)&quot;,cin:&quot;Cincinnati&quot;,cle:&quot;Cleveland&quot;, col:&quot;Colorado&quot;, was:&quot;Washington&quot;]
  39. 39. getGame() void getGame(away, home, num) { println &quot;$away at $home on ${month}/${day}/${year}&quot; def url = base + &quot;year_${year}/month_${month}/day_${day}/&quot; def game = &quot;gid_${year}_${month}_${day}_&quot; + &quot;${away}mlb_${home}mlb_${num}/boxscore.xml&quot; def boxscore = new XmlParser().parse(url + game)
  40. 40. getGame() def awayName = boxscore.'@away_fname' def awayScore = boxscore.linescore[0].'@away_team_runs' def homeName = boxscore.'@home_fname' def homeScore = boxscore.linescore[0].'@home_team_runs' println &quot;$awayName $awayScore, $homeName $homeScore (game $num)&quot;
  41. 41. getGame() def pitchers = boxscore.pitching.pitcher pitchers. each { p -> if (p.'@note' && p.'@note' =~ /W|L|S/) { println &quot; ${p.'@name'} ${p.'@note'}&quot; } }
  42. 42. Interesting Features <ul><li>GStrings simplify string handling </li></ul><ul><ul><li>Java would have to use concatenation to mix strings and variables </li></ul></ul><ul><li>XmlParser has a parse method that takes a URL </li></ul><ul><ul><li>Result is a DOM tree </li></ul></ul><ul><li>Child elements found using dot notation </li></ul><ul><ul><li>boxscore.pitching.pitcher selects all pitchers </li></ul></ul><ul><li>Attribute values use @ notation </li></ul>
  43. 43. Result (so far) bos at col on 10/28/2007 Boston Red Sox 4, Colorado Rockies 3 (game 1) Lester (W, 1-0) Papelbon (S, 3) Cook (L, 0-1)
  44. 44. Process the Data def batters = boxscore.batting.batter for (b in batters) { println &quot;${b.'@name'} went ${b.'@h'} for ${b.'@ab'}&quot; } println batters. size () + &quot; total batters&quot; println 'Total hits: ' + batters.'@h'*. toInteger ().sum()
  45. 45. Notes <ul><li>Groovy prefers for/in loop </li></ul><ul><ul><li>Could use batters.each here instead </li></ul></ul><ul><li>NodeList has size() method </li></ul><ul><ul><li>So does List , String , arrays, etc. </li></ul></ul><ul><li>Spread-dot operator, *. </li></ul><ul><ul><li>Applies function after dot to each element of a list </li></ul></ul>
  46. 46. More Processing println &quot;Batters with at least one hit:&quot; println batters. findAll { it.'@h'. toInteger () > 0 }. collect { it.'@name' + '(' + it.'@h' + ')' } }
  47. 47. List Processing <ul><li>List has a findAll method that takes a closure </li></ul><ul><li>Default reference to each element is “ it ” </li></ul><ul><li>List also has collect method </li></ul><ul><ul><li>Applies closure to each element and returns a list containing the results </li></ul></ul><ul><li>Many other list processing methods available </li></ul><ul><ul><li>each, every, unique, sort, join, flatten, reverse, intersect, +, - </li></ul></ul>
  48. 48. XML Builder <ul><li>Groovy implements Builder pattern </li></ul><ul><ul><li>Uses metaprogramming to intercept “pseudo” method calls </li></ul></ul><ul><li>Builders already included </li></ul><ul><ul><li>NodeBuilder for XML </li></ul></ul><ul><ul><li>AntBuilder for creating Ant scripts </li></ul></ul><ul><ul><li>SwingBuilder for GUIs </li></ul></ul><ul><ul><li>BuilderSupport for creating your own </li></ul></ul>
  49. 49. Create HTML void writeHtml(node) { def away = node.'@away_sname‘ def home = node.'@home_sname' def name = &quot;$away at $home&quot; new File(&quot;${away}${home}.html&quot;). withWriter { w -> def builder = new MarkupBuilder(w)
  50. 50. Notes <ul><li>File has withWriter method that creates a Writer and automatically closes it when done </li></ul><ul><li>MarkupBuilder overloaded to take a Writer </li></ul><ul><ul><li>Default writes to standard out </li></ul></ul>
  51. 51. Building HTML builder.html { head { title name } body { h1 name h2 node.'@date' node.batting. each { batters -> def team = batters.'@team_flag' + '_sname' h2 node.&quot;@${team}&quot;
  52. 52. Notes <ul><li>Looks like HTML </li></ul><ul><li>Each HTML term is intercepted </li></ul><ul><ul><li>Builder then creates XML node with that name </li></ul></ul><ul><li>Closure after node indicates child node </li></ul><ul><ul><li>Argument in parentheses is attribute </li></ul></ul><ul><li>Can freely mix regular Groovy variables and the builder nodes </li></ul>
  53. 53. More HTML table (border:'2') { tr { th 'Batter'; th 'AB'; th 'R'; th 'H' th 'RBI'; th 'BB'; th 'SO' } batters.batter. findAll { it.'@pos' != 'P' }. each { b -> tr { td b.'@name' + ' (' + b.'@pos' + ')' td b.'@ab'; td b.'@r'; td b.'@h' td b.'@rbi'; td b.'@bb'; td b.'@so' } }
  54. 54. Table, continued tr { td(style:'font-weight:bold','Totals') td batters.batter.'@ab'*. toInteger ().sum() td batters.batter.'@r'*. toInteger ().sum() td batters.batter.'@h'*. toInteger ().sum() td batters.batter.'@rbi'*. toInteger ().sum() td batters.batter.'@bb'*. toInteger ().sum() td batters.batter.'@so'*. toInteger ().sum() } }
  55. 55. Results <ul><li>HTML output file </li></ul><ul><li>Generating XML just as easy </li></ul><ul><li>Swing GUIs are fun, too </li></ul>
  56. 56. HTML Boxscore
  57. 57. Screen Scraping <ul><li>Want all games for a given date </li></ul><ul><ul><li>MLB web page lists games in subdirectories, as discussed above </li></ul></ul><ul><ul><li>Can’t just use XML Parser </li></ul></ul><ul><ul><ul><li>HTML not necessarily well-formed </li></ul></ul></ul><ul><ul><li>Without a schedule, don’t know which games played on each day </li></ul></ul><ul><li>Grab page and screen scrape </li></ul>
  58. 58. Games for 5/5/2007
  59. 59. Find Games on Date void getGames() { println &quot;Games for ${month}/${day}/${year}&quot; def url = base + &quot;year_${year}/month_${month}/day_${day}/&quot; def gamePage = new URL(url).text def pattern = /&quot;gid_${year}_${month}_${day}_(w*)mlb_(w*)mlb_(d)/
  60. 60. Notes <ul><li>URL class has getText() method </li></ul><ul><ul><li>Invoked using text property </li></ul></ul><ul><ul><li>Retrieves entire page for processing </li></ul></ul><ul><li>Could use eachLine() method instead </li></ul><ul><ul><li>Processes line by line </li></ul></ul><ul><ul><li>Available in any File </li></ul></ul><ul><ul><li>Works for URLs, too, by duck typing </li></ul></ul><ul><li>Each game in a link with the above pattern </li></ul><ul><ul><li>Also in text, so include the leading quotes to distinguish between the two </li></ul></ul><ul><li>Groups (w*) contain the team abbreviations </li></ul>
  61. 61. Process HTML Page def m = gamePage =~ pattern if (m) { (0..<m. count ).eachWithIndex { line, i -> def away = m[line][1] def home = m[line][2] def num = m[line][3] try { getGame(away,home,num) } catch (Exception e) { println abbrevs[away] + &quot; at &quot; + abbrevs[home] + &quot; not started yet&quot; } } }
  62. 62. Notes <ul><li>Matcher’s count property tells how many matches </li></ul><ul><li>Range (0..<x) is from 0 up to but not including x </li></ul><ul><li>Iterator eachWithIndex available </li></ul><ul><ul><li>Index not actually used here, just shown for illustration </li></ul></ul>
  63. 63. Results Games for 05/05/2007 Boston at Minnesota on 05/05/2007 Boston Red Sox 1, Minnesota Twins 2 (game 1) Tavarez (L, 1-3) Santana (W, 4-2) Nathan (S, 8) Chicago (A) at Los Angeles (A) on 05/05/2007 Chicago White Sox 6, Los Angeles Angels 3 (game 1) Garland (W, 1-2) Jenks (S, 9) Lackey (L, 4-3) …
  64. 64. Database Data <ul><li>Groovy simplifies database access </li></ul><ul><ul><li>groovy.sql.Sql class </li></ul></ul><ul><li>Can do inserts, updates, deletes, and queries </li></ul><ul><li>Includes many convenience methods </li></ul>
  65. 65. Training Data <ul><li>Sample data from training company </li></ul><ul><ul><li>Want to determine teaching days and revenue </li></ul></ul><ul><li>Three tables </li></ul><ul><ul><li>Location </li></ul></ul><ul><ul><ul><li>id, city, state, version (for optimistic locking) </li></ul></ul></ul><ul><ul><ul><li>latitude, longitude (computed from web service) </li></ul></ul></ul><ul><ul><li>Client </li></ul></ul><ul><ul><ul><li>id, name, version </li></ul></ul></ul><ul><ul><li>Course </li></ul></ul><ul><ul><ul><li>id, title, start_date, end_date, rate, version </li></ul></ul></ul><ul><ul><ul><li>location_id FK to Location.id </li></ul></ul></ul><ul><ul><ul><li>client_id FK to Client.id </li></ul></ul></ul>
  66. 66. Training Data <ul><li>Data stored in MySQL database </li></ul><ul><ul><li>Use MySQL JDBC driver </li></ul></ul><ul><li>Note length of course is dependent variable </li></ul><ul><ul><li>Computed from start_date and end_date </li></ul></ul><ul><li>Revenue adding in rate for each day, since course may have calendar gaps </li></ul><ul><li>Lots of annoying Java calendar processing </li></ul>
  67. 67. Side Issues <ul><li>Initial insert into database done by collecting data in spreadsheet </li></ul><ul><ul><li>Can access spreadsheet directly using Scriptom library </li></ul></ul><ul><ul><li>Alternative is to save as CSV and process file </li></ul></ul><ul><li>Need latitude and longitude for each city, state in order to create Google Map </li></ul>
  68. 68. Sample CSV Data 75,5/14/2007,5/18/2007,Java Web Services,Lancaster,PA 76,5/21/2007,5/25/2007,Ajax and Struts,Philadelphia,PA 77,6/4/2007,6/12/2007,Java Web Services,McLean,VA 78,6/25/2007,6/29/2007,Portal Dev. Using RAD6,Enola,PA 80,7/9/2007,7/14/2007,Hibernate EJB3,Atlanta,GA 81,8/23/2007,8/23/2007,J2EE for Managers,Marlborough,CT
  69. 69. Process CSV Data class Record { int num; String startDate; String endDate String title; String city; String state Record(String[] s) { num = s[0].startsWith(&quot;#&quot;) ? 0 : s[0]. toInteger () startDate = s[1]; endDate = s[2] title = s[3]; city = s[4]; state = s[6] } String toString() { &quot;$title ($startDate -- $endDate) $city, $state&quot; } }
  70. 70. Process CSV Data def records = [] try { def dataFile = new File('Course_locations.csv') def lines = dataFile. readLines () for (line in lines) { elements = line.trim().split(',') records << new Record(*elements) } } catch (FileNotFoundException e) { println e } records.each { println it }
  71. 71. GroovyBeans <ul><li>GroovyBeans declare properties </li></ul><ul><ul><li>Put in fields, accessors automatically generated </li></ul></ul><ul><ul><li>(Groovy calls both gets and sets accessors) </li></ul></ul><ul><li>Map constructor generated, too </li></ul><ul><ul><li>new Record(title:”Groovy”,…) assigns value to title, etc. </li></ul></ul>
  72. 72. Notes <ul><li>File has readLines method </li></ul><ul><ul><li>Closure argument applies to each line </li></ul></ul><ul><ul><li>Can use String methods like split and trim </li></ul></ul><ul><li>Spread operator takes list and splits it into input arguments </li></ul><ul><ul><li>Again, not really necessary here since constructor takes an array, but interesting nevertheless </li></ul></ul><ul><li>Note toString method does not require a return keyword </li></ul><ul><ul><li>Value of closure returned by default </li></ul></ul>
  73. 73. Geocoding <ul><li>Geocoders available as web services </li></ul><ul><ul><li>Google has one </li></ul></ul><ul><ul><li>So does the USGS </li></ul></ul><ul><li>Return information as XML or CSV </li></ul>
  74. 74. Using Google Geocoder def key = ‘long_ugly_key' def base = 'http://maps.google.com/maps/geo' def city = 'Marlborough' def state = 'CT' def query = &quot;q=${city},+${state}&output=csv&key=${key}&quot; def url_string = base + '?q=' + ',+' + URLEncoder.encode(city,&quot;UTF-8&quot;) + ',+' + state + &quot;&output=csv&key=${key}&quot; def results = new URL(url_string).text.split(',') def latitude = results[-2].toDouble(); def longitude = results[-1].toDouble(); println &quot;(${latitude},${longitude})&quot; assert &quot;(41.63256,-72.46315) == (${latitude},${longitude})&quot;
  75. 75. Google Geocoder <ul><li>When csv parameter included, result is four strings </li></ul><ul><ul><li>HTTP status code (hopefully 200) </li></ul></ul><ul><ul><li>Accuracy (magnification level) </li></ul></ul><ul><ul><li>Latitude </li></ul></ul><ul><ul><li>Longitude </li></ul></ul><ul><li>Note use in script of indices -1, -2 to grab two elements from the end </li></ul>
  76. 76. USGS Geocoder def partialCsvRequest = &quot;http://geocoder.us/service/csv/geocode?address=&quot; def address = &quot;11 Emily Road, Marlborough, CT&quot; def csvUrl = new URL(partialCsvRequest + URLEncoder.encode(address)) def csvResponse = csvUrl.text def tokens = csvResponse.split(&quot;,&quot; ) println &quot;Latitude: [${tokens[0]}]&quot; println &quot;Longitude: [${tokens[1]}]&quot; println &quot;Address: [${tokens[2]}]&quot; println &quot;City: [${tokens[3]}]&quot; println &quot;State: [${tokens[4]}]&quot; println &quot;Zip: [${tokens[5]}]&quot;
  77. 77. TrainingCourse class TrainingCourse { String title; Date startDate; Date endDate; double rate String toString() { def nf = NumberFormat.currencyInstance &quot;(${title},(${startDate} - ${endDate}),${nf.format(rate)})&quot; } int getLength() { (startDate..endDate). size () } double getRevenue() { length * rate } }
  78. 78. Notes <ul><li>Can provide get methods as necessary </li></ul><ul><li>Dates are a range </li></ul><ul><ul><li>now..then </li></ul></ul><ul><ul><li>size() returns number of days </li></ul></ul><ul><ul><li>Can add, subtract days </li></ul></ul>
  79. 79. CourseProcessor.groovy class CourseProcessor { def courses = [] def courseYearMap = new TreeMap() def cal = Calendar.instance CourseProcessor() { def USER = ‘xxx‘; def PASS = ‘xxx’; def URL = ‘xxx‘; def DRIVER = ‘xxx' def conn = Sql.newInstance(URL,USER,PASS,DRIVER) conn.eachRow('select * from course') { row -> courses << new TrainingCourse( title:row.title,startDate:row.start_date, endDate:row.end_date,rate:row.rate) }
  80. 80. Continuing… courses. each { c -> cal.time = c.startDate def year = cal.get(Calendar.YEAR) courseYearMap[year] = courseYearMap.get(year,[]) + c } }
  81. 81. Notes <ul><li>Want to sort courses by year </li></ul><ul><ul><li>Current approach is to add them to a map </li></ul></ul><ul><ul><li>Using TreeMap instead of [:] means courses automatically sorted by key on add </li></ul></ul><ul><ul><li>Map contains </li></ul></ul><ul><ul><ul><li>Years as keys </li></ul></ul></ul><ul><ul><ul><li>List of courses as values </li></ul></ul></ul>
  82. 82. Printing Courses def printYearMap() { def nf = NumberFormat.currencyInstance courseYearMap. each { year,courseList -> def monthlyDays = new int [12] def monthlyRev = new double [12] courseList. each { course -> cal.time = course.startDate def month = cal.get(Calendar.MONTH) for (date in course.startDate..course.endDate) { monthlyDays[month] += 1 monthlyRev[month] += course.rate } }
  83. 83. Continuing def days = (courseList*.length).sum() def rev = (courseList*.revenue).sum() println &quot;Totals for $year : ${days} days, ${nf.format(rev)}&quot;
  84. 84. Testing <ul><li>Testing </li></ul><ul><ul><li>assert keyword used everywhere </li></ul></ul><ul><ul><li>GroovyTestCase extends JUnit TestCase </li></ul></ul><ul><ul><ul><li>assertArrayEquals </li></ul></ul></ul><ul><ul><ul><li>assertContains(int, int[]) </li></ul></ul></ul><ul><ul><ul><li>assertLength </li></ul></ul></ul><ul><ul><ul><li>assertScript(String script) </li></ul></ul></ul><ul><ul><ul><ul><li>Asserts script runs without exceptions </li></ul></ul></ul></ul><ul><ul><ul><li>shouldFail(Closure) assert closure fails when run </li></ul></ul></ul>
  85. 85. Testing <ul><li>Can execute any GroovyTestCase as regular Groovy script </li></ul><ul><ul><li>Runs as though it has main method </li></ul></ul><ul><li>Mocks and Stubs built in, too </li></ul>
  86. 86. Miscellaneous <ul><li>I see I’ve left out lots of cool topics </li></ul><ul><ul><li>Safe dereferencing operator ?. </li></ul></ul><ul><ul><ul><li>a?.b?.c checks for null a and b </li></ul></ul></ul><ul><ul><li>Templates look like JSPs, but run with Groovy scripts </li></ul></ul><ul><ul><ul><li>Will discuss with Grails </li></ul></ul></ul><ul><ul><li>Metaprogramming (the red pill) </li></ul></ul><ul><ul><ul><li>Great for creating DSLs </li></ul></ul></ul><ul><ul><li>Heredocs </li></ul></ul><ul><ul><ul><li>def doc = '''This is a multiline script written over several lines''‘ </li></ul></ul></ul><ul><ul><li>Grails! </li></ul></ul>
  87. 87. References <ul><li>Groovy home is http://groovy.codehaus.org </li></ul><ul><ul><li>Lots of info, plug-ins, details </li></ul></ul><ul><ul><li>Mailing lists at http://groovy.codehaus.org/Mailing+Lists </li></ul></ul><ul><li>News portal at http://aboutgroovy.com </li></ul><ul><li>Lots of samples at http://pleac.sourceforge.net/pleac_groovy/ </li></ul>

×