{"id":338,"date":"2009-04-19T17:16:01","date_gmt":"2009-04-20T02:16:01","guid":{"rendered":"https:\/\/www.kellyrob99.com\/blog\/?p=338"},"modified":"2009-05-02T21:47:27","modified_gmt":"2009-05-03T06:47:27","slug":"groovy-and-glazed-lists-with-grape","status":"publish","type":"post","link":"https:\/\/www.kellyrob99.com\/blog\/2009\/04\/19\/groovy-and-glazed-lists-with-grape\/","title":{"rendered":"Groovy and Glazed Lists with Grape"},"content":{"rendered":"<p>So today while I was cleaning out the garage, I finally got a chance to the listen to the <a href=\"http:\/\/javaposse.com\/\">Java Posse<\/a> podcast on <a href=\"http:\/\/javaposse.com\/index.php?post_id=451742\">Google Collections and GlazedLists<\/a>. Google Collections has come in handy a few times, the MultiMap extension most notably. It solves essentially the same kind of problem that I described way back in <a href=\"https:\/\/www.kellyrob99.com\/blog\/2009\/02\/08\/my-favorite-new-groovy-trick\/\">this little Groovy post<\/a>.<\/p>\n<p>And recently I had read an article about GlazedLists over on <a href=\"http:\/\/www.jroller.com\/aalmiray\/entry\/glazedlists_groovy_not_your_regular\">Andres&#8217; blog<\/a>, so it was great to get some more context around them, especially on how they can greatly simplify development of responsive and scalable Swing tables.  It inspired me enough to visit their <a href=\"http:\/\/publicobject.com\/glazedlistsdeveloper\/\">developer tutorials page<\/a> and spend an hour watching screencasts. The examples are clear and concise, and the developer, James Lemieux, does a fantastic job of explaining how the different pieces in the GlazedList architecture fit together. If you have a spare hour, well worth watching.<\/p>\n<p>So in the spirit of <a href=\"https:\/\/www.kellyrob99.com\/blog\/2009\/04\/14\/groovy-and-bash-can-scripting-get-much-easier\/\">my last post<\/a>, it occurred to me how to put this to use on the command line as an example.  I chose to do a quick and dirty script for sorting a subset of the results from an &#8216;svn st&#8217; command run in the current directory.  Not terribly useful in its present form, but I think it shows that with a little more effort, you can add some handy utility methods when consuming command line output &#8211; namely sorting.<\/p>\n<p>Essentially this is a demonstration of creating a table binding dynamically and baking automatic sorting across multiple columns right into the model. Grape is used to incorporate the GlazedLists functionality, but otherwise all of the dependencies required are available in Groovy 1.6.1. So you can run this directly on the command line of a *nix machine(execute permissions required of course) with very little additional setup. I did have an initial problem with Grape being unable to resolve the GlazedList dependency, but through the magic of Google I found <a href=\"http:\/\/www.nabble.com\/Unable-to-@Grab-http-builder-td22837819.html\">this article<\/a> which explains how to configure repositories using a grapeConfig.xml file and the problem went away. With no further ado, here&#8217;s the entire code.<\/p>\n<pre class=\"brush: groovy; smart-tabs: true; title: ; notranslate\" title=\"\">\r\n#!\/usr\/bin\/env groovy\r\nimport ca.odell.glazedlists.*\r\nimport ca.odell.glazedlists.gui.*\r\nimport ca.odell.glazedlists.swing.*\r\nimport groovy.swing.*\r\nimport javax.swing.*\r\n\r\n@Grab (group = 'net.java.dev.glazedlists', module = 'glazedlists_java15', version = '1.8.0')\r\npublic class SVNGlazedListExample\r\n{\r\n\tpublic static void main(args)\r\n\t{\r\n\t\tdef columnNames = &#x5B;'Code', 'Revision', 'Filename']\r\n\t\tdef sortedRevisions\r\n\r\n\t\t\/\/execute on the command line and populate buffers with the output\r\n\t\tdef sout = new StringBuffer()\r\n\t\tdef serr = new StringBuffer()\r\n\t\tdef Process process = &quot;svn stat -u&quot;.execute(&#x5B;], new File('.'))\r\n\t\tprocess.consumeProcessOutput(sout, serr)\r\n\t\tprocess.waitFor()\r\n\r\n\t\tSwingBuilder builder = new SwingBuilder();\r\n\r\n\t\t\/**\r\n\t\t * closure returns a JComponent showing the buffered output\r\n\t\t *\/\r\n\t\tdef getTableModel = {out, err -&gt;\r\n\t\t\tif (err)      \/\/Groovy truth FTW\r\n\t\t\t{\r\n\t\t\t\t\/\/create error output table\r\n\t\t\t\treturn builder.textPane(size: &#x5B;500, 500], text: err)\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t\/\/create three column table\r\n\t\t\t\tdef lines = out.toString().split('\\n')\r\n\t\t\t\tdef data = &#x5B;]\r\n\t\t\t\tlines.each() {\r\n\t\t\t\t\tdef tokens = it.tokenize()  \/\/Groovy add which abstract away spaces vs tabs\r\n\t\t\t\t\t\/\/a hack to be sure, but let's just get trebles for now\r\n\t\t\t\t\tif (tokens.size() == 3)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/rows in the data are just a map\r\n\t\t\t\t\t\tdata &lt;&lt; &#x5B;code: tokens&#x5B;0], revision: tokens&#x5B;1], filename: tokens&#x5B;2]]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\/\/passing in null gives us default String comparator\r\n\t\t\t\tsortedRevisions = new SortedList(new BasicEventList(data),null)\r\n\r\n\t\t\t\t\/\/this little trick courtesy of Andres Almiray - Thanks!\r\n\t\t\t\tfinal EventTableModel model = new EventTableModel(sortedRevisions, &#x5B;\r\n\t\t\t\t\t\tgetColumnCount: {columnNames.size()},\r\n\t\t\t\t\t\tgetColumnName: {index -&gt; columnNames&#x5B;index]},\r\n\t\t\t\t\t\tgetColumnValue: {object, index -&gt;\r\n\t\t\t\t\t\t\tobject.&quot;${columnNames&#x5B;index].toLowerCase()}&quot;\r\n\t\t\t\t\t\t}] as TableFormat)\r\n\r\n\t\t\t\treturn builder.table(id: 'revisions', model: model)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\/\/the 'show it' code\r\n\t\tbuilder.frame(title: 'SVN Change List', size: &#x5B;500, 500], pack: true, show: true,\r\n\t\t\t\tdefaultCloseOperation: WindowConstants.DISPOSE_ON_CLOSE) {\r\n\t\t\tscrollPane {\r\n\t\t\t\tdef component = getTableModel(sout, serr)\r\n\t\t\t\t\/\/I'm sure this could be more elegant\r\n\t\t\t\tif(component instanceof JTable){\r\n\t\t\t\t\tdef tableSorter = new TableComparatorChooser(revisions,\r\n\t\t\t\t\t\tsortedRevisions, AbstractTableComparatorChooser.MULTIPLE_COLUMN_MOUSE)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n<\/pre>\n<p>UPDATE: A long time coming, but here&#8217;s a screenshot for you Sho.<br \/>\n<a href=\"https:\/\/i0.wp.com\/www.kellyrob99.com\/blog\/wp-content\/uploads\/2009\/04\/svnglazedlistexamplescreen.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.kellyrob99.com\/blog\/wp-content\/uploads\/2009\/04\/svnglazedlistexamplescreen.png?resize=534%2C522&#038;ssl=1\" alt=\"svnglazedlistexamplescreen\" title=\"svnglazedlistexamplescreen\" width=\"534\" height=\"522\" class=\"alignnone size-full wp-image-368\" srcset=\"https:\/\/i0.wp.com\/www.kellyrob99.com\/blog\/wp-content\/uploads\/2009\/04\/svnglazedlistexamplescreen.png?w=534&amp;ssl=1 534w, https:\/\/i0.wp.com\/www.kellyrob99.com\/blog\/wp-content\/uploads\/2009\/04\/svnglazedlistexamplescreen.png?resize=300%2C293&amp;ssl=1 300w\" sizes=\"auto, (max-width: 534px) 100vw, 534px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>So today while I was cleaning out the garage, I finally got a chance to the listen to the Java Posse podcast on Google Collections and GlazedLists. Google Collections has come in handy a few times, the MultiMap extension most notably. It solves essentially the same kind of problem that I described way back in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[6],"tags":[66,65,64,60,62,61,257,258,59,63,35,18],"class_list":["post-338","post","type-post","status-publish","format-standard","hentry","category-dev","tag-nix","tag-command-line","tag-glazedlist","tag-google","tag-google-collections","tag-grape","tag-groovy","tag-java","tag-javaposse","tag-multimap","tag-programming","tag-swing"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/prjtg-5s","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/posts\/338","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/comments?post=338"}],"version-history":[{"count":23,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/posts\/338\/revisions"}],"predecessor-version":[{"id":367,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/posts\/338\/revisions\/367"}],"wp:attachment":[{"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/media?parent=338"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/categories?post=338"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kellyrob99.com\/blog\/wp-json\/wp\/v2\/tags?post=338"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}