Thursday, November 05, 2009

Griffon Plugins and Addons

In this post, I'm going to talk a bit about Griffon plugins and addons--what they are, why they're useful, and how you can create your own.  As an example, I'll walk through the steps used to create the recently released Griffon Mail Plugin.

What are Plugins and Addons?

  • Plugins provide a mechanism to extend your Griffon application with new functionality at build-time.  They are commonly used to integrate external libraries, add new builders, and support testing tools.  The full list of Griffon plugins is available here.
  • Addons are a new feature in Griffon 0.2 that provide mechanism to extend your Griffon application with new functionality at run-time.  Addons can add items to builders, add MVC groups, and add/respond to runtime events.  An example of this is the Guice plugin, which also bundles an addon that starts up the Guice framework and injects services into your controllers as they are created.  Another example is the REST plugin which uses and addon to add new methods to controllers as they are created.
Plugins and Addons are conceptually very similar.  Both are about adding new functionality and you will often see plugins and addons on working hand in hand.

Creating your own Plugins and Addons
Creating your own Griffon plugins and addons is not too difficult.  We'll walk through the process by re-creating the Griffon mail plugin.  So without further ado, lets get started:

Step 1: Create the plugin
griffon create-plugin mail

Step 2: Edit the plugin metadata
cd mail
edit MailGriffonPlugin.groovy which Griffon created for you:
class MailGriffonPlugin {
    def version = 0.1
    def canBeGlobal = false
    def dependsOn = [:]

    // TODO Fill in your own info
    def author = "Josh Reed"
    def authorEmail = "jareed@andrill.org"
    def title = "Send email from your Griffon app"
    def description = 'Send email from your Griffon app'

    // URL to the plugin's documentation
    def documentation = "http://griffon.codehaus.org/Mail+Plugin"
}

Step 3: Add our plugin functionality
What you do here will depend on your plugin, but in this case we need to add the mail.jar file from the JavaMail API site to the plugins lib/ directory.  When the user installs our plugin, this jar file will be added to the application's classpath.

The JavaMail API requires a bit of work to send emails, so we'll also add a helper class that hides this code behind a single static method call.

Step 4: Package your plugin so you can test it
When you're done with your plugin, you will want to test it out.  To do this:
griffon package-plugin
This will compile your plugin and package it as a zip file called griffon-mail-0.1.zip.  You can then install this plugin in a regular Griffon application with a:
griffon install-plugin griffon-mail-0.1.zip

For the mail plugin, we can send an email by calling our helper class:
MailService.sendMail(mailhost: 'smtp.server.com', to: 'jareed@andrill.org'...)
from our controller or wherever.  This is OK but we can do one better by creating an Addon that injects the sendMail method into every controller.

Step 5: Creating an Addon
Head back to our plugin directory and issue a:
griffon create-addon mail
this will create a MailGriffonAddon.groovy file.
(Note: if you try this with 0.2 you may run into an error about 'createMVC'  This is a bug that has been fixed, but you can fix it yourself by editing your $GRIFFON_HOME/scripts/CreateAddon.groovy file.  The last line should say 'createAddon' instead of 'createMVC')

This is where we do our runtime magic:
import griffon.util.IGriffonApplication

class MailGriffonAddon {
    private IGriffonApplication application
 
    // add ourselves as a listener so we can respond to events
    def addonInit = { app ->
        application = app
        app.addApplicationEventListener(this)
    }
 
    // inject the 'sendMail' into our 
    def onNewInstance = { klass, type, instance ->
        def types = application.config.griffon?.mail?.injectInto ?: ["controller"]
        if (!types.contains(type)) return
        instance.metaClass.sendMail = { Map args -> MailService.sendMail(args)}
    }
}

With this done, we can: griffon package-plugin and install the plugin into our test app: griffon install-plugin griffon-mail-0.1.zip
With the addon in place, we can reference the sendMail() method directly on our controller.

Step 6: Release the plugin
If you have write access to the Griffon Subversion, you can release your plugin with a:
griffon release-plugin
If you don't have write access, ask on the griffon-dev list and someone can help you out.

I hope this demystifies the plugin and addon building process.

Wednesday, October 14, 2009

Multi-Document Griffon Applications

In this post we're going to build the skeleton of a multi-document (tabbed) Griffon application.  Tabbed interfaces are extremely common.  You're probably even viewing this in a web browser with a few open tabs as we speak. We'll be implementing the document-handling logic, so all you will need to do is add your own application-specific logic.

Let's dive in by creating a new Griffon application and creating a Document MVC group:
griffon create-app MultiDocumentExample
cd MultiDocumentExample
griffon create-mvc Document

In our application model, MultiDocumentExampleModel.groovy, we need to add some properties to keep track of open and active documents. We also need a separate 'state' class (more on that later):
import groovy.beans.Bindable

class MultiDocumentExampleModel {
    @Bindable Map activeDocument = null
    List openDocuments = []
    DocumentState state = new DocumentState()
}

@Bindable class DocumentState {
    boolean isDirty = false
}

Next up is the view: MultiDocumentExampleView.groovy. For this minimal example, we'll define some common actions and a tabbed pane for displaying the documents, but you can customize it to suit your application:
actions {
    action(
        id: 'newAction', 
        name:'New', 
        accelerator: shortcut('N'), 
        closure: controller.newAction
    )
    action(
        id: 'openAction', 
        name:'Open', 
        accelerator: shortcut('O'), 
        closure: controller.openAction
    )
    action(
        id: 'closeAction', 
        name:'Close', 
        accelerator: shortcut('W'), 
        closure: controller.closeAction, 
        enabled: bind { model.activeDocument != null }
    )
    action(
        id: 'saveAction', 
        name:'Save', 
        accelerator: shortcut('S'), 
        closure: controller.saveAction, 
        enabled: bind { model.state.isDirty }
    )
}

application(title: 'Multiple Document Example',
    size:[800,600], 
    //pack:true,
    //location:[50,50],
    locationByPlatform:true,
    iconImage: imageIcon('/griffon-icon-48x48.png').image,
    iconImages: [imageIcon('/griffon-icon-48x48.png').image,
        imageIcon('/griffon-icon-32x32.png').image,
        imageIcon('/griffon-icon-16x16.png').image],
    defaultCloseOperation: 0,
    windowClosing: { evt -> if (controller.canClose()) { app.shutdown() } }
) {
    menuBar(id: 'menuBar') {
        menu(text: 'File', mnemonic: 'F') {
            menuItem(newAction)
            menuItem(openAction)
            menuItem(closeAction)
            menuItem(saveAction)
        }
    }
 
    tabbedPane(id: 'documents', stateChanged: { evt -> 
        controller.activeDocumentChanged() 
    })
}

If you look close, you'll see I'm using the window closing trick I blogged awhile ago. The last piece of the puzzle is the controller: MultiDocumentExampleController.groovy. This is where we do the heavy lifting.
class MultiDocumentExampleController {
    int count = 0  // not strictly required
    def model
    def view

    void mvcGroupInit(Map args) {}

    void activeDocumentChanged() {
        // de-activate the existing active document
        if (model.activeDocument) { 
            model.activeDocument.controller.deactivate() 
        }

        // figure out the new active document
        int index = view.documents.selectedIndex
        model.activeDocument = index == -1 ? null : model.openDocuments[index]
        if (model.activeDocument) { 
            model.activeDocument.controller.activate(model.state) 
        }
    }
 
    boolean canClose() {
        return model.openDocuments.inject(true) { flag, doc -> 
            if (flag) flag &= doc.controller.close() }
    }

    def newAction = { evt = null ->
        // TODO: use an id that makes sense for your application, like a file name
        String id = "Document " + (count++) 
  
        def document = buildMVCGroup('Document', id, 
            tabs: view.documents, id: id, name: id /* TODO pass other args */)
        if (document.controller.open()) {
            model.openDocuments << document
            view.documents.addTab(document.model.name, document.view.root)
            view.documents.selectedIndex = view.documents.tabCount - 1
        } else {
            destroyMVCGroup(id)
        }
    }
 
    def openAction = { evt = null ->
        // TODO: pop up a open file dialog or whatever makes sense

        // TODO: use an id that makes sense for your application, like a file name
        String id = "Document" + (count++)

        // check to see if the document is already open
        def open = model.openDocuments.find { it.model.id == id }
        if (open) {
            view.documents.selectedIndex = model.openDocuments.indexOf(open)
        } else {
            def document = buildMVCGroup('Document', id, 
                tabs: view.documents, id: id, name: id /* TODO pass other args */)
            if (document.controller.open()) {
                model.openDocuments << document
                view.documents.addTab(document.model.name, document.view.root)
                view.documents.selectedIndex = view.documents.tabCount - 1
            } else {
                destroyMVCGroup(id)
            }
        }
    }
 
    def saveAction = { evt = null ->
        model.activeDocument.controller.save()
    }
 
    def closeAction = { evt = null ->
        if (model.activeDocument.controller.close()) {
            int index = model.openDocuments.indexOf(model.activeDocument)
            model.openDocuments.remove(index)
            view.documents.removeTabAt(index)
        }
    }
}

There's a fair amount of code but it's all pretty straightforward.  The only thing that warrants further discussion is activeDocumentChanged().  This method is called whenever the active tab in the tabbed pane is changed.  In it, we deactivate() the previously active document and then activate() the new document.  When activating, we pass in the state object I mentioned earlier.  The purpose of the state object is to allow us to use bindings that depend on the active document.  For example, we really only want the save action to be enabled when we have a document open and the document is dirty.  We can't bind directly to the active document's model because as soon as we open or switch to a new document, the binding is no longer correct.  We can, however, bind to the state object and then sync the document's state in the activate() method (or other methods).

The implementation of the Document MVC group is straightforward:
DocumentModel.groovy
import groovy.beans.Bindable

@Bindable class DocumentModel {
    String id
    String name = "Untitled"
    boolean isDirty = false
    DocumentState state
}

DocumentView.groovy
panel(id: 'root') {
    // TODO: put your view implementation here
    button(text: 'Mark Dirty', enabled: bind { !model.isDirty }, 
        actionPerformed: { controller.markDirty() })
    button(text: 'Mark Clean', enabled: bind { model.isDirty }, 
        actionPerformed: { controller.markClean() })
}


DocumentController.groovy
import javax.swing.JOptionPane

/**
 * A skeleton controller for a 'document'.
 */
class DocumentController {
    def model
    def view
    def tabs

    void mvcGroupInit(Map args) {
        tabs = args.tabs
    }

    /**
     * Called when the tab with this document gains focus.
     */
    void activate(state) {
        // save the state object so we can signal the 
        model.state = state
  
        // TODO: sync the model and document state
        state.isDirty = model.isDirty
    }
 
    /**
     * Called when the tab with this document loses focus.
     */
    void deactivate() {
        // forget the state object
        model.state = null
    }
 
    /**
     * Called when the document is initially opened.
     */
    boolean open() {
        // TODO: perform any opening tasks
        return true 
    }
 
    /**
     * Called when the document is closed.
     */
    boolean close() {
        if (model.isDirty) {
            switch (JOptionPane.showConfirmDialog(app.appFrames[0], 
                    "Save changes to '${model.name}'?", "Example", 
                    JOptionPane.YES_NO_CANCEL_OPTION)){
                case JOptionPane.YES_OPTION: return save()
                case JOptionPane.NO_OPTION: return true
                case JOptionPane.CANCEL_OPTION: return false
            }
        }
        return true
    }
 
    /**
     * Called when the document is saved.
     */
    boolean save() {
        // TODO: perform any saving tasks
        markClean()
        return true
    }
 
    /**
     * Marks this document as 'dirty'
     */
    void markDirty() {
        model.isDirty = true
        if (model.state) { model.state.isDirty = true }
        // TODO: update any other model/state properties
        setTitle(model.name + "*")
    }
 
    /**
     * Marks this document as 'clean'
     */
    void markClean() {
        model.isDirty = false
        if (model.state) { model.state.isDirty = false }
        // TODO: update any other model/state properties
        setTitle(model.name)
    }
 
    /**
     * Sets this document's tab title.
     */
    void setTitle(title) {
        int index = tabs.indexOfComponent(view.root)
        if (index != -1) {
            tabs.setTitleAt(index, title)
        }
    }
}

So there it is. You should be able to use this as a starting place for your next Griffon-based text editor, web browser, etc.  I'm personally using in PSICAT to manage the open diagrams:


I zipped up all of the code from this post and made it available for download.

Monday, October 12, 2009

Griffon Tip: Silly SwingBuilder Tricks

Here's two quick tips for working with radio buttons in SwingBuilder.  Radio buttons are created using SwingBuilder's radioButton() method.  To ensure only one is selected at a time, you must also associate them with a button group.   Ideally SwingBuilder would allow you to nest your radioButton()s inside a buttonGroup(), e.g.
buttonGroup() {
    // doesn't work
    radioButton(text: 'Option 1')
    radioButton(text: 'Option 2')
}

Unfortunately this doesn't work as buttonGroup() does not support nesting.  Marc Hedlund stumbled across this same issue and offers up one solution.  I like his solution but it means creating a separate variable to hold the button group.  I've found a bit nicer way to do it using Groovy's with keyword:
buttonGroup().with {
    add radioButton(text: 'Option 1')
    add radioButton(text: 'Option 2')
}

The advantage of this approach is that it conveys our intention better and we don't have to instance extra variables.

EDIT: the 'with' also passes in the created object so you could take it a step further and replace the add as well:
buttonGroup().with { group ->
    radioButton(text: 'Option 1', buttonGroup: group)
    radioButton(text: 'Option 2', buttonGroup: group)
}

As a bonus, here's how you can use mutual binding to keep a boolean in your model in sync with the radio buttons.  Let's say your model looks something like so:
class DemoModel {
    ...
    @Bindable boolean option1 = true
    ...
}

Using mutual binding, you can keep the model synced up without any explicit management code:
buttonGroup().with {
    add radioButton(text: 'Option 1', selected: bind(source: model, sourceProperty: 'option1', mutual:true))
    add radioButton(text: 'Option 2', selected: bind { !model.option1 })
}

Obviously this will only work for cases where you have an either-or choice.

Tuesday, October 06, 2009

Griffon Tip: MVC Groups Revisited

My last post about MVC groups caused a bit of confusion, so I thought I'd follow up with a bit of clarification and, as a bonus, I'll also throw in a new trick for working with MVC groups.

MVC Groups for Dialogs
The withMVC method I introduced in the previous post was meant to help manage the lifecycle of short-lived MVC groups, such as those used in simple dialogs.  In cases like this, the MVC group only has to exist for as long as the dialog is visible.  withMVC was very much inspired by File#withInputStream which handles opening and closing of the input stream like withMVC handles creating and destroying of the MVC group.  Below is a complete example of showing an MVC group as a dialog:

NewProjectWizardModel.groovy
@Bindable class NewProjectWizardModel {
    String name
    String filePath
}

NewProjectWizardView.groovy
panel(id:'options', layout: new MigLayout('fill'), border: etchedBorder()) { 
    // project name
    label('Name:')
    textField(text: bind(source: model, sourceProperty:'name', mutual:true), constraints: 'growx, span, wrap')
    
    // directory
    label('Directory:')
    textField(text: bind(source: model, sourceProperty:'filePath', mutual:true), constraints:'width min(200px), growx')
    button(action: browseAction, constraints: 'wrap')
}

NewProjectWizardController.groovy
class NewProjectWizardController {
    def model
    def view

    void mvcGroupInit(Map args) {}

    def actions = [
        browse': { evt = null ->
            def file = Dialogs.showSaveDirectoryDialog("New Project", null, app.appFrames[0])
            if (file) { 
                model.filePath = file.absolutePath
                if (!model.name) { model.name = file.name }
            }
        }
    ]

    def show() {
        if (Dialogs.showCustomDialog("Create New Project", view.options, app.appFrames[0])) {
            // perform some logic
        }
    }
}

Dialogs.showCustomDialog() just uses JOptionPane to show a custom dialog with a component from the view.  I can show this dialog in response to a menu option, a button click, or whatever with the snippet from last post:
withMVC('NewProjectWizard', 'newProject', [:]) { mvc ->
    def project = mvc.controller.show()
    if (project) {
        // do something
    }
}

Hopefully that's a bit more clear and shows how MVC groups can be used as dialogs.  Now let's look at another use of MVC groups: embedding them as components in other views.

Embedding MVC Groups in Views
MVC groups work well as dialogs but they can also be embedded directly in the views of other MVC groups.  This is useful because it allows you to create re-usable components that you can embed in your views as needed.  Embedding is relatively straightforward:
widget(id:'sidePanel', buildMVCGroup('Project', 'project').view.root)

We build the MVC group and grab a component from the view (in this case it was called 'root' but it could be any id).  If we need to access something in the group, we can reference it by id:
def model = app.models['id']
def view = app.views['id']
def controller = app.controllers['id']

Hopefully these tips help you use MVC groups a bit more effectively and bring a bit more modularity to your Griffon app.

Monday, October 05, 2009

Griffon Tip: MVC Groups

Recently I've been doing some refactoring of PSICAT to make better use of Griffon's MVC Groups.  I like MVC Groups because they provide self-contained building blocks that you can re-use throughout your application.  Below are a few tips that might be useful to others:

Creating MVC Groups
MVC Groups are created with the 'griffon create-mvc ' command.  This creates the appropriate files in the griffon-app directory structure and registers the MVC Group in Application.groovy.

When you want to use the new MVC group in your application, you have two ways to instantiate it: createMVCGroup and buildMVCGroup.  Both methods instantiate the MVC group but differ in the ordering of parameters passed to them and in what they return:
  1. List[model, view, controller] createMVCGroup(type, id, paramMap)
  2. Map['model': model, 'view':view, 'controller':controller, ...] buildMVCGroup(paramMap, type, id)
So if I had created an MVC group: 'griffon create-mvc Diagram', I could instantiate it in my application with either:
def list = createMVCGroup('Diagram', 'diagram1', [:])
or
def map = buildMVCGroup([:], 'Diagram', 'diagram1')


I was initially a bit confused as to why the parameters map was moved to the first argument for buildMVCGroup but then I remembered that Groovy lets you pass named parameters to a method and collects them all as a map.  This allows some nice syntactic sugar when creating an MVC group:
def map = buildMVCGroup('Diagram', 'diagram1', name: 'The Diagram', format: 'jpeg')


The other nifty thing is that Griffon automagically sets parameters passed when creating an MVC group to properties on the model (if applicable).  In the example above, if I had a name and a format properties on my model, they would be set to the values I passed instead of me having to explicitly set them in mvcGroupInit.


Destroying MVC Groups
While probably not critical, it's good practice to dispose of your MVC groups when you're done working with them.  You can do this by calling destroyMVCGroup with the id you passed when creating the group.  I find myself using many short-lived MVC groups.  To ensure I disposed of things properly, I whipped up a little method that simplifies things:
def withMVC(String type, String id, Map params, Closure closure) {
    closure(buildMVCGroup(params, type, id))
    destroyMVCGroup(id)
}
NOTE: you could do the same trick of moving the map to the first argument if you wanted to be able to pass 'loose' named parameters to the method.

You would call it in your code like so:
withMVC('NewSectionDialog', 'newSection', [ project: model.project ]) { mvc ->
    def section = mvc.controller.show()
    if (section) { model.projectSections << section }
}
This creates the MVC group, hands it to the closure you pass, and then destroys the group when done.

MVC groups are a nice feature of Griffon that let you create re-usable building blocks within your application.  Hopefully this tip helps you use them more effectively.

Thursday, September 10, 2009

Happy Birthday Griffon

Well it looks like I'm a bit late to the party as James, Jim, Guillaume, and Andres already beat me to it, but I'll say it anyways: Happy Birthday Griffon!  As birthdays are a natural time for reflection, I thought I'd look through my blog archives to find my first Griffon post.  Although not one of my most original titles, My First Griffon App debuted on September 17, 2008 marking me as a happy Griffon user since nearly the beginning.  Since then I've written several other Griffon apps and a few more blog posts about Griffon.

It's been fun watching not only the project develop but also the enthusiastic community spring up around it.  As Jim mentioned in his post, Griffon's presence at JavaOne this year was a major highlight.  The fact that the major IDEs are adding Griffon support and Griffon in Action is set to release this March shows that Griffon truly has arrived.

So congrats to everyone involved.  If you haven't tried Griffon yet, what are you waiting for?  The upcoming 0.2 release is shaping up to be the best yet!

Wednesday, September 09, 2009

Griffon Tip: Mac Look and Feel on Snow Leopard

If you upgraded your Mac to Snow Leopard recently, you may have noticed that your Griffon apps no longer look the same as they used to on Leopard.  Aside from a few color differences, the biggest giveaway will be that your app no longer uses the standard top menubar.  Fortunately there is a simple fix if you want to use the more native look and feel.  Change your griffon-app/lifecycle/Initialize.groovy file to include the 'mac' look and feel first:
import groovy.swing.SwingBuilder
import griffon.util.GriffonPlatformHelper

GriffonPlatformHelper.tweakForNativePlatform(app)
SwingBuilder.lookAndFeel('mac', 'nimbus', 'gtk', ['metal', [boldFonts: false]])

The crux of the problem is SwingBuilder tries various L&Fs in the order you specify them and stops when it finds one that works.  Nimbus is the L&F for Java 6 but previous versions of Mac OS X shipped with Java 5.  So SwingBuilder would try Nimbus and it would fail then it would try the Mac look and feel which would work.  Snow Leopard ships with Java 6 so when SwingBuilder tried Nimbus, it worked and it never tried to set the Mac L&F.

EDIT: This will be the default behavior in the upcoming Griffon 0.2 release.  So you will only need this fix if you have Griffon apps from pre-0.2 days.

Tuesday, September 01, 2009

Griffon Tip: Intercepting Window Closing Events

When building an editor of sorts, it is often useful to intercept the window closing event so that you can prompt the user to save if the editor has unsaved changes.  To do this in Griffon, we first need to change the defaultCloseOperation and define a windowClosing event handler on our application node in the view:

application(title:'Editor', size:[800,600], locationByPlatform: true,
    defaultCloseOperation: WindowConstants.DO_NOTHING_ON_CLOSE, 
    windowClosing: { evt -> 
        // check if the editor needs saving
    }) {
// the rest of your view code here
}

If we run our application now, we should notice that...the windowClosing event handler never runs.  Turns out that there's one more step--we need to tell Griffon that we want to explicitly handle shutting down the application.  We do this by setting autoShutdown=false in griffon-app/conf/Application.groovy Once this is set, we should notice that our windowClosing event handler runs.  Since Griffon is no longer responsible for automatically shutting down our application, we must make sure to call app.shutdown() somewhere in our closing logic or the user won't be able to exit the app.

Wednesday, August 19, 2009

Building your Griffon app with Gradle

In this post, I'm going to show how to build your Griffon apps with Gradle. Griffon already comes with a command line tool to build and interact with your app, so why would you want to do this? I can think of a few reasons:
  • you have an existing Gradle build you want to integrate with
  • you have a complex build or packaging scenario
  • you want to take advantage of Gradle's dependency management for your app
Prerequisites
  1. Griffon
  2. Gradle
The build.gradle file
Below is a generic Gradle build file for Griffon applications. Either copy and paste the code below or download it and place it in a build.gradle in your Griffon application folder.
// standard configuration
project.captureStandardOutput(LogLevel.INFO)
configurations { compile }

// add additional repositories here if you need them
repositories {
//mavenRepo(urls:'http://download.java.net/maven/2/')
mavenCentral()
}

// add any dependencies you want downloaded as part of the bootstrap process
dependencies {
//compile 'com.google.collections:google-collections:1.0-rc2'
}

// this should be run once for the project
createTask('bootstrap') << {
// download any dependencies and put them in lib/
configurations['compile']?.files?.findAll { it.absolutePath.indexOf('unspecified') < 0 }.each { dep ->
ant.copy(toDir: new File('lib'), file: dep)
}

// try to install any plugins
new File('application.properties').eachLine { line ->
if (line.startsWith("plugins")) {
def plugin = line[8..-1].split('=')
griffon "install-plugin ${plugin[0]} ${plugin[1]}"
}
}
}

// mimic the tasks provided by the Java plugin
createTask('clean') << { griffon 'clean' }
createTask('compile') << { griffon 'compile' }
createTask('libs') << { griffon 'package' }
createTask('test') << { griffon 'test-app' }
createTask('dists') << { griffon 'prod package' }

// additional run-related tasks
createTask('run-app') << { griffon 'run-app' }
createTask('debug-app') << { griffon 'run-app -debug' }
createTask('run-webstart') << { griffon 'run-webstart' }
createTask('run-applet') << { griffon 'run-applet' }

// call out to the griffon command
def griffon(target) {
if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
ant.exec(executable: 'griffon.bat', dir: projectDir, failonerror: true) {
arg(value: target)
}
} else {
ant.exec(executable: 'griffon', dir: projectDir, failonerror: true) {
arg(value: target)
}
}
}

// if you want to use the gradle wrapper
createTask('wrapper', type: Wrapper).configure {
gradleVersion = '0.7'
}
To verify that everything is working, let's try running our app from Gradle (substitute gradle.bat if you are on Windows):
gradle run-app

Your Griffon app should run. If it doesn't, try invoking the Gradle command with the '-i' flag:
gradle -i run-app
to see what the problem is.

Multi-project Builds
So far it may seem like "What's the point?" We could already run our Griffon app with the griffon command. Where Gradle really starts to shine is when you start wanting to perform complex builds, such as building multiple Griffon apps at once or integrating with an existing build.

Gradle has good support for multi-project builds. Assuming you are using a directory layout like this:
settings.gradle
griffonapp1/
build.gradle
...
griffonapp2/
build.gradle
...
Building these multiple Griffon apps is as simple as listing them in the settings.gradle file:
include 'griffonapp1', 'griffonapp2'
Similarly, integrating with an existing build should be as simple as adding the name of your Griffon app to your existing settings.gradle file.

Complex Build/Packaging Scenarios
Griffon offers several packaging options with the Installer plugin. Sometimes you need more flexibility. For my PSICAT build, I wanted to package two Griffon apps together in a specific directory structure. The nice thing about Gradle build files is that they are just Groovy code and offer great integration with Ant. Below is a snippet from the PSICAT build that illustrates using Ant within Gradle:
// griffon create-all-launchers for PSICAT and SchemeEditor
griffon(psicat, 'create-all-launchers')
griffon(schemeEditor, 'create-all-launchers')

// build linux package
ant.copy(toDir: new File(linux, 'PSICAT')) {
fileset(dir: new File(psicat, 'installer/linux/dist')) {
include(name: 'bin/**')
include(name: 'lib/**')
}
}
ant.copy(toDir: new File(linux, 'PSICAT/tools/SchemeEditor')) {
fileset(dir: new File(schemeEditor, 'installer/linux/dist')) {
include(name: 'bin/**')
include(name: 'lib/**')
}
}
ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/bin'), includes: '*')
ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/tools/SchemeEditor/bin'), includes: '*')
ant.tar(destFile: new File(linux, "PSICAT-linux-${version}.tar.gz"), compression: 'gzip', basedir: linux, includes: 'PSICAT/**')
...
(if you're curious, the whole build.gradle file is here)

Cool Gradle Tricks
One cool feature of Gradle is that you can generate a 'wrapper' script that will download and bootstrap Gradle itself so that developers who download your source don't have to have Gradle installed to build your project.
The build.gradle file above is already setup to create this wrapper. You simply have to run the command:
gradle wrapper
and it will create four files in your project:
gradle-wrapper.jar
gradle-wrapper.properties
gradlew
gradlew.bat
If you include these files when you send someone your project, they can use the gradlew or gradlew.bat scripts as if they had Gradle installed:
./gradlew run-app

The other neat trick with Gradle is you can use its ability to pull from Maven repositories to bootstrap the dependencies of your Griffon app. This means you don't need to send someone your Griffon app with a bunch of JARs in the lib directory. Instead you can send them a Gradle-ized version of your app and tell them to run the command:
./gradlew bootstrap

This will download all of the dependencies you've listed in the build.gradle file and put them in the lib directory of your Griffon app. Specifying dependencies is as simple as adding a few lines to the dependencies section of the build file:
dependencies {
compile 'com.google.collections:google-collections:1.0-rc2'
compile 'org.slf4j:slf4j-api:1.5.8'
}
Conclusion
Hopefully this has given you a taste of using Gradle with Griffon. Both are awesome tools that really show off the power of the Groovy language. If there's enough interest in using Gradle with Griffon, we can make build.gradle a standard artifact created by the griffon create-app task.

Saturday, August 15, 2009

Are Groovy Stacktraces a Showstopper?

I've been part of an interesting debate on the merits of Groovy via Twitter this week. I'm reproducing it here because I think it's an interesting take on what potential users feel are make or break issues for a language, and to give others who may not have seen it the chance to weigh in without the 140 character limit. :)

(Sorry in advance. Reproducing a Twitter convo in a blog is kind of hideous)

[@philswenson] if you want to know why groovy sucks, click here: http://pastie.org/583115
[@joshareed] @philswenson It took me a second to diagnose your problem. Did you want Groovy to say: "Hey joker, actually import the classes you're using"
[@joshareed] @philswenson Apparently Ruby has some magical mind-reading interpreter/compiler that saves you from yourself?

[@philswenson] @joshareed why the extra 500 lines of crap? in java or jruby that stacktrace would have been 1/50 the size. that's my point
[@joshareed] @philswenson re: Java guess that's the trade off for Groovy dynamics. Ruby's (http://bit.ly/IagLJ) is not any better [Note: Nambu mangled the original link]

[@philswenson] @joshareed Ruby's stack dump = 67 lines. Groovy's = 222 lines. Not as different as I said, but still...
[@joshareed] @philswenson # of lines doesn't matter when you see the cause of the prob in the first 10 lines, does it? You don't have to keep reading...
[@joshareed] @philswenson and was the 67 vs 222 equivalent programs in each? I'd be interested in comparing the source of each

[@philswenson] @joshareed this was a case where the cause was obvious (it usually isn't). isn't groovy in large part about succinctness over java?
[@joshareed] @philswenson how much time do you spend looking at stacktraces that this is hang up for you? Write a 3 line .findAll{} to filter traces
[@joshareed] @philswenson and if you want to stick with stacktrace length as your argument then you won't be plain old Java traces with JRuby

From my perspective, this isn't a JRuby vs Groovy debate even though both languages were brought up in the conversation. Both are two different ways to skin a cat but they ultimately get you to the same place. Which you choose to use is more a matter of personal preference than the actual merits of the language.

What I'm more interested in is how important/serious is the stacktrace issue that Phil Swenson argues? I've been writing Groovy for a couple years now and can't remember (off the top of my head) a stacktrace from Groovy that was so confusing I couldn't figure out the problem. I'm sure there are some, especially when you start getting deep into the Groovy innards, but I'm guessing that's also the case with other languages on the JVM, like JRuby.

All dynamic languages built on the JVM introduce a certain level of indirection (by necessity), so are Groovy's stacktraces that much more hideous than JRuby's? What qualifies as hideous? I think length is a naive measure because the cause of the problem is likely to show up in the first dozen lines. Who cares if it goes on for another 200 lines? Length is also easily manipulated. You could write a simple findAll closure that sanitizes the stacktrace by removing all the lines that start with 'sun.reflect' or 'org.codehaus.groovy.runtime'. I have never once run into a problem that could be tracked to one of those lines in the stacktrace. So now does that make Groovy better than JRuby because the stacktrace is shorter?

I'm sure this is just a case of me being used to seeing Groovy stacktraces, so I'm used to feel of them and can quickly hone in on the cause of the problem. Likewise I'm sure Phil is used to [J]Ruby errors which probably look much different than Groovy's. I say probably because the two example put forth didn't look all that dissimilar IMO but were not from the same problem so it was an apples to oranges comparison.

Anyways, how important are stacktraces when making the decision to use a particular language? Are they important enough to declare one language's superiority over another? Are they worth focusing on to improve Groovy's ease-of-use?

Wednesday, August 12, 2009

Griffon Installer Plugin

Last week Andres Almiray and I released a new version of the Griffon Installer plugin. This version adds support for creating native Mac app bundles for your Griffon application, as well a few refactorings to make the generated artifacts more consistent. In this post, I'm going to walk you through the process of creating a set of native launchers for your application. A future post will explain the process of creating installers.

Getting started using the installer plugin is simple:
griffon install-plugin installer

This will fetch the plugin and install it into your Griffon app. The plugin adds several new targets. All of the launcher-related targets are broken into two steps: a prepare step that copies all of the configuration files for that particular launcher into your application, and a create step that actually generates the native launcher. The prepare targets only need to be run once for your project. They give you a chance to tweak the launcher configuration files. The create targets are run every time you want to make a release.

The plugin provides launchers for the following platforms:
  • Linux
  • Mac
  • Windows
  • Executable Jar
The plugin also provides two other convenient targets, prepare-all-launchers and create-all-launchers which invoke the prepare- and create- targets for the four platforms above.

We'll use these convenience targets to create launchers for all platforms:
griffon prepare-all-launchers

This will create several new files for you:
  • installer/jar - the executable JAR launcher directory
  • installer/jsmooth - the Windows launcher directory
  • installer/linux - the Linux launcher directory
  • installer/mac - the Mac launcher directory
  • installer/jar/MANIFEST.MF - customize the manifest of the executable JAR
  • installer/jsmooth/$appName-icon.png - customize the .exe icon
  • installer/jsmooth/$appName.jsmooth - customize the .exe options
  • installer/mac/$appName.icns - customize the Mac icon
Once you are done customizing the configuration files, invoke the
griffon create-all-launchers target to actually create the launchers. This will create all of the launchers in the installer/$platform/dist directories and create archives of the launchers:
  • installer/jar/dist/$appName-$version.jar - the executable jar
  • installer/linux/dist/bin/$appName - the Linux launcher shell script
  • installer/linux/dist/$appName-linux-$version.zip - a zip of the Linux launcher
  • installer/mac/dist/$appName-$version.dmg - a disk image of the Mac launcher [only created if on a Mac]
  • installer/mac/dist/$appName.app - the Mac application bundle
  • installer/jsmooth/dist/$appName-windows-$version.zip - a zip of the Windows launcher
  • installer/jsmooth/dist/$appName.exe - the Windows launcher
  • installer/windows/dist/$appName-windows-$version.zip - copied from the JSmooth directory
  • installer/windows/dist/$appName.exe - copied from the JSmooth directory
As you make changes to your code, simply invoke griffon create-all-launchers and your latest changes will be packaged up. If you increment the version number of your app, the created artifacts will be updated to match.

Hopefully the Installer plugin makes it easy to distribute your Griffon applications to users on various platforms. In a future blog post, I'll cover creating installers for when a simple zip file is not enough.

Tuesday, July 14, 2009

Griffon Plugin Development

I finally got to the point in my Griffon application development that I was ready to release my app. Griffon has a plugin for creating installers and platform-specific launchers, but it didn't generate a proper Mac .app bundle. Being a Mac user myself, I really wanted to be able to deploy my app natively on Macs, so I decided to scratch my itch and add support to the plugin. I learned a fair bit about Griffon plugin development, so keep reading if you're interested in tweaking existing plugins or creating new plugins.

First off, Griffon plugins are simply Griffon applications with a few extra bits not normally used in a standard application—scripts for providing command line targets e.g., griffon create-mac-launcher, and interception of Griffon events like eventCleanEnd. To get started with plugin development, you'll want to decide whether you want to create a new plugin or edit an existing one.

Setting up your Griffon plugin development environment is fairly straightforward. If you want to edit an existing plugin:

cd MyExistingApplication
griffon install-plugin installer


If you want to create a new plugin and install it in an existing application:

griffon create-plugin MyNewPlugin
cd MyExistingApplication
{edit application.properties to include a line 'plugin.my-new-plugin=0.1'}



The real trick for both of these scenarios is to link the plugin source to somewhere where you can edit it live. This makes testing a whole lot easier. I had to pull a few strings to get this tip, but I'm going to share it with you for free:
Go into your ~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/ and either edit the plugin directly or symlink it to something more convenient:

cd ~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/
rm -rf installer-0.3
ln -s ~/Workspace/griffon/griffon-installer installer-0.3

You just have to make sure the name and the version match what is in your MyExistingApplication/application.properties file. If you installed an existing plugin, you should be good to go. If you're creating a new plugin, it defaults to 0.1.

Once you have your plugin development environment setup, you're ready to go to town. Below are a few things I learned along the way. Most have to do with Gant scripting, which I wasn't very familiar with before this but is easy to pick up.

If you want to create a new command line option, e.g. griffon say-hello-world you need to create a new script in your plugin. You can do this by creating a SayHelloWorld.groovy file in the $plugin/scripts. Or as Andres points out in the comments a simple griffon create-script SayHelloWorld works as well. Even better! You also have to define a sayHelloWorld target in your script and make it the default target:

target(sayHelloWorld:"Says 'Hello World'") {
// any Groovy is valid here
println "Hello World!"
}
setDefaultTarget(sayHelloWorld)


Another useful feature is the ability to invoke targets in other strips, such as if you wanted to make sure the Griffon app was built before your task was executed. You can accomplish this by including the targets of other scripts in your script:

includeTargets << griffonScript("_GriffonPackage") // script name minus the .groovy

target(doAfterBuild: "Build and then run me") {
depends(checkVersion, packageApp, classpath) // defined by _GriffonPackage.groovy
packageApp() // calls the packageApp target in _GriffonPackage.groovy
println "The JARs in $basedir/staging should be fresh!"
}

setDefaultTarget(doAfterBuild)


Finally, before this post gets too long-winded, I'll cover the topic of events. Events are fired by the Griffon core runtime and by plugins in response to actions in the workflow. They provide an extension mechanism for plugins who wish to respond to various actions. Your plugin can intercept events by providing a $plugin/scripts/_Events.groovy file which declares interest in specific events:

eventCleanEnd = {
// jar
ant.delete(dir:"${basedir}/installer/jar/dist", failonerror:false)
ant.delete(dir:"${basedir}/installer/jar/classes", failonerror:false)

// jsmooth
ant.delete(dir:"${basedir}/installer/jsmooth/dist", failonerror:false)

// linux
ant.delete(dir:"${basedir}/installer/linux/dist", failonerror:false)

// mac
ant.delete(dir:"${basedir}/installer/mac/dist", failonerror:false)

// windows
ant.delete(dir:"${basedir}/installer/windows/dist", failonerror:false)
}


If you are developing your own plugin, you should consider publishing events so that other plugins can hook into your workflow. You can do this by including the _GriffonEvents targets in your script, or by including any script that itself includes _GriffonEvents:

includeTargets << griffonScript("_GriffonEvents")

target(myPluginTask: "") {
event("MyPluginTaskStart", [])
println "My Plugin Task!"
event("MyPluginTaskEnd", [])
}

setDefaultTarget(myPluginTask)


Thanks for following along. Any suggestions or questions are greatly appreciated!

Oh and if you're interested in the installer plugin stuff, check out: http://dev.psicat.org/ for some example outputs. These are not the exact outputs as I am bundling two Griffon apps--the main PSICAT one and a second one in tools/--but you get the idea.

Thursday, July 02, 2009

Griffon Action Patterns

I've been working on a decent-sized Griffon app recently and hit upon a pattern for managing actions that I thought might be useful to others. I began by following SwingPad's lead and declaring my actions separate (PSICATActions.groovy) from my view (PSICATView.groovy):

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.exit)
action(id: 'openAction', name: 'Open', closure: controller.open)
...
}


which I import into my view:

// build actions
build(PSICATActions)

application(title:'PSICAT', size:[800,600], locationByPlatform: true, layout: new MigLayout('fill')) {
...
}


This is nice because it keeps all of your action definitions in one place and doesn't clutter your view code with them. When implementing the actions, the common pattern seems to define closures on your controller:

class PSICATController {
def model
def view

void mvcGroupInit(Map args) {
...
}

def exit = { evt = null ->
...
}

def open = { evt = null ->
...
}
}


This is fine but when you start getting many actions your controller becomes cluttered and it is difficult to tell what is an action implementation and what is regular controller logic. You also have to be careful with naming:

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.exit)
action(id: 'openAction', name: 'Open', closure: controller.open)
action(id: 'newAction', name: 'New', closure: controller.new) // ERROR
}


We are forced to break with our naming convention because 'new' is a reserved keyword. To combat the clutter and avoid naming issues, I've taken to declaring my action implementations in a map on the controller:

class PSICATController {
def model
def view

void mvcGroupInit(Map args) {
...
}

def actions = [
'exit': { evt = null -> ... },
'open': { evt = null -> ... },
'new': { evt = null -> ... } // no issues here
]
}


Using the map makes it easy to tell what is an action and what is not, and linking the action definitions to the implementations becomes elegant and intuitive:

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.actions['exit'])
action(id: 'openAction', name: 'Open', closure: controller.actions['open'])
action(id: 'newAction', name: 'New', closure: controller.actions['new'])
}


So far I have been pretty happy with this approach. What do other Griffon developers out there think?

Friday, February 27, 2009

Understudy

A few days ago I stumbled across Understudy which lets you watch Hulu and Netflix from inside Front Row on your Mac. As there wasn't much on TV last night, I decided to test it out. I have an old MacBook Pro whose screen backlight died but is otherwise fully functional. I threw a copy of Understudy on it and hooked it up to the TV in the living room. Much to my delight it worked like a charm. I could use my Apple Remote to browse and navigate the shows and movies in my Hulu and Netflix queues. The only real issue I had was that I couldn't always trigger full screen mode in Hulu via the remote. This is a known bug so I expect it will eventually be fixed.

Tuesday, February 24, 2009

Updates

My blogging has sort of fallen to the wayside since I started using Twitter. If you want to keep up with what I'm doing day to day (and don't mind a bit lower signal-to-noise ratio), check out my Twitter feed. You can follow along without signing up, but it's more fun if you have an account and we can interact.

Here's a whirlwind tour of the last three months for me:

Work

Work with ANDRILL is still going well. I seem to be doing something different each day, which is some times nice and some times a burden. I've got enough projects (both internal and external collaborations) going to keep me busy around the clock. I have to re-interview for my position in June (which I'm told is just a formality). Such is the way of university politics and being dependent on public funding, I guess.

My traveling has slowed down somewhat. I was in Lincoln in November, San Francisco in December for AGU, and Wellington, NZ this month for a meeting. I currently have no trips in the forseeable future, though they have a tendency to pop up on short notice.

I've got a manuscript submitted to Computers and Geosciences that reviewed favorably. Hopefully one more short round of revisions and it will be published. Then I can move on to the several papers that are in the 'needs revision' or 'just plain needs writing' stage. I've not been very good about forcing myself to write.

I helped write a fairly ambitious proposal for NSF's CDI solicitation. We're still waiting to hear how it reviewed, but it would be a lot of fun work if funded.

Personal

Things are still going well for Elizabeth at work. The last 3 months have been comparatively 'easy' for her. Easy being relative but she's not had as many rotations which require to spend the night at the hospital on call. Currently she's back into the call rotations, so she's gone every 4 days or so--she tells me it's payback for all of my travel.

The house is still standing. We had a minor pipe freeze a couple months back before we realized that there was an on/off lever for heating the basement. As you can probably guess, it was off. No problems since.

We're hoping to put in a nice concrete patio out back this spring. This step 1 of our renovation plans. Step 2 is to replace the bank of windows in the basement with a set of French doors to walk out onto the patio. Step 3 is to convert some space in the basement to a proper bathroom with a shower. Once all that is done, we can start looking at gutting and renovating the bathroom upstairs. Well down the road will be kitchen.

We're all excited here for spring. Bella likes the snow but it will be nice to be able to take her to the lake for a swim. It'll also be nice to setup the hammock again so I can blog from the backyard. I think it's going to be a busy spring cleaning up things around the yard that we didn't get done last fall.

I'm still brewing beer. Right now I've got a Strong Ale and a Brown Ale (my own recipe) bottled and the rest of the brown on tap. There's a Maibock fermenting away in one of my primaries and something (can't remember it offhand) on deck to brew probably in a week or two. I'm also still exploring single malt Scotches. I've got a bottle of Lagavulin 16YO that Elizabeth gave me for Christmas and a bottle of The Macallan 12YO open at the moment. Between the beer on tap, the whisky, and the new patio, this should be a fun place to grill out this summer.

That's probably enough for now. I need to blog more often, if only to avoid these long rambling posts.