Griffon

1. Introduction

Developing desktop/RIA applications on the JVM is a hard task. You have to make choices upfront during application design that might complicate the implementation, compromising the user experience; not to mention the amount of configuration needed.

RCP solutions like Eclipse RCP and NetBeans RCP are great for developing desktop applications, not so much for RIAs and applets. Griffon is a framework inspired by Grails, whose aim is to overcome the problems you may encounter while developing all these types of applications. It brings along popular concepts like

  1. Convention over Configuration

  2. Don’t Repeat Yourself (DRY)

  3. Pervasive MVC

  4. Testing supported "out of the box"

This documentation will take you through getting started with Griffon and building desktop/RIA applications with the Griffon framework.

Credits and Acknowledgements

This guide is heavily influenced by the Grails Guide. It simply would not have been possible without the great efforts made by: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown and their sponsor: SpringSource. The Griffon team would like to thank them all (and the Grails community too!) for making such a great framework and bringing the fun back to programming applications.

2. Getting Started

This chapter will help you get started with Griffon by explaining how to download and install the project, how to use Griffon from within your favorite IDE, and how to write your first application.

2.1. Environment Setup

The following section outlines the minimum environment requirements to get started with Griffon.

2.1.1. JDK

JDK7 is the lowest JVM version supported by Griffon. You can safely use JDK8 features such as lambdas too. We strongly suggest to use JDK8 if you’re planning to build JavaFX based applications as JavaFX8 delivers many enhancements over JavaFX 2.2 (the version included in JDK7).

2.1.2. Gradle

Gradle 2.0 was used to thoroughly test the Griffon source code and as such is guaranteed to work. We suggest installing GVM as a means to keep your Gradle installation up to date. GVM can also be used to install Lazybones, a templating tool for creating Griffon projects.

2.1.3. Maven

Alternatively you may use Maven instead of Gradle as your build tool of choice. Maven is a popular choice amongst Java developers, however it’s our firm belief that Gradle delivers a much better development and user experience. YMMV.

2.1.4. IDEs

Any IDE that supports Gradle and/or Maven can be used to build a Griffon project.

2.2. Console Example

2.2.1. Creating a Project

The first step is to get Lazybones installed on your system. The easiest way to achieve this goal is to install GVM first

$ curl -s get.gvmtool.net | bash

Install the latest version of Lazybones with the following command

$ gvm install lazybones

Next, add the official Griffon Lazybones templates repository to your Lazybones configuration. Edit $USER_HOME/.lazybones/config.groovy and paste the following content

$USER_HOME/.lazybones/config.groovy
1bintrayRepositories = [
2    "griffon/griffon-lazybones-templates",
3    "pledbrook/lazybones-templates"
4]

We’re now ready to create the project. You can list all available templates with the following command

$ lazybones list
Available templates in griffon/griffon-lazybones-templates:

    griffon-javafx-java
    griffon-javafx-groovy
    griffon-lanterna-java
    griffon-lanterna-groovy
    griffon-pivot-java
    griffon-pivot-groovy
    griffon-swing-java
    griffon-swing-groovy
    griffon-plugin

Notice that template names follow a naming convention identifying the main UI toolkit and main programming language. Alright, let’s create a simple project using Swing as main UI toolkit and Groovy as main language

$ lazybones create griffon-swing-groovy console
Creating project from template griffon-swing-groovy (latest) in 'console'
Define value for 'group' [org.example]: console
Define value for 'version' [0.1.0-SNAPSHOT]:
Define value for 'package' [console]:
Define value for 'griffonVersion' [2.0.0.RC1]:

2.2.2. Project Layout

Take a moment to familiarize yourself with the standard Griffon project layout. Every Griffon project shares the same layout, making it easy to dive in as artifacts are located in specific directories according to their responsibilities and behavior.

console
├── griffon-app
│   ├── conf
│   ├── controllers
│   ├── i18n
│   ├── lifecycle
│   ├── models
│   ├── resources
│   ├── services
│   └── views
└── src
    ├── integration-test
    │   └── groovy
    ├── main
    │   ├── groovy
    │   └── resources
    └── test
        ├── groovy
        └── resources

Griffon uses "convention over configuration" approach to enable a fast development pace. This typically means that the name and location of files is used instead of explicit configuration, hence you need to familiarize yourself with the directory structure provided by Griffon.

Here is a breakdown and links to relevant sections:

Here’s an screenshot of the finished application running to give you an idea of what we’re aiming at with this example . A small Groovy script has been executed, you can see the result on the bottom right side

Swing
Gradle

The following listing shows the Gradle build file generated by the Lazybones template

build.gradle
 1buildscript {
 2    repositories {
 3        jcenter()
 4    }
 5
 6    dependencies {
 7        classpath 'org.codehaus.griffon:gradle-griffon-plugin:2.0.0.RC1'
 8        classpath 'net.saliman:gradle-cobertura-plugin:2.2.4'
 9        classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:1.0.2'
10        classpath 'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0'
11        classpath 'org.gradle.api.plugins:gradle-izpack-plugin:0.2.3'
12        classpath 'com.github.jengelman.gradle.plugins:shadow:1.0.2'
13        classpath 'edu.sc.seis.gradle:macappbundle:2.0.0'
14        classpath 'org.kordamp.gradle:stats-gradle-plugin:0.1.1'
15        classpath 'com.github.ben-manes:gradle-versions-plugin:0.4'
16    }
17}
18
19apply plugin: 'groovy'
20apply plugin: 'org.codehaus.griffon.griffon'
21
22griffon {
23    disableDependencyResolution = false
24    includeGroovyDependencies = true
25    version = '2.0.0.RC1'
26    toolkit = 'swing'
27}
28
29apply from: 'gradle/publishing.gradle'
30apply from: 'gradle/code-coverage.gradle'
31apply from: 'gradle/code-quality.gradle'
32apply from: 'gradle/integration-test.gradle'
33apply from: 'gradle/package.gradle'
34apply from: 'gradle/docs.gradle'
35apply plugin: 'com.github.johnrengelman.shadow'
36apply plugin: 'org.kordamp.gradle.stats'
37apply plugin: 'versions'
38
39mainClassName = 'console.Launcher'
40
41dependencies {
42    compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
43
44    runtime('log4j:log4j:1.2.17') {
45        exclude group: 'ant',         module: 'ant-nodeps'
46        exclude group: 'ant',         module: 'ant-junit'
47        exclude group: 'ant-contrib', module: 'ant-contrib'
48    }
49    runtime 'org.slf4j:slf4j-log4j12:1.7.7'
50
51    testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
52    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
53}
54
55import org.codehaus.griffon.gradle.shadow.transformers.*
56shadowJar {
57    mergeServiceFiles()
58    transform(GriffonFileTransformer)
59    transform(PropertiesFileTransformer) {
60        paths = [
61            'META-INF/editors/java.beans.PropertyEditor'
62        ]
63    }
64}

Alright, let’s get started with the code. We’ll visit the Model first.

Model

The model for this application is simple: it contains properties that hold the script to be evaluated and the results of the evaluation. Make sure you paste the following code into griffon-app/models/console/ConsoleModel.groovy.

griffon-app/models/console/ConsoleModel.groovy
 1package console
 2
 3import griffon.core.artifact.GriffonModel
 4import griffon.metadata.ArtifactProviderFor
 5import griffon.transform.Observable
 6
 7@ArtifactProviderFor(GriffonModel)
 8class ConsoleModel {
 9    String scriptSource                                  (1)
10    @Observable Object scriptResult                      (2)
11    @Observable boolean enabled = true                   (3)
12}
1 Holds the script’s text
2 Holds the result of the script’s execution
3 Enable/disable flag

Griffon Models are not domain classes like the ones you find in Grails; they’re more akin to presentation models, and as such, they’re used to transfer data between Views and Controllers.

Controller

The controller is also trivial: throw the contents of the script from the model at an Evaluator, then store the result back into the model. Make sure you paste the following code into griffon-app/controllers/console/ConsoleController.groovy.

griffon-app/controllers/console/ConsoleController.groovy
 1package console
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.metadata.ArtifactProviderFor
 5
 6import javax.inject.Inject
 7
 8@ArtifactProviderFor(GriffonController)
 9class ConsoleController {
10    def model                                                (1)
11
12    @Inject
13    Evaluator evaluator                                      (2)
14
15    void executeScript() {                                   (3)
16        model.enabled = false
17        def result
18        try {
19            result = evaluator.evaluate(model.scriptSource)  (4)
20        } finally {
21            model.enabled = true
22            model.scriptResult = result                      (5)
23        }
24    }
25}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Controller action; automatically executed off the UI thread
4 Evaluate the script
5 Write back result to Model

The Griffon framework will inject references to the other portions of the MVC triad if fields named model, view, and controller are present in the Model, Controller or View. This allows us to access the view widgets and the model data if needed. Any other class members annotated with @javax.inject.Inject participate in dependency injection as laid out by JSR 330, in this case the controller will get an instance of Evaluator if a suitable implementation is bound.

The executeScript 3 method will be used later in the View in combination with a button. You may notice that there’s no explicit threading management. All Swing developers know they must obey the Swing Rule: long running computations must run outside of the EDT; all UI components should be queried/modified inside the EDT. It turns out Griffon is aware of this rule, making sure an action is called outside of the EDT by default; all bindings made to UI components via the model will be updated inside the EDT 5. We’ll setup the bindings in the next listing.

We must create a Module in order to bind Evaluator. These are the required class definitions

src/main/groovy/console/Evaluator.groovy
1package console
2
3public interface Evaluator {
4    Object evaluate(String input)
5}
src/main/groovy/console/GroovyShellEvaluator.groovy
 1package console
 2
 3class GroovyShellEvaluator implements Evaluator {
 4    private GroovyShell shell = new GroovyShell()
 5
 6    @Override
 7    Object evaluate(String input) {
 8        shell.evaluate(input)
 9    }
10}
src/main/groovy/console/ApplicationModule.groovy
 1package console
 2
 3import griffon.core.injection.Module
 4import griffon.inject.DependsOn
 5import griffon.swing.SwingWindowDisplayHandler
 6import org.codehaus.griffon.runtime.core.injection.AbstractModule
 7import org.kordamp.jipsy.ServiceProviderFor
 8
 9import static griffon.util.AnnotationUtils.named
10
11@DependsOn('swing')                                                        (3)
12@ServiceProviderFor(Module)
13class ApplicationModule extends AbstractModule {
14    @Override
15    protected void doConfigure() {
16        bind(Evaluator)                                                    (1)
17            .to(GroovyShellEvaluator)
18            .asSingleton()
19
20        bind(SwingWindowDisplayHandler)                                    (2)
21            .withClassifier(named('defaultWindowDisplayHandler'))
22            .to(CenteringWindowDisplayHandler)
23            .asSingleton()
24    }
25}
1 Binding definition
2 Overriding an existing binding
3 Loaded after swing module

Modules can define several bindings, even override existing bindings. In our particular case we defined a binding 1 for Evaluator and overrode a binding 2 for SwingWindowDisplayHandler. The latter is supplied by the swing module thus we must mark it as a dependency 3 in our module definition. The implementation of our custom SwingWindowDisplayHandler is quite trivial, as shown by the following snippet

src/main/groovy/console/CenteringWindowDisplayHandler.groovy
 1package console
 2
 3import org.codehaus.griffon.runtime.swing.DefaultSwingWindowDisplayHandler
 4
 5import javax.annotation.Nonnull
 6import java.awt.Window
 7
 8import static griffon.swing.support.SwingUtils.centerOnScreen
 9
10class CenteringWindowDisplayHandler extends DefaultSwingWindowDisplayHandler {
11    @Override
12    void show(@Nonnull String name, @Nonnull Window window) {
13        centerOnScreen(window)
14        super.show(name, window)
15    }
16}

This handler is only concerned with centering the window on the screen before showing it.

View

The view classes contain the visual components for your application. Please paste the following code into griffon-app/views/console/ConsoleView.groovy.

griffon-app/views/console/ConsoleView.groovy
 1package console
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class ConsoleView {
 8    def builder                                                              (1)
 9    def model                                                                (1)
10
11    void initUI() {
12        builder.with {
13            actions {
14                action(executeScriptAction,                                  (2)
15                    enabled: bind { model.enabled })
16            }
17
18            application(title: application.configuration['application.title'],
19                pack: true, locationByPlatform: true, id: 'mainWindow',
20                iconImage:   imageIcon('/griffon-icon-48x48.png').image,
21                iconImages: [imageIcon('/griffon-icon-48x48.png').image,
22                             imageIcon('/griffon-icon-32x32.png').image,
23                             imageIcon('/griffon-icon-16x16.png').image]) {
24                panel(border: emptyBorder(6)) {
25                    borderLayout()
26
27                    scrollPane(constraints: CENTER) {
28                        textArea(text: bind(target: model, 'scriptSource'),  (3)
29                            enabled: bind { model.enabled },                 (2)
30                            columns: 40, rows: 10)
31                    }
32
33                    hbox(constraints: SOUTH) {
34                        button(executeScriptAction)                          (4)
35                        hstrut(5)
36                        label('Result:')
37                        hstrut(5)
38                        textField(editable: false,
39                                  text: bind { model.scriptResult })         (5)
40                    }
41                }
42            }
43        }
44    }
45}
1 MVC member injected by MVCGroupManager
2 Bind enabled state from model
3 Bind script source to model
4 Apply controller action by convention
5 Bind script result from model

The View contains a fairly straightforward SwingBuilder script. Griffon will execute these groovy scripts in context of it’s CompositeBuilder.

2.2.3. Running the application

Running the application requires you to execute the run task if using Gradle

$ ./gradlew run

Or the exec:java plugin goal if using Maven. Take special note that this goal assumes classes have been compiled already, so it’s best to pair it up with compile for safe measure. Or better yet, use the pre-configured run profile, like so

$ mvn -Prun

Now that we know the basic structure of a Griffon application and how to run it we turn to testing.

2.2.4. Testing

It’s always a good idea to test out the code we write. It’s pretty easy to write tests for regular components such as Evaluator and GroovyShellEvaluator, as they require little to no external dependencies. Testing Griffon artifacts such as Controllers and Models on the other hand requires a bit more of effort, but not much as shown by ConsoleControllerTest

src/test/groovy/console/ConsoleControllerTest.groovy
 1package console
 2
 3import griffon.core.artifact.ArtifactManager
 4import griffon.core.injection.Module
 5import griffon.core.test.GriffonUnitRule
 6import griffon.core.test.TestFor
 7import griffon.inject.DependsOn
 8import org.codehaus.griffon.runtime.core.injection.AbstractModule
 9import org.junit.Rule
10import org.junit.Test
11
12import javax.annotation.Nonnull
13import javax.inject.Inject
14
15import static com.jayway.awaitility.Awaitility.await
16import static com.jayway.awaitility.Awaitility.fieldIn
17import static java.util.concurrent.TimeUnit.SECONDS
18import static org.hamcrest.Matchers.notNullValue
19
20@TestFor(ConsoleController)                                                   (1)
21class ConsoleControllerTest {
22    private ConsoleController controller                                      (2)
23
24    @Inject
25    private ArtifactManager artifactManager                                   (3)
26
27    @Rule
28    public final GriffonUnitRule griffon = new GriffonUnitRule()              (4)
29
30    @Test
31    void testExecuteScriptAction() {
32        // given:                                                             (5)
33        ConsoleModel model = artifactManager.newInstance(ConsoleModel.class)
34        controller.model = model
35
36        // when:                                                              (6)
37        String input = 'var = "Griffon"'
38        model.scriptSource = input
39        controller.invokeAction('executeScript')
40
41        // then:                                                              (7)
42        await().atMost(2, SECONDS)
43            .until(fieldIn(model)
44            .ofType(Object)
45            .andWithName('scriptResult'),
46            notNullValue())
47        assert input == model.scriptResult
48    }
49
50    private static class EchoEvaluator implements Evaluator {                 (8)
51        @Override
52        Object evaluate(String input) {
53            input
54        }
55    }
56
57    @DependsOn('application')
58    private static class TestModule extends AbstractModule {
59        @Override
60        protected void doConfigure() {
61            bind(Evaluator)
62                .to(EchoEvaluator)
63                .asSingleton()
64        }
65    }
66
67    @Nonnull
68    private List<Module> moduleOverrides() {
69        [new TestModule()]
70    }
71}
1 Indicate class under test
2 Injected by GriffonUnitRule given 1
3 Injected by GriffonUnitRule via JSR 330
4 Instantiates and configures a GriffonAplication for testing
5 Setup collaborators
6 Stimulus
7 Validate after waiting 2 seconds at most

At the heart we have the @TestFor 1 annotation and a JUnit4 rule: @GriffonUnitRule 4. These two key elements are responsible for injecting the required behavior to the test case. @TestFor identifies the type of component, a Controller in this case, that’s under test; it assumes a suitable private field 2 to be defined in the testcase. This field is used to inject the instance under test. The GriffonUnitRule is responsible for bootstrapping a barebones application and putting together all the required bindings. Notice that the test case can participate in dependency injection too 3.

Running tests requires executing the test task

$ ./gradlew test

A similar command can be invoked with Maven

$ mvn test

These are the basics for getting started with a Griffon project.

3. Application Overview

3.1. Configuration

It may seem odd that in a framework that embraces "convention-over-configuration" that we tackle this topic now, but since what configuration there is typically a one off, it is best to get it out the way.

3.1.1. Basic Configuration

For general configuration Griffon provides a file called griffon-app/conf/Config.groovy. This file uses Groovy’s ConfigSlurper which is very similar to Java properties files except it is pure Groovy hence you can re-use variables and use proper Java types!

Here’s a typical configuration file

griffon-app/conf/sample/swing/groovy/Config.groovy
 1package sample.swing.groovy
 2
 3application {
 4    title = 'Swing + Groovy'
 5    startupGroups = ['sample']
 6    autoShutdown = true
 7}
 8mvcGroups {
 9    // MVC Group for "sample"
10    'sample' {
11        model      = 'sample.swing.groovy.SampleModel'
12        view       = 'sample.swing.groovy.SampleView'
13        controller = 'sample.swing.groovy.SampleController'
14    }
15}

You can define this file using Java too

griffon-app/conf/sample/swing/java/Config.java
 1package sample.swing.java;
 2
 3import griffon.util.AbstractMapResourceBundle;
 4import griffon.util.CollectionUtils;
 5
 6import javax.annotation.Nonnull;
 7import java.util.Map;
 8
 9import static java.util.Arrays.asList;
10
11public class Config extends AbstractMapResourceBundle {
12    @Override
13    protected void initialize(@Nonnull Map<String, Object> entries) {
14        CollectionUtils.map(entries)
15            .e("application", CollectionUtils.map()
16                .e("title", "Swing + Java")
17                .e("startupGroups", asList("sample"))
18                .e("autoShutdown", true)
19            )
20            .e("mvcGroups", CollectionUtils.map()
21                .e("sample", CollectionUtils.map()
22                    .e("model", "sample.swing.java.SampleModel")
23                    .e("view", "sample.swing.java.SampleView")
24                    .e("controller", "sample.swing.java.SampleController")
25                )
26            );
27    }
28}

Or if you prefer properties files then do the following

griffon-app/resources/Config.properties
1application.title = Swing + Groovy
2application.startupGroups = sample
3application.autoShutdown = true
4mvcGroups.sample.model = sample.swing.groovy.SampleModel
5mvcGroups.sample.view = sample.swing.groovy.SampleView
6mvcGroups.sample.controller = sample.swing.groovy.SampleController

Take special note that this file must be placed under griffon-app/resources instead.

The application’s runtime configuration is available through the configuration property of the application instance.

3.1.2. Internationalization Support

Configuration files are i18n aware which means you can append locale specific strings to a configuration file, for example Config_de_CH.groovy. Locale suffixes are resolved from least to most specific; for a locale with language = 'de', country = 'CH' and variant = 'Basel' the following files are loaded in order

  • Config.groovy

  • Config.properties

  • Config_de.groovy

  • Config_de.properties

  • Config_de_CH.groovy

  • Config_de_CH.properties

  • Config_de_CH_Basel.groovy

  • Config_de_CH_Basel.properties

The current java.util.Locale is used to determine values for language, country and variant.

3.2. Metadata

The Griffon runtime keeps track of useful metadata that can be consumed by applications. The following sections describe them with more detail.

3.2.1. Application Metadata

Access to the application’s metadata file (application.properties) is available by querying the Metadata singleton. Here’s a snippet of code that shows how to setup a welcome message that displays the application’s name and version

griffon-app/views/sample/SampleView.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.core.env.Metadata
 5import griffon.metadata.ArtifactProviderFor
 6
 7@ArtifactProviderFor(GriffonView)
 8class SampleView {
 9    FactoryBuilderSupport builder
10
11    void initUI() {
12        Metadata meta = Metadata.current
13        builder.with {
14            application(pack: true,
15                title: application.configuration['application.title']) {
16                label "Hello, I'm ${meta['application.name']}-${meta['application.version']}"
17            }
18        }
19    }
20}

There are also a few helpful methods found in Metadata

  • getApplicationName() - same result as meta[application.name]

  • getApplicationVersion() - same result as meta[application.version]

3.2.2. Feature

A Feature is a boolean flag that determines if a capability is available to the application at runtime. Features are nothing more than a System property. They can be written as properties on Metadata too. If a feature key is found in both System properties and Metadata then the former takes precedence. Here’s an example of a module that decides if a specific binding should be applied over another

src/main/groovy/org/opendolphin/demo/DolphinModule.groovy
 1package org.opendolphin.demo
 2
 3import griffon.core.env.Feature
 4import griffon.core.injection.Module
 5import org.codehaus.griffon.runtime.core.injection.AbstractModule
 6import org.kordamp.jipsy.ServiceProviderFor
 7import org.opendolphin.core.client.comm.ClientConnector
 8import org.opendolphin.demo.injection.HttpClientConnectorProvider
 9import org.opendolphin.demo.injection.InMemoryClientConnectorProvider
10
11@ServiceProviderFor(Module)
12class DolphinModule extends AbstractModule {
13    @Override
14    protected void doConfigure() {
15        bind(ClientConnector)
16            .toProvider(InMemoryClientConnectorProvider)
17            .asSingleton()
18
19        Feature.withFeature('dolphin.remote') {
20            bind(ClientConnector)
21                .toProvider(HttpClientConnectorProvider)
22                .asSingleton()
23        }
24    }
25}

The remote option can be enabled when running the application with -Ddolphin.remote=true or adding the following entry to griffon-app/resources/application.properties

griffon-app/resources/application.properties
1dolphin.remote=true

3.2.3. Application Environment

A Griffon application can run in several environments, default ones being DEVELOPMENT, TEST and PRODUCTION. Applications can inspect its current running environment by means of the Environment enum.

The following example enhances the previous one by displaying the current running environment

griffon-app/views/sample/SampleView.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.core.env.Metadata
 5import griffon.core.env.Environment
 6import griffon.metadata.ArtifactProviderFor
 7
 8@ArtifactProviderFor(GriffonView)
 9class SampleView {
10    FactoryBuilderSupport builder
11
12    void initUI() {
13        Metadata meta = Metadata.current
14        builder.with {
15            application(pack: true,
16                title: application.configuration['application.title']) {
17                gridLayout cols: 1, rows: 2
18                label "Hello, I'm ${meta['application.name']}-${meta['application.version']}"
19                label "Current environment is ${Environment.current}"
20            }
21        }
22    }
23}

The default environment is DEVELOPMENT. A different value can be specified by setting a proper value for the griffon.env System property. The Environment class recognizes the following aliases

  • dev - short for development.

  • prod - short for production.

3.2.4. Griffon Environment

The GriffonEnvironment gives you access to the following values

  • Griffon version

  • Griffon build date & time

  • JVM version

  • OS version

Here’s an example displaying all values

griffon-app/views/sample/SampleView.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.core.env.Metadata
 5import griffon.core.env.Environment
 6import griffon.metadata.ArtifactProviderFor
 7import static griffon.core.env.GriffonEnvironment.*
 8
 9@ArtifactProviderFor(GriffonView)
10class SampleView {
11    FactoryBuilderSupport builder
12
13    void initUI() {
14        Metadata meta = Metadata.current
15        builder.with {
16            application(pack: true,
17                title: application.configuration['application.title']) {
18                gridLayout cols: 1, rows: 6
19                label "Hello, I'm ${meta['application.name']}-${meta['application.version']}"
20                label "Current environment is ${Environment.current}"
21                label "Griffon version is ${getGriffonVersion()}"
22                label "Build date/time is ${getBuildDateTime()}"
23                label "JVM version is ${getJvmVersion()}"
24                label "OS version is ${getOsVersion()}"
25            }
26        }
27    }
28}

3.3. Lifecycle

Every Griffon application goes through the same lifecycle phases no matter in which mode they are running, with the exception of applet mode where there is an additional phase due to the intrinsic nature of applets. The application’s lifecycle has been inspired by JSR-296, the Swing Application Framework.

Every phase has an associated lifecycle class that will be invoked at the appropriate time. Class names match each phase name; you’ll find them inside griffon-app/lifecycle.

3.3.1. Initialize

The initialization phase is the first to be called by the application’s life cycle. The application instance has just been created and its configuration has been read.

This phase is typically used to tweak the application for the current platform, including its Look & Feel.

The Initialize lifecycle handler will be called right after the configuration has been read but before addons and managers are initialized.

3.3.2. Startup

This phase is responsible for instantiating all MVC groups that have been defined in the application’s configuration and that also have been marked as startup groups in the same configuration file.

The Startup lifecycle handler will be called after all MVC groups have been initialized.

3.3.3. Ready

This phase will be called right after Startup with the condition that no pending events are available in the UI queue. The application’s main window will be displayed at the end of this phase.

3.3.4. Shutdown

Called when the application is about to close. Any artifact can invoke the shutdown sequence by calling shutdown() on the GriffonApplication instance.

The Shutdown lifecycle handler will be called after all ShutdownHandlers and event handlers interested in the ShutdownStart event.

3.3.5. Stop

This phase is only available when running on applet mode. It will be called when the applet container invokes destroy() on the applet instance.

3.4. Modules

Modules define components that can be injected using the JSR 330 API. Module definitions are highly influenced by the Guice 3.x API however they do not impose a strict dependency on Guice.

3.4.1. Module Definition

All modules must implement the Module interface shown next

1public interface Module {
2    @Nonnull
3    List<Binding<?>> getBindings();
4
5    void configure();
6}

They also require to have a well defined name. This can be achieved by annotating the module class with @javax.inject.Named. If a value for @javax.inject.Named is not defined then one will be automatically calculated using the fully qualified class name. For example

 1package org.example;
 2
 3import griffon.core.injection.Module;
 4import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 5import org.kordamp.jipsy.ServiceProviderFor;
 6
 7import javax.inject.Named;
 8
 9@ServiceProviderFor(Module.class)
10@Named
11public class CustomModule extends AbstractModule {
12    @Override
13    protected void doConfigure() {
14        // bindings
15    }
16}

Results in a module named org.example.custom, where as the following definition

 1package org.example;
 2
 3import griffon.core.injection.Module;
 4import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 5import org.kordamp.jipsy.ServiceProviderFor;
 6
 7import javax.inject.Named;
 8
 9@ServiceProviderFor(Module.class)
10@Named("custom")
11public class CustomModule extends AbstractModule {
12    @Override
13    protected void doConfigure() {
14        // bindings
15    }
16}

Results in a module simply called custom. A module may depend on another module; when that’s the case then the bindings of this module are processed after their dependencies. This allows dependent modules to override bindings made by their dependencies. Module dependencies are specified using the @griffon.inject.DependsOn annotation, like so

 1import griffon.core.injection.Module;
 2import griffon.inject.DependsOn;
 3import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 4import org.kordamp.jipsy.ServiceProviderFor;
 5
 6import javax.inject.Named;
 7
 8@ServiceProviderFor(Module.class)
 9@DependsOn("parent-module")
10@Named
11public class CustomModule extends AbstractModule {
12    @Override
13    protected void doConfigure() {
14        // bindings
15    }
16}

3.4.2. Module Configuration

Modules perform their duty by defining bindings, of which there are 4 kinds

Bindings have an scope, either singleton or prototype. InstanceBinding is the only binding that has singleton as implicit scope; this setting can’t be changed. All bindings accept a classifier or an classifier_type. Classifiers are annotations that have been annotated with @javax.inject.Qualifier. You must specify one or the other but not both; classifier has precedence over classifier_type.

Instance Binding

This binding is used to define explicit and eager singletons, for example

bind(Calculator.class)
    .toInstance(new GroovyShellCalculator());
Target Binding

This binding enables you to specify the source type and target type. Typically the source is an interface while the target is a concrete implementation. If the target is omitted then the binding assumes the source to be a concrete type.

bind(Calculator.class)
    .to(GroovyShellCalculator.class);
Provider Binding

This binding behaves like a factory, giving you the power to decide with extreme precision how the instance should be created. Bindings of this type require an eager instance of type javax.inject.Provider.

bind(Calculator.class)
    .toProvider(new CalculatorProvider());
Provider Type Binding

Finally we have the most flexible binding as it lets you specify a javax.injectProvider type that will be used to lazily instantiate the provider before obtaining the type instance.

bind(Calculator.class)
    .toProvider(CalculatorProvider.class);

3.4.3. The Guice Injector

TBD

3.5. Shutdown Handlers

Applications have the option to let particular artifacts abort the shutdown sequence and/or perform a task while the shutdown sequence is in process. Artifacts that desire to be part of the shutdown sequence should implement the ShutdownHandler interface and register themselves with the application instance.

The contract of a ShutdownHandler is very simple

  • boolean canShutdown(GriffonApplication application) - return false to abort the shutdown sequence.

  • void onShutdown(GriffonApplication application) - called if the shutdown sequence was not aborted.

ShutdownHandlers will be called on the same order as they were registered.

3.6. Startup Arguments

Command line arguments can be passed to the application and be accessed by calling getStartupArgs() on the application instance. This will return a copy of the args (if any) defined at the command line.

Here’s a typical example of this feature in development mode

src/main/groovy/sample/ApplicationEventHandler.groovy
 1package sample
 2
 3import griffon.core.GriffonApplication
 4import griffon.core.event.EventHandler
 5
 6class ApplicationEventHandler implements EventHandler {
 7    void onBootstrapStart(GriffonApplication application) {
 8        println application.startupArgs
 9    }
10}

Arguments must be defined in the build file if using Gradle

build.gradle
run {
    args = ['one', 'two']
}

Running the application with run command results in an output similar to the following

$ gradle run
:compileJava
:compileGroovy
:processResources
:classes
:run
// logging statements elided
[one, two]

4. The MVC Pattern

All Griffon applications operate with a basic unit called the MVC group. An MVC group is comprised of 3 member parts: Models, Views and Controllers. However it is possible to add (or even remove) members from an MVC group by carefully choosing a suitable configuration.

MVC groups configuration is setup in Config.groovy located inside griffon-app/conf (or Config.java if using Java as main language). This file holds an entry for every MVC group that the application has (not counting those provided by Addons).

Here’s how a typical MVC group configuration looks like

1mvcGroups {
2    // MVC Group for "sample"
3    'sample' {
4        model      = 'sample.SampleModel'
5        view       = 'sample.SampleView'
6        controller = 'sample.SampleController'
7    }
8}

The definition order is very important, it determines the order in which each member will be initialized. In the previous example both model and view will be initialized before the controller. Do not mistake initialization for instantiation, as initialization relies on calling mvcGroupInit() on the group member.

MVC group configurations accept a special key that defines additional configuration for that group, as it can be seen in the following snippet

 1mvcGroups {
 2    // MVC Group for "sample"
 3    'sample' {
 4        model      = 'sample.SampleModel'
 5        view       = 'sample.SampleView'
 6        controller = 'sample.SampleController'
 7    }
 8
 9    // MVC Group for "foo"
10    'foo' {
11        model      = 'sample.FooModel'
12        view       = 'sample.FooView'
13        controller = 'sample.FooController'
14        config {
15            key = 'bar'
16        }
17    }
18}

Values placed under this key become available to MVC members during the call to mvcGroupInit(), as part of the arguments sent to that method. Here’s how the FooController can reach the key defined in the configuration

1@ArtifactProviderFor(GriffonController)
2class FooController {
3    void mvcGroupInit(Map<String, Object> args) {
4        println args.configuration.config.key
5    }
6}

While being able to set additional values under this key is certainly an advantage it would probably be better if those values could be mutated or tweaked, probably treating them as variables, effectively making a group configuration work as a template. For that we’ll have to discuss the MVCGroupManager first.

4.1. The MVCGroupManager

This class is responsible for holding the configuration of all MVC groups no matter how they were defined, which can be either in Config.groovy or in an addon descriptor.

During the startup sequence an instance of MVCGroupManager will be created and initialized. Later the application will instruct this instance to create all startup groups as required. MVCGroupManager has a handful set of methods that deal with MVC group configuration alone; however those that deal with group instantiation come in 3 versions, with 2 flavors each (one Groovy, the other Java friendly).

Locating a group configuration is easily done by specifying the name you’re interested in finding

MVCGroupConfiguration configuration = application.mvcGroupManager.findConfiguration('foo')

Once you have a configuration reference you can instantiate a group with it by calling any of the variants of the create method

MVCGroupConfiguration configuration = application.mvcGroupManager.findConfiguration('foo')
MVCGroup group1 = configuration.create('foo1')
MVCGroup group2 = configuration.create('foo2', [someKey: 'someValue'])
// the following will make the group's id match its name
MVCGroup group3 = configuration.create()
MVCGroup group4 = configuration.create(someKey: 'someValue')

Be aware that creating groups with the same name is usually not a good idea. The default MVCGroupManager will complain when this happens and will automatically spit out an exception. This behavior may be changed by setting a configuration key in Config.groovy

griffon.mvcid.collision = 'warning' // accepted values are 'warning', 'exception' (default)

The manager will log a warning and destroy the previously existing group before instantiating the new one when warning is the preferred strategy .

Now, even though you can create group instances based on their configurations the preferred way is to call any of createMVCGroup(), buildMVCGroup() or withMVCGroup() methods. Any class annotated with the @griffon.transform.MVCAware will also gain access to these methods.

Groups will be available by id regardless of how they were instantiated. You can ask the MVCGroupManager for a particular group at any time, for example

def g1 = application.mvcGroupManager.groups.foo1
def g2 = application.mvcGroupManager.findGroup('foo1')
def g3 = application.mvcGroupManager.foo1
assert g1 == g2
assert g1 == g3

It’s also possible to query all models, views, controllers and builders on their own. Say you’d want to inspect all currently instantiated models, this is how it can be done

application.mvcGroupManager.models.each { model ->
    // do something with model
}

4.2. MVC Groups

Now that we know which are the different ways to instantiate MVC groups we can go back to customizing them.

The simplest way is to pass in new values as part of the arguments map that mvcGroupInit() receives, for example

MVCGroup group = application.mvcGroupManager.buildMVCGroup('foo', [key: 'foo'])

However is you wish to use the special config key that every MVC group configuratio may have then you must instantiate the group in the following way

MVCGroupConfiguration configuration = application.mvcGroupManager
                                          .cloneMVCConfiguration('foo', [key: 'someValue'])
MVCGroup group = configuration.create()

Note that you can still send custom arguments to the create() method.

4.2.1. Configuring MVC groups

The following options are available to all MVC groups as long as you use the config key.

Disabling Lifecycle Events

Every MVC group triggers a few events during the span of its lifetime. These events will be sent to the event bus even if no component is interested in handling them. There may be times when you don’t want these events to be placed in the event bus in order to speed up group creation/destruction. Use the following configuration to gain this effect:

 1mvcGroups {
 2    // MVC Group for "sample"
 3    'sample' {
 4        model      = 'sample.SampleModel'
 5        view       = 'sample.SampleView'
 6        controller = 'sample.SampleController'
 7        config {
 8            events {
 9                lifecycle = false
10            }
11        }
12    }
13}

The following events will be disabled with this setting:

Disabling Instantiation Events

The Griffon runtime will trigger an event for every artifact it manages. As with the previous events this one will be sent to the event bus even if no component handles it. Skipping publication of this event may result in a slight increase of speed during group instantiation. Use the following configuration to gain this effect:

 1mvcGroups {
 2    // MVC Group for "sample"
 3    'sample' {
 4        model      = 'sample.SampleModel'
 5        view       = 'sample.SampleView'
 6        controller = 'sample.SampleController'
 7        config {
 8            events {
 9                instantiation = false
10            }
11        }
12    }
13}

The following events will be disabled with this setting:

Disabling Destruction Events

This is the counterpart of the NewInstance event. Skipping publication of this event may result in a slight increase of speed when a group or any artifact instance is destroyed. Use the following configuration to gain this effect:

 1mvcGroups {
 2    // MVC Group for "sample"
 3    'sample' {
 4        model      = 'sample.SampleModel'
 5        view       = 'sample.SampleView'
 6        controller = 'sample.SampleController'
 7        config {
 8            events {
 9                destruction = false
10            }
11        }
12    }
13}

The following events will be disabled with this setting:

Disabling Controllers as Application Event Listeners

Controllers are registered as application event handlers by default when a group is instantiated. This makes it very convenient to have them react to events placed in the application’s event bus. However you may want to avoid this automatic registration altogether, as it can lead to performance improvements. You can disable this feature with the following configuration:

 1mvcGroups {
 2    // MVC Group for "sample"
 3    'sample' {
 4        model      = 'sample.SampleModel'
 5        view       = 'sample.SampleView'
 6        controller = 'sample.SampleController'
 7        config {
 8            events {
 9                listener = false
10            }
11        }
12    }
13}

You can still manually register a controller as an application event handler at any time, with the caveat that it’s now your responsibility to unregister it when the time is appropriate, most typically during the group’s destroy sequence when mvcGroupDestroy() is invoked.

4.3. The @MVCAware Transformation

Any component may gain the ability to create and destroy MVC groups through a MVCGroupManager instance. You only need annotate the class with @griffon.transform.MVCAware and it will automatically gain all methods exposed by MVCHandler.

This feature is just a shortcut to avoid reaching for the application instance from objects that do not hold a reference to it.

Here’s an example of a custom bean that’s able to work with MVC groups

1@griffon.transform.MVCAware
2class Bean {
3}

This class can be used in the following way

1class SampleService {
2    @Inject Bean bean
3
4    void buildSecondary(String groupName) {
5        def (m, v, c) = bean.createMVCGroup(groupName)
6        // do something with m, v and c
7    }
8}

5. Models

This chapter describe models and all binding options.

Models are very simple in nature. Their responsibility is to hold data that can be used by both Controller and View to communicate with each other. In other words, Models are not equivalent to domain classes.

5.1. Swing Binding

Binding in Griffon is achieved by leveraging Java Beans’ java.beans.PropertyChangeEvent and their related classes, thus binding will work with any class that fires this type of event, regardless of its usage of @griffon.transform.Observable or not.

TBD

5.2. JavaFX Binding

TBD

5.3. The @Observable AST Transformation

The @griffon.transform.Observable transformation will inject the behavior of Observable into the annotated class. It basically injects an instance of java.beans.PropertyChangeSupport and all methods required to make the model an observable class. It will also make sure that a java.beans.PropertyChangeEvent is fired for each observable property whenever said property changes value.

The following is a list of all methods added by @griffon.transform.Observable

  • void addPropertyChangeListener(PropertyChangeListener listener)

  • void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)

  • void removePropertyChangeListener(PropertyChangeListener listener)

  • void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)

  • PropertyChangeListener[] getPropertyChangeListeners()

  • PropertyChangeListener[] getPropertyChangeListeners(String propertyName)

  • void firePropertyChange(String propertyName, Object oldValue, Object newValue)

5.4. The @Vetoable AST Transformation

The @griffon.transform.Vetoable transformation will inject the behavior of Vetoable into the annotated class. It basically injects an instance of java.beans.VetoableChangeSupport and all methods required to make the model a vetoable class. It will also make sure that a java.beans.PropertyChangeEvent is fired for each vetoable property whenever said property changes value.

The following is a list of all methods added by @griffon.transform.Vetoable

  • void addVetoableChangeListener(VetoableChangeListener listener)

  • void addVetoableChangeListener(String propertyName, VetoableChangeListener listener)

  • void removeVetoableChangeListener(VetoableChangeListener listener)

  • void removeVetoableChangeListener(String propertyName, VetoableChangeListener listener)

  • VetoableChangeListener[] getVetoableChangeListeners()

  • VetoableChangeListener[] getVetoableChangeListeners(String propertyName)

  • void fireVetoableChange(String propertyName, Object oldValue, Object newValue)

5.5. The @PropertyListener AST Transformation

The @griffon.transform.PropertyListener helps you to register PropertyChangeListeners without so much effort. The following code

 1import griffon.transform.PropertyListener
 2import griffon.transform.Observable
 3import griffon.core.artifact.GriffonModel
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonModel)
 6@PropertyListener(snoopAll)
 7class SampleModel {
 8    def controller
 9    @Observable String name
10
11    @Observable
12    @PropertyListener({controller.someAction(it)})
13    String lastname
14
15    def snoopAll = { evt -> ... }
16}

is equivalent to this one

 1import java.beans.PropeetyChangeListener
 2import griffon.transform.Observable
 3import griffon.core.artifact.GriffonModel
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonModel)
 6@PropertyListener(snoopAll)
 7class SampleModel {
 8    def controller
 9    @Observable String name
10    @Observable String lastname
11
12    def snoopAll = { evt -> ... }
13
14    SampleModel() {
15        addPropertyChangeListener(snoopAll as PropertyChangeListener)
16        addPropertyChangeListener('lastname', {
17            controller.someAction(it)
18        } as PropertyChangeListener)
19    }
20}

@griffon.transform.PropertyListener accepts the following values

  • in-place definition of a closure

  • reference of a closure property defined in the same class

  • a List of any of the previous two

6. Controllers

Controllers are the entry point for your application’s logic. Each controller has access to their model and view instances from their respective MVC group.

Controller actions are defined as public methods on a controller class, for example in Groovy you’d write

 1package sample
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class SampleController {
 7    def model
 8
 9    void click() {
10        model.clickCount++
11    }
12}

The corresponding code for Java 8 would be

 1package sample;
 2
 3import griffon.core.artifact.GriffonController;
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController.class)
 6public class SampleController {
 7    private SampleModel model;
 8
 9    public void setModel(SampleModel model) {
10        this.model = model;
11    }
12
13    public void click() {
14        runInsideUIAsync(() -> {
15            model.setClickCount(model.getClickCount() + 1);
16        });
17    }
18}

Actions must follow these rules in order to be considered as such:

  • must have public visibility modifier.

  • name does not match an event handler, i.e, it does not begin with on.

  • must pass GriffonClassUtils.isPlainMethod().

  • must have void as return type.

The application’s ActionManager will automatically configure an Action instance for each matching controller action. These Action instances can be later used within Views to link them to UI components. The following example shows a Swing View making use of the configured clickAction from SampleController

 1package sample
 2
 3import griffon.core.artifact.GriffonView
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonView)
 6class SampleView {
 7    FactoryBuilderSupport builder
 8    SampleModel model
 9
10    void initUI() {
11        builder.with {
12            application(title: 'Clicker', pack: true)) {
13                button(clickAction, label: bind { model.clickCount })
14            }
15        }
16    }
17}

Controllers can perform other tasks too:

  • listen to application events.

  • react to MVC initialization/destruction via a pair of methods (mvcGroupInit(), mvcGroupDestroy()).

  • hold references to services.

  • participate in dependency injection (members must be annotated with @javax.inject.Inject).

6.1. Actions and Threads

A key aspect that you must always keep in mind is proper threading. Often times controller actions will be bound in response to an event driven by the UI. Those actions will usually be invoked in the same thread that triggered the event, which would be the UI thread. When that happens you must make sure that the executed code is short and that it quickly returns control to the UI thread. Failure to do so may result in unresponsive applications.

The following example is the typical usage one finds out there

 1package sample
 2
 3import groovy.sql.Sql
 4import java.awt.event.ActionEvent
 5import griffon.core.artifact.GriffonController
 6
 7@griffon.metadata.ArtifactProviderFor(GriffonController)
 8class BadController {
 9    def model
10
11    void badAction(ActionEvent evt = null) {
12        def sql = Sql.newInstance(
13            app.config.datasource.url,
14            model.username,
15            model.password,
16            app.config.datasource.driver
17        )
18        model.products.clear()
19        sql.eachRow("select * from products") { product ->
20            model.products << [product.id, product.name, product.price]
21        }
22        sql.close()
23    }
24}

What’s wrong with this code? It’s very likely that this action is triggered by clicking on a button, in which case its body will be executed inside the UI thread. This means the database query will be executed on the UI thread too. The model is also updated, once could assume the model is bound to an UI component. This update should happen inside the UI thread, but clearly that’s not what’s happening here.

In order to simplify things the Griffon runtime (via the ActionManager) assumes by default that all actions will be invoked outside of the UI thread. This solves the first problem, that of realizing a database operation on the wrong thread. The second problem, updating the model, can be solved in the following manner

 1package sample
 2
 3import groovy.sql.Sql
 4import java.awt.event.ActionEvent
 5import griffon.core.artifact.GriffonController
 6
 7@griffon.metadata.ArtifactProviderFor(GriffonController)
 8class GoodController {
 9    def model
10
11    void goodAction(ActionEvent evt = null) {                           (1)
12        def sql = Sql.newInstance(
13            app.config.datasource.url,
14            model.username,
15            model.password,
16            app.config.datasource.driver
17        )
18
19        try {
20            List results = []
21            sql.eachRow("select * from products") { product ->
22                results << [product.id, product.name, product.price]
23            }
24
25            runInsideUIAsync {                                          (2)
26                model.products.clear()
27                model.products.addAll(results)
28            }
29        } finally {
30            sql.close()
31        }
32    }
33}
1 Executed outside the UI thread
2 Go back inside the UI thread

There are other options at your disposal to make sure the code behaves properly according to the specific threading rules of a particular UI toolkit. These options are covered in the threading chapter.

6.2. The ActionManager

Controller actions may automatically be wrapped and exposed as toolkit specific actions; this greatly simplifies how actions can be configured based on i18n concerns.

At the heart of this feature lies the ActionManager. This component is responsible for instantiating, configuring and keeping references to all actions per controller. It will automatically harvest all action candidates from a Controller once it has been instantiated. Each action has all of its properties configured following this strategy:

  • match <controller.class.name>.action.<action.name>.<key>

  • match application.action.<action.name>.<key>

<action.name> should be properly capitalized. In other words, you can configure action properties specifically per Controller or application wide. Available keys are

Table 1. Swing
Key Type Default Value

name

String

Natural name minus the Action suffix

accelerator

String

undefined

long_description

String

undefined

short_description

String

undefined

mnemonic

String

undefined

small_icon

String

undefined

large_icon

String

undefined

enabled

boolean

true

selected

boolean

false

Table 2. JavaFX
Key Type Default Value

name

String

Natural name minus the Action suffix

accelerator

String

undefined

long_description

String

undefined

short_description

String

undefined

mnemonic

String

undefined

small_icon

String

undefined

large_icon

String

undefined

enabled

boolean

true

selected

boolean

false

Table 3. Pivot
Key Type Default Value

name

String

Natural name minus the Action suffix

description

String

undefined

enabled

boolean

true

Table 4. Lanterna
Key Type Default Value

name

String

Natural name minus the Action suffix

Icon keys should point to an URL available in the classpath.

Values must be placed in resources files following the internationalization guidelines.

6.2.1. Configuration Examples

The following Controller defines 2 actions, one of them uses the Action suffix because its name clashes with a known Java keyword.

 1package sample
 2
 3import java.awt.event.ActionEvent
 4
 5import griffon.core.artifact.GriffonController
 6
 7@griffon.metadata.ArtifactProviderFor(GriffonController)
 8class SampleController {
 9    void close(ActionEvent evt) { ... }
10    void newAction(ActionEvent evt) { ... }
11}

The ActionManager will generate and configure the following actions:

  • newAction

  • closeAction

The following keys are expected to be available in the application’s i18n resources (i.e. griffon-app/i18n/messages.properties)

1sample.SampleController.action.New.name = New
2sample.SampleController.action.Open.name = Open
3sample.SampleController.action.Close.name = Close
4sample.SampleController.action.Delete.name = Delete
5# additional keys per action elided

In the case that you’d like the close action to be customized for all controllers, say using the Spanish language, then you’ll have to provide a file named griffon-app/i18n/messages_es.properties with the following keys

1application.action.Close.name = Cerrar

Make sure to remove any controller specific keys when reaching for application wide configuration.

6.3. Action Interceptors

ActionInterceptors open a new set of possibilities by allowing developers and addon authors to define code that should be executed before and after any controller action is invoked by the framework. For example, you may want to protect the execution of a particular action given specific permissions; the shiro plugin uses annotations that are handled by an ActionInterceptor, like this

 1import griffon.core.artifact.GriffonController
 2import griffon.metadata.ArtifactProviderFor
 3import griffon.plugins.shiro.annotation.*
 4
 5@ArtifactProviderFor(GriffonController)
 6@RequiresAuthentication
 7class PrinterController {
 8   @RequiresPermission('printer:print')
 9   void print () { ... }
10
11   @RequiresRoles('administrator')
12   void configure() { ... }
13}

The scaffolding plugin on the other hand modifies the arguments sent to the action. Take the following snippet for example

 1import griffon.core.artifact.GriffonController
 2import griffon.metadata.ArtifactProviderFor
 3import griffon.plugins.shiro.annotation.*
 4import org.apache.shiro.authc.UsernamePasswordToken
 5impprt org.apache.shiro.subject.Subject
 6import javax.swing.JOptionPane
 7
 8import javax.inject.Inject
 9
10@ArtifactProviderFor(GriffonController)
11class StrutsController {
12    @Inject
13    private Subject subject
14
15    @RequiresGuest
16    void login(LoginCommandObject cmd) {
17        try {
18            subject.login(new UsernamePasswordToken(cmd.username, cmd.password))
19        } catch(Exception e) {
20            JOptionPane.showMessageDialog(
21                app.windowManager.findWindow('mainWindow'),
22                'Invalid username and/or password',
23                'Security Failure', JOptionPane.ERROR_MESSAGE)
24        }
25    }
26
27    @RequiresAuthentication
28    void logout() {
29        subject.logout()
30    }
31}

Note that the login action requires an instance of LoginCommandObject. The scaffolding plugin is aware of this fact; it will create an instance of said class, wire up an scaffolded view in a dialog and present it to the user. The LoginCommandObject instance will be set as the action’s arguments if it validates successfully, otherwise action execution is aborted.

6.3.1. Implementing an Action Interceptor

Action interceptors must implement the ActionInterceptor interface. There’s a handy base class (org.codehaus.griffon.runtime.core.controller.AbstractActionInterceptor) that provides sensible defaults. Say you’d want to know how much time it took for an action to be executed, also if an exception occurred during its execution. This interceptor could be implemented as follows

 1package com.acme
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.core.controller.ActionExecutionStatus
 5import org.codehaus.griffon.runtime.core.controller.AbstractActionInterceptor
 6
 7import javax.inject.Named
 8
 9@Named('tracer')
10class TracerActionInterceptor extends AbstractActionInterceptor {
11    private final Map<String, Long> TIMES = [:]
12
13    Object[] before(GriffonController controller, String actionName, Object[] args) {
14        TIMES[qualifyActionName(controller, actionName)] = System.currentTimeMillis()
15        return super.before(controller, actionName, args)
16    }
17
18    void after(ActionExecutionStatus status, GriffonController controller, String actionName, Object[] args) {
19        String qualifiedActionName = qualifyActionName(controller, actionName)
20        long time = System.currentTimeMillis() - TIMES[qualifiedActionName]
21        println("Action ${qualifiedActionName} took ${time} ms [${status}]")
22    }
23}

The ActionInterceptor interface defines a handful of methods that are invoked by the ActionManager at very specific points during the lifetime and execution of controller actions.

void configure(GriffonController controller, String actionName, Method method)

The configure() method is called during the configuration phase, when the ActionManager creates the actions. This method is called once in the lifetime of an action.

Object[] before(GriffonController controller, String actionName, Object[] args)

The before() method is executed every time an action is about to be invoked. This method is responsible for adjusting the arguments (if needed) or aborting the action execution altogether. Any exception thrown by an interceptor in this method will halt action execution however only AbortActionExecution is interpreted as a graceful abort.

boolean exception(Exception exception, GriffonController controller, String actionName, Object[] args)

The exception() method is invoked only when an exception occurred during the action’s execution. Implementors must return true if the exception was handled successfully. The exception will be rethrown by the ActionManager if no interceptor handled the exception. This happens as the last step of the action interception procedure.

void after(ActionExecutionStatus status, GriffonController controller, String actionName, Object[] args)

The after() method is called after an action has been executed. Any exceptions occurred during the action’s execution should have been handled by exception(). The status argument specifies if the action was successfully executed (OK), if it was aborted by an interceptor (ABORTERD) or if an exception occurred during its execution (EXCEPTION).

Action interceptors can participate in Dependency Injection.

6.3.2. Configuration

Action Interceptors must be registered within a Module in order to be picked up by the ActionManager. The following example shows how the previous TracerActionInterceptor can be registered in a Module

src/main/com/acme/ApplicationModule.groovy
 1package com.acme
 2
 3import griffon.core.injection.Module
 4import griffon.core.controller.ActionInterceptor
 5import org.codehaus.griffon.runtime.core.injection.AbstractModule
 6import org.kordamp.jipsy.ServiceProviderFor
 7
 8import javax.inject.Named
 9
10@ServiceProviderFor(Module)
11@Named('application')
12public class ApplicationModule extends AbstractModule {
13    @Override
14    protected void doConfigure() {
15        bind(ActionInterceptor)
16            .to(TracerActionInterceptor)
17            .asSingleton()
18    }
19}

An Interceptor may define a dependency on another interceptor; use the @griffon.inject.DependsOn annotation to express the relationship.

It’s also possible to globally override the order of execution of interceptors, or define and order when interceptors are orthogonal. Take for example the security interceptor provided by the shiro plugin and the scaffolding interceptor provided by scaffolding plugin. These interceptors know nothing about each other however security should be called before scaffolding. This can be accomplished by adding the following snippet to Config.groovy

griffon.controller.action.interceptor.order = ['security', 'scaffolding']

7. Services

Services are responsible for the application logic that does not belong to a single controller. They are meant to be treated as singletons, injected to MVC members by following a naming convention. Services must be located inside the griffon-app/services

Let’s say you want to create a Math service. A trivial implementation of an addition operation performed by the MathService would look like the following snippet

griffon-app/services/sample/MathService.groovy
1package sample
2
3import griffon.core.artifact.GriffonService
4
5@griffon.metadata.ArtifactProviderFor(GriffonService)
6class MathService {
7    def addition(a, b) { a + b }
8}

Using this service from a Controller is a straight forward task, you just have to define an injection point and annotated it with @javax.inject.Inject

griffon-app/controllers/sample/SampleService.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class SampleController
 7    SampleModel model
 8
 9    @javax.inject.Inject
10    private MathService mathService
11
12    void calculate(evt = null) {
13        model.result = mathService.addition model.a, model.b
14    }
15}

Given that services are inherently treated as singletons they are also automatically registered as application event listeners. Be aware that services will be instantiated lazily which means that some events might not reach a particular service if it has not been instantiated by the framework by the time of event publication. It’s also discouraged to use Groovy’s @Singleton annotation on a Service class as it will cause trouble with the automatic singleton management Griffon has in place.

7.1. Life Cycle

Services do not have a well-defined lifecycle like other MVC artifacts because they do implement the GriffonMvcArtifact interface. However you may annotate service methods with @javax.annotation.PostConstruct and @javax.annotation.PreDestroy.

The Griffon runtime guarantees that methods annotated with @javax.annotation.PostConstruct will be invoked right after the instance has been created by the Injector. Likewise it will invoke all methods annotated with @javax.annotation.PreDestroy when the Injector is closed.

Only one method of the same class can be annotated with @javax.annotation.PostConstruct or @javax.annotation.PreDestroy.

8. Views

8.1. The WindowManager

Although the following API refers directly to Swing in all examples it’s possible to use the WindowManager with other toolkits such as JavaFX, Pivot and Lanterna as there are specific WindowManager implementations plus helper classes for those UI toolkits too.

The WindowManager class is responsible for keeping track of all the windows managed by the application. It also controls how these windows are displayed (via a pair of methods: show, hide). WindowManager relies on an instance of WindowDisplayHandler to actually show or hide a window. The default implementation simple shows and hide windows directly, however you can change this behavior by setting a different implementation of WindowDisplayHandler on the application instance.

8.1.1. WindowManager DSL

This configuration can be set in griffon-app/conf/Config.groovy file, here is how it looks

griffon-app/conf/Config.groovy
 1windowManager {
 2    myWindowName = [
 3        show: {name, window -> ... },
 4        hide: {name, window -> ... }
 5    ]
 6    myOtherWindowName = [
 7        show: {name, window -> ... }
 8    ]
 9}

The name of each entry must match the value of the Window’s name: property (if supported) or the name used to register the Window with the WindowManager. Each entry may have any the following options

show

Used to show the window to the screen. It must be a closure that takes two parameters: the name of the window and the window to be displayed.

hide

Used to hide the window from the screen. It must be a closure that takes two parameters: the name of the window and the window to be hidden.

handler

A custom WindowDisplayHandler instance.

You must use CallableWithArgs instead of closures if using the Java version of the Config file.

The first two options have priority over the third one. If one is missing then the WindowManager will invoke the default behavior. There is one last option that can be used to override the default behavior provided to all windows

griffon-app/conf/Config.groovy
1windowManager {
2    defaultHandler = new MyCustomWindowDisplayHandler()
3}

You can go a bit further by specifying a global show or hide behavior as shown in the following example

griffon-app/conf/Config.groovy
 1windowManager {
 2    defaultShow: {name, window -> ... },
 3    myWindowName = [
 4        show: {name, window -> ... },
 5        hide: {name, window -> ... }
 6    ]
 7    myOtherWindowName = [
 8        show: {name, window -> ... }
 9    ]
10}

8.1.2. Starting Window

By default the WindowManager picks the first available window from the managed windows list to be the starting window. However, this behavior can be configured, by means of the WindowManager DSL. Simply specify a value for windowManager.startingWindow, like this

griffon-app/conf/Config.groovy
1windowManager {
2    startingWindow = 'primary'
3}

This configuration flag accepts two types of values:

  • a String that defines the name of the Window. You must make sure the Window has a matching name property or was attached to the WindowManager with the same name

  • a Number that defines the index of the Window in the list of managed windows.

If no match is found then the default behavior will be executed.

8.1.3. Custom WindowDisplayHandlers

The following example shows how you can center on screen all managed windows

src/main/groovy/sample/CenteringWindowDisplayHandler.groovy
 1package sample
 2
 3import org.codehaus.griffon.runtime.swing.DefaultSwingWindowDisplayHandler
 4
 5import javax.annotation.Nonnull
 6import java.awt.Window
 7
 8import static griffon.swing.support.SwingUtils.centerOnScreen
 9
10class CenteringWindowDisplayHandler extends DefaultSwingWindowDisplayHandler {
11    @Override
12    void show(@Nonnull String name, @Nonnull Window window) {
13        centerOnScreen(window)
14        super.show(name, window)
15    }
16}

You can register CenteringWindowDisplayHandler using the WindowManager DSL. Alternatively you may use a Module to register the class/instance.

src/main/groovy/sample/ApplicationModule.groovy
 1package sample
 2
 3import griffon.core.injection.Module
 4import griffon.inject.DependsOn
 5import griffon.swing.SwingWindowDisplayHandler
 6import org.codehaus.griffon.runtime.core.injection.AbstractModule
 7import org.kordamp.jipsy.ServiceProviderFor
 8
 9import static griffon.util.AnnotationUtils.named
10
11@DependsOn('swing')
12@ServiceProviderFor(Module)
13class ApplicationModule extends AbstractModule {
14    @Override
15    protected void doConfigure() {
16        bind(SwingWindowDisplayHandler)
17            .withClassifier(named('defaultWindowDisplayHandler'))
18            .to(CenteringWindowDisplayHandler)
19            .asSingleton()
20    }
21}

This example is equivalent to defining a WindowDisplayHandler for all windows. You may target specific windows, by define multiple bindings, making sure that the name of the classifier matches the window name. Notice the explicit dependency on the swing module. If this dependency is left out it’s very likely that the WindowManager will fail to pick the correct WindowDisplayHandler.

8.2. BuilderCustomizers

Recall from the ConsoleView sample application that you can use a Groovy DSL for building View components when the main language of the application is Groovy. This DSL is configured with default nodes that are tightly coupled with the chosen UI toolkit.

8.2.1. Extending the UI DSL

The UI DSL can be extended by registering new BuilderCustomizers with the application using a Module. The BuilderCustomizer instance defines methods that can be used to configure every aspect of groovy.factory.FactoryBuilderSupport. Here’s an example of the miglayout-swing extension

src/main/java/griffon/builder/swing/MiglayoutSwingBuilderCustomizer.java
 1package griffon.builder.swing;
 2
 3import griffon.inject.DependsOn;
 4import groovy.swing.factory.LayoutFactory;
 5import net.miginfocom.swing.MigLayout;
 6import groovy.util.Factory;
 7import org.codehaus.griffon.runtime.groovy.view.AbstractBuilderCustomizer;
 8
 9import javax.inject.Named;
10import java.util.LinkedHashMap;
11import java.util.Map;
12
13@DependsOn("swing")
14@Named("miglayout-swing")
15public class MiglayoutSwingBuilderCustomizer extends AbstractBuilderCustomizer {
16    public MiglayoutSwingBuilderCustomizer() {
17        Map<String, Factory> factories = new LinkedHashMap<>();
18        factories.put("migLayout", new LayoutFactory(MigLayout.class));
19        setFactories(factories);
20    }
21}

This customizer registers a single factory, miglayout. It also defines a dependency on the swing customizer in order for its customizations to be applied after swing's. The customizer is registered using the following Module.

src/main/java/org/codehaus/griffon/runtime/miglayout/MiglayoutSwingGroovyModule.java
 1package org.codehaus.griffon.runtime.miglayout;
 2
 3import griffon.builder.swing.MiglayoutSwingBuilderCustomizer;
 4import griffon.core.injection.Module;
 5import griffon.inject.DependsOn;
 6import griffon.util.BuilderCustomizer;
 7import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 8import org.kordamp.jipsy.ServiceProviderFor;
 9
10import javax.inject.Named;
11
12@DependsOn("swing-groovy")
13@Named("miglayout-swing-groovy")
14@ServiceProviderFor(Module.class)
15public class MiglayoutSwingGroovyModule extends AbstractModule {
16    @Override
17    protected void doConfigure() {
18        bind(BuilderCustomizer.class)
19            .to(MiglayoutSwingBuilderCustomizer.class)
20            .asSingleton();
21    }
22}

This module is loaded before swing-groovy, as per the @griffon.inject.DependsOn definition; thus ensuring that the customizations from this customizer are applied after.

8.2.2. Default DSL Nodes

The griffon-groovy dependency delivers the following nodes

Root

Identifies the top level node of a secondary View script. View scripts are expected to return the top level node, however there may be times when further customizations prevent this from happening, for example wiring up a custom listener. When that happens the result has to be made explicit otherwise the script will return the wrong value. Using the root() node avoids forgetting this fact while also providing an alias for the node.

griffon-app/views/com/acme/Secondary.groovy
1root(
2    tree(id: 'mytree')
3)
4
5mytree.addTreeSelectionModel(new DefaultTreeSelectionModel() { /* ... */ })
griffon-app/views/com/acme/PrimaryView.groovy
 1package com.acme
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class PrimaryView {
 8    def builder
 9
10    void initUI() {
11        builder.with {
12            build(Secondary)
13            application(title: 'Sample') {
14                borderLayout()
15                label 'Options', constraints: NORTH
16                widget root(SampleSecondary)
17            }
18        }
19    }
20}

This node accepts an additional parameter name that can be used to override the default alias assigned to the node. If you specify a value for this parameter when the node is built then you’ll need to use it again to retrieve the node.

MetaComponent

Enables the usage of a meta-component as a View node. Meta-components are MVC groups that contain additional configuration, for example

 1mvcGroups {
 2    'custom' {
 3        model      = 'sample.CustomModel'
 4        view       = 'sample.CustomView'
 5        controller = 'sample.CustomController'
 6        config {
 7            component = true
 8            title = 'My Default Title'
 9        }
10    }
11}

The metaComponent() node instantiates the MVC group and attaches the top node from the groups’ View member into the current hierarchy. Using the previous group definition in a View is straight forward

metaComponent('custom', title: 'Another Title')

8.3. Swing Specific

Refer to the list of nodes that become available when the griffon-swing-groovy-2.0.0.RC1.jar is added as a dependency.

8.4. JavaFX Specific

Refer to the list of nodes that become available when the griffon-javafx-groovy-2.0.0.RC1.jar is added as a dependency.

8.5. Lanterna Specific

Lanterna is a Java library allowing you to write easy semi-graphical user interfaces in a text-only environment, very similar to the C library curses but with more functionality. Lanterna supports xterm compatible terminals and terminal emulators such as konsole, gnome-terminal, putty, xterm and many more. One of the main benefits of lanterna is that it’s not dependent on any native library but runs 100% in pure Java.

Refer to the list of nodes that become available when the griffon-lanterna-groovy-2.0.0.RC1.jar is added as a dependency.

8.6. Pivot Specific

Apache Pivot is an open-source platform for building installable Internet applications (IIAs). It combines the enhanced productivity and usability features of a modern user interface toolkit with the robustness of the Java platform.

Pivot has a deep listener hierarchy as opposed to the simple one found in Swing. This listener hierarchy does not follow the conventions set forth by the JavaBeans Conventions, thus making it difficult to extrapolate synthetic properties based on event methods when using Groovy builders, as it happens with Swing classes. However this plugin applies a convention for wiring up listeners. Take for example Button and ButtonPressListener, the following example shows how to wire up a buttonPressed event handler.

button('Click me!') {
    buttonPressListener {
        buttonPressed = { source -> println "You pressed on button ${source}!" }
    }
}

For each listener in the Pivot listener list there’s a corresponding node matching their name. For each method of such listener interface there’s a variable matching its name that may have a closure assigned to it. The closure must match the same arguments as the method.

Refer to the list of nodes that become available when the griffon-pivot-groovy-2.0.0.RC1.jar is added as a dependency.

9. Threading

Building a well behaved multi-threaded desktop application has been a hard task for many years, however it does not have to be that way anymore. The following sections explain the threading facilities exposed by the Griffon framework.

9.1. Synchronous Calls

Synchronous calls inside the UI thread are made by invoking the runInsideUISync method. This method results in the same behavior as calling SwingUtilities.invokeAndWait() when using Swing.

 1package sample
 2
 3import java.awt.event.ActionEvent
 4import griffon.core.artifact.GriffonController
 5
 6@griffon.metadata.ArtifactProviderFor(GriffonController)
 7class SampleController {
 8    def model
 9
10    void work(evt = null) {
11        // will be invoked outside of the UI thread by default
12        def value = model.value
13        // do some calculations
14        runInsideUISync {
15            // back inside the UI thread
16            model.result = ...
17        }
18    }
19}

9.2. Asynchronous Calls

Similarly to synchronous calls, asynchronous calls inside the UI thread are made by invoking the runInsideUIAsync method. This method results in the same behavior as calling SwingUtilities.invokeLater() when using Swing.

 1package sample
 2
 3import java.awt.event.ActionEvent
 4import griffon.core.artifact.GriffonController
 5
 6@griffon.metadata.ArtifactProviderFor(GriffonController)
 7class SampleController {
 8    def model
 9
10    void work(evt = null) {
11        // will be invoked outside of the UI thread by default
12        def value = model.value
13        // do some calculations
14        runInsideUIAsync {
15            // back inside the UI Thread
16            model.result = ...
17        }
18    }
19}

9.3. Outside Calls

Making sure a block of code is executed outside the UI thread is made by invoking the execOutsideUI method. This method is smart enough to figure out if the unit of work is already outside of the UI thread; otherwise it instructs the Griffon runtime to runt he unit in a different thread. This is usually performed by a helper java.util.concurrent.ExecutorService.

 1package sample
 2
 3import java.awt.event.ActionEvent
 4import griffon.core.artifact.GriffonController
 5
 6@griffon.metadata.ArtifactProviderFor(GriffonController)
 7class SampleController {
 8    def model
 9
10    void work(evt = null) {
11        // will be invoked outside of the UI thread by default
12        def value = model.value
13        // do some calculations
14        runInsideUIAsync {
15            // back inside the UI thread
16            model.result = ...
17            runOutsideUI {
18                // do more calculations
19            }
20        }
21    }
22}

9.4. Additional Threading Methods

There are two additional methods that complement the generic threading facilities that Griffon exposes to the application and its artifacts

isUIThread()

Returns true if the current thread is the UI thread, false otherwise. Functionally equivalent to calling SwingUtilities.isEventDispatchThread() in Swing.

runFuture(ExecutorService s, Callable c)

schedules a callable on the target ExecutorService. The executor service can be left unspecified, if so a default Thread pool executor will be used.

9.5. The @Threading Annotation

The @griffon.transform.Threading annotation can be used to alter the default behavior of executing a controller action outside of the UI thread. There are 4 possible values that can be specified for this annotation

INSIDE_UITHREAD_SYNC

Executes the code in a synchronous call inside the UI thread. Equivalent to wrapping the code with runInsideUISync.

INSIDE_UITHREAD_ASYNC

Executes the code in an asynchronous call inside the UI thread. Equivalent to wrapping the code with runInsideUIAsync.

OUTSIDE_UITHREAD

Executes the code outside of the UI thread. Equivalent to wrapping the code with runOutsideUI.

SKIP

Executes the code in the same thread as the invoker, whichever it may be.

This annotation can be usend as an AST transformation on any other component that’s not a controller. Any component may gain the ability to execute code in a particular thread, following the selected UI toolkit’s execution rules.

Here’s an example of a custom component that’s able to call its methods in different threads

 1package sample
 2
 3import griffon.transform.Threading
 4
 5class Sample {
 6    @Threading
 7    void doStuff() {
 8        // executed outside of the UI thread
 9    }
10
11    @Threading(Threading.Policy.INSIDE_UITHREAD_SYNC)
12    void moreStuff() {
13        // executed synchronously inside the UI thread
14    }
15}

You must annotate a method with @griffon.transform.Threading. Annotated methods must conform to these rules

9.6. The @ThreadingAware AST Transformation

Any component may gain the ability to execute code in a particular thread, following the selected UI toolkit’s execution rules. It injects the behavior of ThreadingHandler into the annotated class.

This feature is just a shortcut to avoid reaching for the UIThreadManager instance from objects that do not hold a reference to it.

Here’s an example of a custom component that’s able to call its methods in different threads

 1package sample
 2
 3@griffon.transform.ThreadingAware
 4class Sample {
 5    void doStuff() {
 6        runOutsideUIThread {
 7            // executed outside of the UI thread
 8        }
 9    }
10
11    void moreStuff() {
12        runInsideUIAsync {
13            // executed asynchronously inside the UI thread
14        }
15    }
16}

10. Events

Applications have the ability to publish events from time to time to communicate that something of interest has happened at runtime. Events will be triggered by the application during each of its life cycle phases, also when MVC groups are created and destroyed.

All application event handlers are guaranteed to be called in the same thread that originated the event.

10.1. Publishing Events

Any instance that obtains a reference to an EventRouter can publish events. GriffonApplication exposes the application wide EventRouter via a read-only property. You may use Dependency Injection to inject an EventRouter to any class too.

Publishing an event can be done synchronously on the current thread, asynchronously to the current thread, or asynchronously relative to the UI thread. For example, the following snippet will trigger an event that will be handled in the same thread, which could be the UI thread itself

application.eventRouter.publish('MyEventName', ['arg0', 'arg1'])

Whereas the following snippet guarantees that all event handlers that are interested in an event of type MyEventName will be called outside of the UI thread

application.eventRouter.publishOutsideUI('MyEventName', ['arg0', 'arg1'])

Finally, if you’d want event notification to be handed in a thread that is not the current one (regardless if the current one is the UI thread or not) then use the following method

application.eventRouter.publishAsync('MyEventName', ['arg0', 'arg1'])

Alternatively, you may specify an instance of a subclass of Event as the sole argument to any of these methods. The event instance will be the single argument sent to the event handlers when the event methods are invoked in this way.

There may be times when event publishing must be stopped for a while. If that’s the case then you can instruct the application to stop delivering events by invoking the following code

application.eventRouter.eventPublishingEnabled = false

Any events sent through the application’s event bus will be discarded after that call; there’s no way to get them back or replay them. When it’s time to enable the event bus again simply call

application.eventRouter.eventPublishingEnabled = true

10.2. Consuming events

Any artifact or class that abides to the following conventions can be registered as an application listener, those conventions are:

  • it is a Map, a CallableWithArgs or an Object.

  • in the case of a Map, each key maps to <EventName>, the value must be a CallableWithArgs.

  • in the case of object, public methods whose name matches on<EventName> will be used as event handlers.

  • Objects and maps can be registered/unregistered by calling addApplicationListener()/removeApplicationListener() on the EventRouter instance.

  • CallableWithArgs event handlers must be registered with an overloaded version of addApplicationListener()/removeApplicationListener() that takes <EventName> as the first parameter, and the callable itself as the second parameter.

There is a global, per-application event handler that can be registered. If you want to take advantage of this feature you must define a class that implements the EventHandler interface. This class must be registered with a Module. Lastly both Controller and Service instances are automatically registered as application event listeners.

10.2.1. Examples

These are some examples of event handlers:

Display a message right before default MVC groups are instantiated

src/main/com/acme/ApplicationEventHandler.groovy
 1package com.acme
 2
 3import griffon.core.GriffonApplication
 4import griffon.core.event.EventHandler
 5
 6class ApplicationEventHandler implements EventHandler {
 7    void onBootstrapEnd(GriffonApplication application) {
 8        println """
 9            Application configuration has finished loading.
10            MVC Groups will be initialized now.
11        """.stripIndent(12)
12    }
13}
src/main/groovy/com/acme/ApplicationModule.groovy
 1package com.acme
 2
 3import griffon.core.event.EventHandler
 4import griffon.core.injection.Module
 5import org.codehaus.griffon.runtime.core.injection.AbstractModule
 6import org.kordamp.jipsy.ServiceProviderFor
 7
 8@ServiceProviderFor(Module)
 9class ApplicationModule extends AbstractModule {
10    @Override
11    protected void doConfigure() {
12        bind(EventHandler)
13            .to(ApplicationEventHandler)
14            .asSingleton()
15    }
16}

Print the name of the application plus a short message when the application is about to shut down.

griffon-app/controller/MyController.groovy
 1package com.acme
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class MyController {
 7    void onShutdownStart(application)
 8        println "${application.configuration['application.title']} is shutting down"
 9    }
10}

Print a message every time the event "Foo" is published

griffon-app/controller/MyController.groovy
 1package com.acme
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class MyController {
 7    void mvcGroupInit(Map<String, Object> args) {
 8        application.eventRouter.addEventListener([
 9            Foo: { println 'got foo!' } as CallableWithArgs
10        ])
11    }
12
13    void fooAction() {
14        // do something
15        application.eventRouter.publish('Foo')
16    }
17}

An alternative to the previous example using a CallableWithArgs event handler

griffon-app/controller/MyController.groovy
 1package com.acme
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class MyController {
 7    void mvcGroupInit(Map<String, Object> args) {
 8        application.eventRouter.addEventListener('Foo',
 9            { println 'got foo!' } as CallableWithArgs
10        ])
11    }
12
13    void fooAction() {
14        // do something
15        application.eventRouter.publish('Foo')
16    }
17}

An alternative to the previous example using a custom event class

griffon-app/controller/MyController.groovy
 1package com.acme
 2
 3import griffon.core.artifact.GriffonController
 4
 5@griffon.metadata.ArtifactProviderFor(GriffonController)
 6class MyController {
 7    void mvcGroupInit(Map<String, Object> args) {
 8        application.eventRouter.addListener(Foo) {
 9            e -> assert e instanceof Foo
10        }
11    }
12
13    void fooAction() {
14        // do something
15        application.eventRouter.publish(new MyController.Foo(this))
16    }
17
18    static class Foo extends griffon.core.Event {
19        Foo(Object source) { super(source) }
20    }
21}

10.3. Application Events

The following events will be triggered by the application when dealing with artifacts

NewInstance(Class klass, Object instance)

When a new artifact is created.

DestroyInstance(Class klass, Object instance)

When an artifact instance is destroyed.

LoadAddonsStart(GriffonApplication application)

Before any addons are initialized, during the Initialize phase.

LoadAddonsEnd(GriffonApplication application, Map<String, GriffonAddon> addons)

After all addons have been initialized, during the Initialize phase.

LoadAddonStart(String name, GriffonAddon addon, GriffonApplication application)

Before an addon is initialized, during the Initialize phase.

LoadAddonEnd(String name, GriffonAddon addon, GriffonApplication application)

After an addon has been initialized, during the Initialize phase.

These events will be triggered when dealing with MVC groups

InitializeMVCGroup(MVCGroupConfiguration configuration, MVCGroup group)

When a new MVC group is initialized.

CreateMVCGroup(MVCGroup group)

When a new MVC group is created.

DestroyMVCGroup(MVCGroup group)

When an MVC group is destroyed.

10.4. Lifecycle Events

The following events will be triggered by the application during each one of its phases

BootstrapStart(GriffonApplication application)

After logging configuration has been setup, during the Initialize phase.

BootstrapEnd(GriffonApplication application)

At the end of the Initialize phase.

StartupStart(GriffonApplication application)

At the beginning of the Startup phase.

StartupEnd(GriffonApplication application)

At the end of the Startup phase.

ReadyStart(GriffonApplication application)

At the beginning of the Ready phase.

ReadyEnd(GriffonApplication application)

At the end of the ready phase.

ShutdownRequested(GriffonApplication application)

Before the Shutdown begins.

ShutdownAborted(GriffonApplication application)

If a ShutdownHandler prevented the application from entering the Shutdown phase.

ShutdownStart(GriffonApplication application)

At the beginning of the Shutdown phase.

10.5. Miscellaneous Events

These events will be triggered when a specific condition is reached

WindowShown(W window)

Triggered by the WindowManager when a window is shown.

WindowHidden(W window)

Triggered by the WindowManager when a window is hidden.

10.6. The @EventPublisher AST Transformation

Any component may gain the ability to publish events through an EventRouter instance. You only need annotate the class with @griffon.transform.EventPublisher and it will automatically gain all methods exposed by EventPublisher.

The following example shows a trivial usage of this feature

 1@griffon.transform.EventPublisher
 2class Publisher {
 3   void doit(String name) {
 4      publishEvent('arg', [name])
 5   }
 6
 7   void doit() {
 8      publishEvent('empty')
 9   }
10}

The application’s event router will be used by default. If you’d like your custom event publisher to use a private EventRouter then you must define a binding for it using a specific name, like this

 1import griffon.core.injection.Module
 2import griffon.core.event.EventRouter
 3import org.kordamp.jipsy.ServiceProviderFor
 4import org.codehaus.griffon.runtime.core.injection.AbstractModule
 5import org.codehaus.griffon.runtime.core.event.DefaultEventRouter
 6
 7import javax.inject.Named
 8import static griffon.util.AnnotationUtils.named
 9
10@ServiceProviderFor(Module)
11@Named
12class ApplicationModule extends AbstractModule {
13    @Override
14    protected void doConfigure() {
15        bind(EventRouter)
16            .withClassifier(named('my-private-event-router'))
17            .to(DefaultEventRouter)
18            .asSingleton()
19    }
20}

Next specify the named EventRouter as a parameter on the @griffon.transform.EventPublisher transformation

 1@griffon.transform.EventPublisher('my-private-event-router')
 2class Publisher {
 3   void doit(String name) {
 4      publishEvent('arg', [name])
 5   }
 6
 7   void doit() {
 8      publishEvent('empty')
 9   }
10}

11. Internationalization

This chapter describes Internationalization (I18N) features available to all applications.

11.1. MessageSource

Applications have the ability to resolve internationalizable messages by leveraging the behavior exposed by MessageSource. This interface exposes the following methods:

  • String getMessage(String key)

  • String getMessage(String key, Locale locale)

  • String getMessage(String key, Object[] args)

  • String getMessage(String key, Object[] args, Locale locale)

  • String getMessage(String key, List args)

  • String getMessage(String key, List args, Locale locale)

  • String getMessage(String key, Map args)

  • String getMessage(String key, Map args, Locale locale)

  • Object resolveMessageValue(String key, Locale locale)

The first set throws NoSuchMessageException if a message could not be resolved given the key sent as argument. The following methods take and additional defaultMessage parameter that may be used if no configured message is found. If this optional parameter were to be null then the key is used as the message; in other words, these methods never throw NoSuchMessageException nor return null unless the passed in key is null.

  • String getMessage(String key, String defaultMessage)

  • String getMessage(String key, Locale locale, String defaultMessage)

  • String getMessage(String key, Object[] args, String defaultMessage)

  • String getMessage(String key, Object[] args, Locale locale, String defaultMessage)

  • String getMessage(String key, List args, String defaultMessage)

  • String getMessage(String key, List args, Locale locale, String defaultMessage)

  • String getMessage(String key, Map args, String defaultMessage)

  • String getMessage(String key, Map args, Locale locale, String defaultMessage)

The simplest way to resolve a message thus results like this

getApplication().getMessageSource().getMessage('some.key')

The set of methods that take a List as arguments are meant to be used from Groovy code whereas those that take an Object[] are meant for Java code; this leads to better idiomatic code as the following examples reveal

getApplication().getMessageSource()
                .getMessage('groovy.message', ['apples', 'bananas'])
getApplication().getMessageSource()
                .getMessage("java.message", new Object[]{"unicorns", "rainbows"});

Of course you may also use List versions in Java, like this

getApplication().getMessageSource()
                .getMessage("hybrid.message", Arrays.asList("bells", "whistles"));

11.1.1. Message Formats

There are three types of message formats supported by default. Additional formats may be supported if the right plugins are installed. Resources may be configured using either properties files or Groovy scripts, please refer to the configuration section.

Standard Format

The first set of message formats are those supported by the JDK’s MessageFormat facilities. These formats work with all versions of the getMessage() method that take a List or an Object[] as arguments. Examples follow. First the messages stored in a properties file

1healthy.proverb = An {0} a day keeps the {1} away
2yoda.says = {0} is the path to the dark side. {0} leads to {1}. {1} leads to {2}. {2} leads to suffering.

Then the code used to resolve them

String quote = getApplication().getMessageSource()
                               .getMessage('healthy.proverb', ['apple', 'doctor'])
assert quote == 'An apple a day keeps the doctor away'
String quote = getApplication().getMessageSource()
                               .getMessage("yoday.says", new Object[]{"Fear", "Anger", "Hate"});
assertEquals(quote, "Fear is the path to the dark side. Fear leads to Anger. Anger leads to Hate. Hate leads to suffering");
Map Format

The following format is non-standard (i.e, not supported by MessageFormat) and can only be resolved by Griffon. This format uses symbols instead of numbers as placeholders for arguments. Thus the previous messages can be rewritten as follows

1healthy.proverb = An {:fruit} a day keeps the {:occupation} away
2yoda.says = {:foo} is the path to the dark side. {:foo} leads to {:bar}. {:bar} leads to {:foobar}. {:foobar} leads to suffering.

Which may be resolved in this manner

String quote = getApplication().getMessageSource()
                               .getMessage('healthy.proverb', [fruit: 'apple', occupation: 'doctor'])
assert quote == 'An apple a day keeps the doctor away
import static griffon.util.CollectionUtils.map;
String quote = getApplication().getMessageSource()
                               .getMessage("yoday.says", map().e("foo", "Fear")
                                                              .e("bar", "Anger")
                                                              .e("foobar","Hate"));
assertEquals(quote, "Fear is the path to the dark side. Fear leads to Anger. Anger leads to Hate. Hate leads to suffering");
Groovy format

Groovy scripts have one advantage over properties files as you can embed custom logic that may conditionally resolve a message based on environmental values or generate a message on the fly. In order to accomplish this feat messages must be defined as closures and must return a String value; if they do not then their return value will be translated to a String. The following message uses the value of the current running environment to determine the text of a warning to be displayed on a label

 1import griffon.util.Environment
 2
 3warning.label = { args ->
 4    if (Environment.current == Environment.PRODUCTION) {
 5        "The application has encountered an error: $args"
 6    } else {
 7        "Somebody setup us the bomb! $args"
 8    }
 9}

11.1.2. Reference Keys

There may be times where you would want to have a 2 keys reference the same value, as if one key were an alias for the other. MessageSource supports the notion of referenced keys for this matter. In order to achieve this, the value of the alias key must define the aliased key with a special format, for example

1famous.quote = This is {0}!
2hello.world = @[famous.quote]

Resolving those keys results in

assert getApplication()
           .getMessageSource()
           .getMessage('famous.quote', ['Sparta']) == 'This is Sparta!'

assert getApplication()
          .getMessageSource()
          .getMessage('hello.world', ['Griffon']) == 'This is Griffon!'

11.2. MessageSource Configuration

Messages may be configured in either properties files or Groovy scripts. Groovy scripts have precedence over properties files should there be two files that match the same basename. The default configured basename is "messages", thus the application will search for the following resources in the classpath.

  • messages.properties

  • messages.groovy

Of course Groovy scripts are only enabled if you add a dependency to the griffon-groovy module to your project. The default basename may be changed to some other value, or additional basenames may be specified too; it’s just a matter of configuring a Module override

 1@ServiceProviderFor(Module.class)
 2@Named("application")
 3@DependsOn("core")
 4public class ApplicationModule extends AbstractModule {
 5    @Override
 6    protected void doConfigure() {
 7        bind(MessageSource.class)
 8            .withClassifier(named("applicationMessageSource"))
 9            .toProvider(new MessageSourceProvider("custom_basename"))
10            .asSingleton();
11    }
12}

Both properties files and Groovy scripts are subject locale aware loading mechanism described next. For a Locate set to de_CH_Basel the following resources will be searched for and loaded

  • messages.properties

  • messages.groovy

  • messages_de.properties

  • messages_de.groovy

  • messages_de_CH.properties

  • messages_de_CH.groovy

  • messages_de_CH_Basel.properties

  • messages_de_CH_Basel.groovy

Properties files and Groovy scripts used for internationalization purposes are usually placed under griffon-app/i18n The default messages.properties file is placed in this directory upon creating an application using the standard project templates.

11.3. The @MessageSourceAware AST Transformation

Any component may gain the ability to resolve messages through a MessageSource instance. You only need annotate the class with @griffon.transform.MessageSourceAware and it will automatically gain all methods exposed by MessageSource.

This feature is just a shortcut to avoid reaching for the application instance from objects that do not hold a reference to it.

Here’s an example of a custom bean that’s able to resolve messages

1@griffon.transform.MessageSourceAware
2class Bean {
3    String name
4}

This class can be used in the following way

1class SampleService {
2    @Inject Bean bean
3
4    String lookupValues(String arg) {
5        bean.name = arg
6        bean.getMessage('some.message.key', [bean.name])
7    }
8}

The application’s MessageSource will be injected to annotated beans if no name is specified as an argument to MessageSourceAware. You may define multiple MessageSource bindings as long as you qualify them with a distinct name, such as

 1@ServiceProviderFor(Module.class)
 2@Named("application")
 3public class ApplicationModule extends AbstractModule {
 4    @Override
 5    protected void doConfigure() {
 6        bind(MessageSource.class)
 7            .withClassifier(AnnotationUtils.named("foo"))
 8            .toProvider(new MessageSourceProvider("foofile"))
 9            .asSingleton();
10
11        bind(MessageSource.class)
12            .withClassifier(AnnotationUtils.named("bar"))
13            .toProvider(new MessageSourceProvider("barfile"))
14            .asSingleton();
15    }
16}

Then make use of any of these bindings like so

1@griffon.transform.MessageSourceAware('foo')
2class Bean {
3    String name
4}

12. Resource Management

This chapter describes resource management and injection features available to all applications.

12.1. Locating Classpath Resources

Resources can be loaded form the classpath using the standard mechanism provided by the Java runtime, that is, ask a ClassLoader instance to load a resource URL or obtain an InputStream that points to the resource.

But the code can get quite verbose, take for example the following view code that locates a text file and displays it on a text component

scrollPane {
    textArea(columns: 40, rows: 20,
        text: this.class.classLoader.getResource('someTextFile.txt').text)
}

In order to reduce visual clutter, also to provide an abstraction over resource location, applications rely on ResourceHandler, which exposes the following contract

  • URL getResourceAsURL(String name)

  • InputStream getResourceAsStream(String name)

  • List<URL> getResources(String name)

  • ClassLoader classloader()

Thus, the previous example can be rewritten in this way

scrollPane {
    textArea(columns: 40, rows: 20,
        text: application.resourceHandler.getResourceAsURL('someTextFile.txt').text)
}

In the future Griffon may switch to a modularized runtime, this abstraction will shield you from any problems when the underlying mechanism changes, such as picking the correct ClassLoader.

12.2. The @ResourcesAware AST Transformation

Any component may gain the ability to locate resources through a ResourceHandler instance. You only need annotate the class with @griffon.transform.ResourcesAware and it will automatically gain all methods exposed by ResourceHandler.

This feature is just a shortcut to avoid reaching for the application instance from objects that do not hold a reference to it.

Here’s an example of a custom bean that’s able to locate resources

1@griffon.transform.ResourcesAware
2class Bean {
3    String name
4}

This class can be used in the following way

1class SampleService {
2    @Inject Bean bean
3
4    InputStream fetchResource(String arg) {
5        bean.name = arg
6        bean.getResourceAsStream(bean.name)
7    }
8}

12.3. ResourceResolver

Applications have the ability to resolve internationalizable messages by leveraging the behavior exposed by ResourceResolver. This interface exposes the following methods:

  • Object resolveResource(String key)

  • Object resolveResource(String key, Locale locale)

  • Object resolveResource(String key, Object[] args)

  • Object resolveResource(String key, Object[] args, Locale locale)

  • Object resolveResource(String key, List args)

  • Object resolveResource(String key, List args, Locale locale)

  • Object resolveResource(String key, Map args)

  • Object resolveResource(String key, Map args, Locale locale)

  • Object resolveResorceValue(String key, Locale locale)

The first set throws NoSuchResourceException if a message could not be resolved given the key sent as argument. The following methods take and additional defaultValue parameter that may be used if no configured resource is found. If this optional parameter were to be null then the key is used as the literal value of the resource; in other words, these methods never throw NoSuchResourceException nor return null unless the passed in key is null.

  • Object resolveResource(String key, Object defaultValue)

  • Object resolveResource(String key, Locale locale, Object defaultValue)

  • Object resolveResource(String key, Object[] args, Object defaultValue)

  • Object resolveResource(String key, Object[] args, Locale locale, Object defaultValue)

  • Object resolveResource(String key, List args, Object defaultValue)

  • Object resolveResource(String key, List args, Locale locale, Object defaultValue)

  • Object resolveResource(String key, Map args, Object defaultValue)

  • Object resolveResource(String key, Map args, Locale locale, Object defaultValue)

The simplest way to resolve a message thus results like this

getApplication().getResourceResolver().resolveResource('menu.icon')

The set of methods that take a List as arguments are meant to be used from Groovy code whereas those that take an Object[] are meant for Java code; this leads to better idiomatic code as the following examples reveal

getApplication().getResourceResolver()
                .resolveResource('groovy.icon.resource', ['small']))
getApplication().getResourceResolver()
                .resolveResource("java.icon.resource", new Object[]{"large"});

Of course you may also use List versions in Java, like this

getApplication().getResourceResolver()
                .resolveResource("hybrid.icon.resource", Arrays.asList("medium"));

12.3.1. Message Formats

There are three types of resource formats supported by default. Additional formats may be supported if the right plugins are installed. Resources may be configured using either properties files or Groovy scripts, please refer to the configuration section.

Standard Format

The first set of resource formats are those supported by the JDK’s MessageFormat facilities. These formats work with all versions of the resolveResource() method that take a List or an Object[] as arguments. Examples follow. First the resource definitions stored in a properties file

1menu.icon = /img/icons/menu-{0}.png

Assuming there are three icon files stored at griffon-app/resources/img/icons whose filenames are menu-small.png, menu-medium.png and menu-large.png, a component may resolve any of them with

Object icon = getApplication().getResourceResolver()
                              .resolveResource('menu.icon', ['large'])
Map Format

The following format is non-standard (i.e, not supported by MessageFormat) and can only be resolved by Griffon. This format uses symbols instead of numbers as placeholders for arguments. Thus the previous messages can be rewritten as follows

1menu.icon = /img/icons/menu-{:size}.png

Which may be resolved in this manner

Object icon = getApplication().getResourceResolver()
                              .resolveResource('menu.icon', [size: 'large'])
Groovy format

Groovy scripts have one advantage over properties files as you can embed custom logic that may conditionally resolve a resource based on environmental values or generate a message on the fly. In order to accomplish this feat resources must be defined as closures. The following message uses the value of the current running environment to determine the text of a warning to be displayed on a label

1import java.awt.Rectangle
2
3direct.instance = new Rectangle(10i, 20i, 30i, 40i)
4computed.instance = { x, y, w, h ->
5    new Rectangle(x, y, w, h)
6}

12.3.2. Type Conversion

Note that the return value of resolveResource is marked as Object but you’ll get a String from the first two formats. You’ll have to rely on property editors in order to transform the value into the correct type. Injected resources are automatically transformed to the expected type.

Here’s how it can be done

import javax.swing.Icon
import java.beans.PropertyEditor
import java.beans.PropertyEditorManager
...
Object iconValue = getApplication().getResourceResolver()
                                   .resolveResource('menu.icon', ['large'])
PropertyEditor propertyEditor = PropertyEditorManager.findEditor(Icon)
propertyEditor.setAsText(String.valueOf(iconValue))
Icon icon = propertyEditor.getValue()

12.3.3. Reference Keys

There may be times where you would want to have a 2 keys reference the same value, as if one key were an alias for the other. ResourceResolver supports the notion of referenced keys for this matter. In order to achieve this, the value of the alias key must define the aliased key with a special format, for example

1action.icon = /img/icons/action-{0}.png
2hello.icon = @[action.icon]

Resolving those keys results in

assert getApplication()
           .getResourceResolver()
           .resolveResource('action.icon', ['copy']) == '/img/icons/action-copy.png'

assert getApplication()
           .getResourceResolver()
           .resolveResource('hello.icon', ['paste']) == '/img/icons/action-paste.png'

12.4. ResourceResolver Configuration

Resources may be configured in either properties files or Groovy scripts. Groovy scripts have precedence over properties files should there be two files that match the same basename. The default configured basename is "resources", thus the application will search for the following resources in the classpath.

  • resources.properties

  • resources.groovy

Of course Groovy scripts are only enabled if you add a dependency to the griffon-groovy module to your project. The default basename may be changed to some other value, or additional basenames may be specified too; it’s just a matter of configuring a Module override

 1@ServiceProviderFor(Module.class)
 2@Named("application")
 3@DependsOn("core")
 4public class ApplicationModule extends AbstractModule {
 5    @Override
 6    protected void doConfigure() {
 7        bind(ResourceResolver.class)
 8            .withClassifier(named("applicationResourceResolver"))
 9            .toProvider(new ResourceResolverProvider("custom_basename"))
10            .asSingleton();
11    }
12}

Both properties files and Groovy scripts are subject locale aware loading mechanism described next. For a Locate set to de_CH_Basel the following resources will be searched for and loaded

  • resources.properties

  • resources.groovy

  • resources_de.properties

  • resources_de.groovy

  • resources_de_CH.properties

  • resources_de_CH.groovy

  • resources_de_CH_Basel.properties

  • resources_de_CH_Basel.groovy

Properties files and Groovy scripts used for internationalization purposes are usually placed under griffon-app/resources The default resources.properties file is placed in this directory upon creating an application using the standard project templates.

12.5. The @ResourceResolverAware AST Transformation

Any component may gain the ability to resolve resources through a ResourceResolver instance. You only need annotate the class with @griffon.transform.ResourceResolverAware and it will automatically gain all methods exposed by ResourceResolver.

This feature is just a shortcut to avoid reaching for the application instance from objects that do not hold a reference to it.

Here’s an example of a custom bean that’s able to resolve resources

1@griffon.transform.ResourceResolverAware
2class Bean {
3    String name
4}

This class can be used in the following way

1class SampleService {
2    @Inject Bean bean
3
4    String lookupValues(String arg) {
5        bean.name = arg
6        bean.resolveResource('some.resource.key', [bean.name])
7    }
8}

The application’s ResourceResolver will be injected to annotated beans if no name is specified as an argument to ResourceResolverAware. You may define multiple ResourceResolver bindings as long as you qualify them with a distinct name, such as

 1@ServiceProviderFor(Module.class)
 2@Named("application")
 3public class ApplicationModule extends AbstractModule {
 4    @Override
 5    protected void doConfigure() {
 6        bind(ResourceResolver.class)
 7            .withClassifier(AnnotationUtils.named("foo"))
 8            .toProvider(new ResourceResolverProvider("foofile"))
 9            .asSingleton();
10
11        bind(ResourceResolver.class)
12            .withClassifier(AnnotationUtils.named("bar"))
13            .toProvider(new ResourceResolverProvider("barfile"))
14            .asSingleton();
15    }
16}

Then make use of any of these bindings like so

1@griffon.transform.ResourceResolverAware('foo')
2class Bean {
3    String name
4}

12.6. Resource Injection

Resources may be automatically injected to any instance created via the application’s Injector. Injection points must be annotated with @griffon.core.resources.InjectedResource which can be set on properties (Groovy), fields (Java and Groovy) and setter methods (Java and Groovy). @InjectedResource is a perfect companion to models as the following example shows

griffon-app/resources/resources.properties
1sample.SampleModel.griffonLogo = /griffon-logo-48x48.png
2logo = /griffon-logo-{0}x{0}.png
griffon-app/models/sample/SampleModel.groovy
 1package sample
 2
 3import griffon.core.resources.InjectedResource
 4import javax.swing.Icon
 5import griffon.core.artifact.GriffonModel
 6
 7@griffon.metadata.ArtifactProviderFor(GriffonModel)
 8class SampleModel {
 9    @InjectedResource Icon griffonLogo
10    @InjectedResource(key='logo', args=['16']) Icon smallGriffonLogo
11    @InjectedResource(key='logo', args=['64']) Icon largeGriffonLogo
12}

@InjectedResource assumes a naming convention in order to determine the resource key to use. These are the rules applied by the default by ResourcesInjector:

  • If a value is specified for the key argument then use it as is.

  • otherwise construct a key based in the field name prefixed with the full qualified class name of the field’s owner.

You may also specify a default value if the resource definition is not found, however be aware that this value must be set as a String thus guaranteeing a type conversion. An optional format value may be specified as a hint to the PropertyEditor used during value conversion, for example

griffon-app/models/sample/SampleModel.groovy
 1package sample
 2
 3import griffon.core.resources.InjectedResource
 4import griffon.core.artifact.GriffonModel
 5
 6@griffon.metadata.ArtifactProviderFor(GriffonModel)
 7class SampleModel {
 8    @InjectedResource(defaultValue='10.04.2013 2:30 PM', format='dd.MM.yyyy h:mm a')
 9    Date date
10}

12.7. Property Editors

Resource injection makes use of the PropertyEditor mechanism provided by the java.beans package. The default ResourcesInjector queries PropertyEditorManager whenever a resource value must be transformed to a target type.

PropertyEditorManager provides methods for registering custom PropertyEditors, it also follows a class name convention to load PropertyEditors should a custom one is not programmatically registered. Griffon applications will automatically load and register PropertyEditors from the following classpath resource: /META-INF/services/java.beans.PropertyEditor. Each line follows the format

target.type = full.qualified.classname

The following table enumerates the default PropertyEditors loaded by Griffon at startup.

Table 5. Core PropertyEditor mappings
Type Editor Class Format

java.lang.String

griffon.core.editors.StringPropertyEditor

 

java.io.File

griffon.core.editors.FilePropertyEditor

java.net.URL

griffon.core.editors.URLPropertyEditor

java.net.URI

griffon.core.editors.URIPropertyEditor

java.math.BigDecimal

griffon.core.editors.BigDecimalPropertyEditor

currency, percent

java.math.BigInteger

griffon.core.editors.BigIntegerPropertyEditor

currency, percent

java.lang.Boolean

griffon.core.editors.BooleanPropertyEditor

boolean, query, switch

java.lang.Byte

griffon.core.editors.BytePropertyEditor

currency, percent

java.lang.Short

griffon.core.editors.ShortPropertyEditor

currency, percent

java.lang.Integer

griffon.core.editors.IntegerPropertyEditor

currency, percent

java.lang.Long

griffon.core.editors.LongPropertyEditor

currency, percent

java.lang.Float

griffon.core.editors.FloatPropertyEditor

currency, percent

java.lang.Double

griffon.core.editors.DoublePropertyEditor

currency, percent

java.util.Calendar

griffon.core.editors.CalendarPropertyEditor

java.util.Date

griffon.core.editors.DatePropertyEditor

Where the following apply

  • currency and percent are literal values.

  • boolean accepts true and false as values.

  • query accepts yes and no as values.

  • switch accepts on and off as values.

Core UI Toolkit dependencies, such as griffon-swing and griffon-javafx, deliver additional PropertyEditors. The following tables summarize these additions

Table 6. Swing PropertyEditor mappings
Type Editor Class Format

java.awt.Color

 griffon.swing.editors.ColorPropertyEditor

#RGB ; #RGBA ; #RRGGBB; #RRGGBBAA ; Color constant

 java.awt.Dimension

 griffon.swing.editors.DimensionPropertyEditor

width, height

 java.awt.Font

griffon.swing.editors.FontPropertyEditor

family-style-size

 java.awt.GradientPaint

 griffon.swing.editors.GradientPaintPropertyEditor

x1, y1, #RGB, x2, y2, #RGB, CYCLIC

 java.awt.Image

 griffon.swing.editors.ImagePropertyEditor

path/to/image_file

 java.awt.Insets

 griffon.swing.editors.InsetsPropertyEditor

top, left, bottom, right

 java.awt.LinearGradientPaint

 griffon.swing.editors.LinearGradientPaintPropertyEditor

xy, y1, x2, x2, [0.0, 1.0], [#RGB, #RGB], REPEAT

 java.awt.Point

 griffon.swing.editors.PointPropertyEditor

x, y

 java.awt.Polygon

 griffon.swing.editors.PolygonPropertyEditor

x1, y1, x2, y2, …, xn, yn

 java.awt.RadialGradientPaint

 griffon.swing.editors.RadialGradientPaintPropertyEditor

xy, y1, r, fx, fy, [0.0, 1.0], [#RGB, #RGB], REPEAT

 java.awt.Rectangle

 griffon.swing.editors.RectanglePropertyEditor

x, y, width, height

 java.awt.geom.Point2D

 griffon.swing.editors.Point2DPropertyEditor

x, y

 java.awt.geom.Rectangle2D

 griffon.swing.editors.Rectangle2DPropertyEditor

x, y, width, height

 java.awt.image.BufferedImage

 griffon.swing.editors.BufferedImagePropertyEditor

path/to/image_file

 javax.swing.Icon

griffon.swing.editors.IconPropertyEditor

path/to/image_file

Where the following apply

  • R, G, B and A represent an hexadecimal number.

  • CYCLIC may be true or false.

  • REPEAT must be one of MultipleGradientPaint.CycleMethod.

  • GradientPaint supports another format: x1, y1 | x2, y2, | #RGB, #RGB | CYCLIC

  • Color supports all color constants defined by griffon.swing.support.Colors.

  • All color formats are supported by gradient editors.

The following styles are supported by FontPropertyEditor

  • BOLD

  • ITALIC

  • BOLDITALIC

  • PLAIN

Table 7. JavaFX PropertyEditor mappings
Type Editor Class Format

 javafx.geometry.Dimension2D

 griffon.javafx.editors.Dimension2DPropertyEditor

width, height

javafx.geometry.Insets

 griffon.javafx.editors.InsetsPropertyEditor

top, left, bottom, right

javafx.geometry.Point2D

griffon.javafx.editors.Point2DPropertyEditor

x, y

javafx.geometry.Rectangle2D

 griffon.javafx.editors.Rectangle2DPropertyEditor

x, y , width, height

javafx.scene.image.Image

 griffon.javafx.editors.ImagePropertyEditor

path/to/image_file

 javafx.scene.paint.Color

 griffon.javafx.editors.ColorPropertyEditor

#RGB ; #RGBA ; #RRGGBB; #RRGGBBAA ; Color constant

Where the following apply

  • R, G, B and A represent an hexadecimal number.

Table 8. Pivot PropertyEditor mappings
Type Editor Class Format

java.awt.Color

 griffon.pivot.editors.ColorPropertyEditor

#RGB ; #RGBA ; #RRGGBB; #RRGGBBAA ; Color constant

 org.apache.pivot.wtk.Bounds

 griffon.pivot.editors.BoundsPropertyEditor

x, y , width, height

org.apache.pivot.wtk.Dimensions

 griffon.pivot.editors.DimensionsPropertyEditor

width, height

org.apache.pivot.wtk.Insets

 griffon.pivot.editors.InsetsPropertyEditor

top, left, right, bottom

 org.apache.pivot.wtk.Point

griffon.pivot.editors.PointPropertyEditor

x, y

Where the following apply

  • R, G, B and A represent an hexadecimal number.

  • Color supports all color constants defined by griffon.pivot.support.Colors.

13. Addons

Addons allow plugin authors to perform the following tasks

Addons are automatically registered as ShutdownHandlers with the application instance. They are also registered as event handlers with the application’s EventRouter.

Addons must be registered with a Module in order to be discovered by the runtime. Here’s a simple example of a custom Addon that prints out the name of an MVCGroup when said group is initialized

 1package com.acme;
 2
 3import griffon.core.mvc.MVCGroup;
 4import griffon.core.mvc.MVCGroupConfiguration;
 5import org.codehaus.griffon.runtime.core.addon.AbstractGriffonAddon;
 6
 7import javax.annotation.Nonnull;
 8import javax.inject.Named;
 9
10@Named("inspector")
11public class InspectorAddon extends AbstractGriffonAddon {
12    public void onInitializeMVCGroup(@Nonnull MVCGroupConfiguration configuration, @Nonnull MVCGroup group) {
13        System.out.println("MVC group " + group.getMvcType() + " initialized");
14    }
15}

And here is how it can be registered with a Module

 1package com.acme;
 2
 3import griffon.core.addon.GriffonAddon;
 4import griffon.core.injection.Module;
 5import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 6import org.kordamp.jipsy.ServiceProviderFor;
 7
 8import javax.inject.Named;
 9
10@Named("inspector")
11@ServiceProviderFor(Module.class)
12public class InspectorModule extends AbstractModule {
13    @Override
14    protected void doConfigure() {
15        bind(GriffonAddon.class)
16            .to(InspectorAddon.class)
17            .asSingleton();
18    }
19}

13.1. The AddonManager

The AddonManager is responsible for keeping track of instantiated GriffonAddons. You may use this manager to query which addons have been registered with the application, thus conditionally enable further features if a particular addon is instantiated or not.

The name of a GriffonAddon is used as the key to register it with the AddonManager, in other words, the previous inspector addon can be queried in the following way

GriffonAddon addon = application.getAddonManager().findAddon("inspector");

14. Testing

14.1. Unit Testing

TBD

14.2. Integration Testing

TBD

15. Build Tools

15.1. GVM

From GVM’s website

GVM is a tool for managing parallel Versions of multiple Software Development Kits on most Unix based systems. It provides a convenient command line interface for installing, switching, removing and listing Candidates.

GVM can be used to install and keep up to date other build tools that make your life easier when developing Griffon projects. These tools are lazybones and gradle. Installing GVM itself is as easy as typing the following on a command prompt

$ curl -s get.gvmtool.net | bash

Next install the latest versions of lazybones and gradle by invoking

$ gvm install lazybones
$ gvm install gradle

GVM works on POSIX compliant environments, event on Windows if Cygwin is installed. We recommend you to install Babun shell as it enables much more features than plain Cygwin. There’s also a Power Shell alternative: posh-gvm.

15.2. Lazybones

Lazybones allows you to create a new project structure for any framework or library for which the tool has a template.

15.2.1. Configuration

All standard Griffon templates are published to https://bintray.com/griffon/griffon-lazybones-templates You must configure this repository with Lazybones settings file. Edit $USER_HOME/.lazybones/config.groovy and paste the following content

$USER_HOME/.lazybones/config.groovy
1bintrayRepositories = [
2    "griffon/griffon-lazybones-templates",
3    "pledbrook/lazybones-templates"
4]

Invoking the lazybones list command should result in all currently available Griffon project templates to be displayed in the output.

15.2.2. Templates

The following templates are available at the standard template repository

griffon-swing-java

A template that initializes an application with Swing and Java.

griffon-swing-groovy

A template that initializes an application with Swing and Groovy.

griffon-javafx-ava

A template that initializes an application with JavaFX and Java.

griffon-javafx-groovy

A template that initializes an application with JavaFX and Groovy.

griffon-pivot-java

A template that initializes an application with Pivot and Java.

griffon-pivot-groovy

A template that initializes an application with Pivot and Groovy.

griffon-lanterna-java

A template that initializes an application with Lanterna and Java.

griffon-lanterna-groovy

A template that initializes tan application with Lanterna and Groovy.

griffon-plugin

A template that initializes a Griffon plugin project.

All application project templates include a subtemplate named artifact that provides the following artifact templates

model

A standard Model artifact.

view

A toolkit specific View artifact.

controller

A standard Controller artifact.

service

A standard Service artifact.

test

A standard unit Test artifact.

mvcgroup

Initializes Model, View, Controller, Test and IntegrationTest artifacts for a single MVC group.

The swing and pivot artifact templates provided additional artifact templates

integrationTest

A standard integration Test artifact.

You can invoke any of these templates in the following ways

$ lazybones generate artifact::controller

This command creates a new Controller artifact. You’ll be asked for a package and a class name.

$ lazybones generate artifact::mvcgroup::com.acme::group

This command creates an MVC Group whose package is com.acme and class name is group. There will be 5 new artifacts in total.

15.3. Gradle

Gradle is the preferred build tool for a Griffon project. The Lazybones templates create a default build.gradle file that contains the minimum configuration to build, test and package a Griffon application or plugin.

15.3.1. The "griffon" Plugin

The Griffon plugin adds default dependencies and conventional configuration to a Griffon project. This configuration follows the standard Griffon project layout.

Usage

To use the Griffon plugin, include in your build script:

build.gradle
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'org.codehaus.griffon:gradle-griffon-plugin:2.0.0.RC1'
    }
}

apply plugin: 'org.codehaus.griffon.griffon'

This plugin performs the following configurations when applied to a project

  • Register jcenter() as default repository.

  • Apply the following plugins: idea, java, application.

  • Create additional compile-time only configurations: compileOnly, testCompileOnly.

  • Resolve plugin dependencies using the griffon configuration.

  • Adjusts javadoc/groovydoc/idea/eclipse classpaths given the new configurations.

  • Configures standard source directories with main and test source sets.

  • Adjusts how main and test resource processing is performed.

The following dependencies are added by default too

  • on compile

    • org.codehaus.griffon:griffon-core:2.0.0.RC1

  • on compileOnly

    • org.codehaus.griffon:griffon-core-compile:2.0.0.RC1

  • on testCompile

    • org.codehaus.griffon:griffon-core-test:2.0.0.RC1

  • on testCompileOnly

    • org.codehaus.griffon:griffon-core-compile:2.0.0.RC1

If the toolkit conventional property is defined (plugins may opt to skip it) then the following dependencies are added

  • on compile

    • org.codehaus.griffon:griffon-<toolkit>:2.0.0.RC1

If the groovy plugin is applied then the following dependencies are also added

  • on compile

    • org.codehaus.griffon:griffon-groovy:2.0.0.RC1

  • on compile

    • org.codehaus.griffon:griffon-<toolkit>-groovy:2.0.0.RC1

  • on compileOnly

    • org.codehaus.griffon:griffon-groovy-compile:2.0.0.RC1

  • on testCompileOnly

    • org.codehaus.griffon:griffon-groovy-compile:2.0.0.RC1

The griffon configuration can be used to resolve dependencies using BOM files. Assume for a moment that the griffon-scaffolding-plugin is comprised of the following modules

  • griffon-scaffolding - the core of the plugin, UI toolkit agnostic.

  • griffon-scaffolding-swing - Swing specific additions.

  • griffon-scaffolding-javafx-groovy - Groovy enhancements via BuilderCustomizer.

  • griffon-scaffolding-javafx - JavaFX specific additions.

  • griffon-scaffolding-javafx-groovy - Groovy enhancements via BuilderCustomizer.

  • griffon-scaffolding-groovy-compile - AST transformations.

As you can appreciate those are quite the set. You can manually define any of these dependencies on the build file, but given the many combinations it may be a bit hard to determine which dependencies should be added and which shouldn’t. The griffon configuration can make this decision for you; you just have to use it in the following way

build.gradle
dependencies {
    griffon 'org.codehaus.griffon.plugins:griffon-scaffolding-plugin:0.0.0-SNAPSHOT'
}

This will add all required dependencies to your build by taking into account the project’s choice of UI toolkit and whether the groovy plugin has been applied or not. This behavior can be configured and/or disabled by using the conventional properties describe in the next section.

Convention properties

The Griffon plugin adds some properties to the project, which you can use to configure its behaviour.

Table 9. Griffon plugin - properties
Property name Type Default value Description

disableDependencyResolution

boolean

 false

Disable automatic inclusion of dependencies defined with the griffon configuration.

includeGroovyDependencies

boolean

 -

Force inclusion of Groovy dependencies defined with the griffon configuration.

toolkit

String

 _

The UI toolkit to use. May be left unset. Valid values are swing, javafx, pivot, lanterna.

version

String

 2.0.0.RC1

The Griffon version to use for Griffon core dependencies.

The includeGroovyDependencies property has 3 states: unset, false and true. Groovy dependencies will be added automatically to the project only if the value of includeGroovyDependencies is unset (default) and the groovy plugin has been applied to the project or if the value of includeGroovyDependencies is set to true. When the value of includeGroovyDependencies is set to false then Groovy dependencies will not be added, even if the groovy plugin has been applied. This is useful for Java projects that use Spock for testing, as you need the groovy plugin in order to compile Spock specifications but you wouldn’t want Groovy dependencies to be pulled in for compilation.

15.3.2. The "griffon-build" Plugin

The Griffon Build plugin enables useful tasks required for plugin authors, such as the aggregation of Cobertura data files and reports; generation of a plugin BOM file, and more.

Usage

To use the Griffon Build plugin, include in your build script:

build.gradle
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'org.codehaus.griffon:gradle-griffon-build-plugin:2.0.0.RC1'
    }
}

apply plugin: 'org.codehaus.griffon.griffon-build'
Tasks

The Griffon Build plugin adds a number of tasks to your project, as shown below.

Table 10. Griffon Build plugin - tasks
Task name Depends on Type Description

aggregateCoberturaMerge

-

 AggregateCoberturaMergeTask

Aggregates all cobertura data files found in the project.

aggregateCoberturaReport

-

 AggregateCoberturaReport

Generates a project-wide Cobertura report.

GenerateBomTask

-

 GenerateBomTask

Generates a BOM file that includes all subprojects.

Convention Properties

The Griffon Build plugin adds a number of convention properties to the project, shown below.

Table 11. Griffon Build plugin - tasks
Property name Type Default value Description

coverageOutputDatafile

File

 buildDir/cobertura/cobertura.ser

Path to the data file to produce during instrumentation.

coverageReportDir

File

 reportsDir/cobertura

Path to report directory for coverage report.

15.3.3. Dependencies

Whether you’re using the griffon plugin or not it’s very important you take special note of the dependencies ending with -compile. As an application developer these dependencies belong to either compileOnly or testCompileOnly configurations, as these dependencies contain functionality that should not be exposed at runtime, such as compile-time metadata generation via Jipsy, Gipsy and other AST transformations.

The only reason for a -compile dependency to appear on a compile or testCompile configuration is for testing out new compile-time metadata generators. This task is usually performed in plugin projects.

15.4. Maven

Application projects can also be built using Maven. The Lazybones templates create a default pom.xml file that contains the minimum configuration to build, test and package a Griffon application. The bulk of the conventions is performed by the application-master-pom.pom.

15.4.1. Plugins

The application-master-pom configures the following plugins

Table 12. application-master-pom - plugins
Group ArtifactId Version

com.zenjava

javafx-maven-plugin

2.0

org.apache.maven.plugins

maven-antrun-plugin

1.7

org.apache.maven.plugins

maven-assembly-plugin

2.4

org.apache.maven.plugins

maven-checkstyle-plugin

2.12.1

org.apache.maven.plugins

maven-compiler-plugin

3.1

org.apache.maven.plugins

maven-dependency-plugin

2.8

org.codehaus.mojo

exec-maven-plugin

1.2.1

org.apache.maven.plugins

maven-javadoc-plugin

2.9.1

org.apache.maven.plugins

maven-jxr-plugin

2.4

org.apache.maven.plugins

maven-project-info-reports-plugin

2.7

org.apache.maven.plugins

maven-release-plugin

2.1

org.apache.maven.plugins

maven-site-plugin

3.4

org.apache.maven.plugins

maven-surefire-plugin

2.17

org.apache.maven.plugins

maven-surefire-report-plugin

2.17

org.bsc.maven

maven-processor-plugin

2.2.4

org.codehaus.gmavenplus

gmavenplus-plugin

1.2

org.codehaus.mojo

build-helper-maven-plugin

1.9

org.codehaus.mojo

findbugs-maven-plugin

2.5.4

org.codehaus.mojo

jdepend-maven-plugin

2.0

org.codehaus.mojo

versions-maven-plugin

2.1

Of which the following are applied by default

 1<plugins>
 2    <plugin>
 3        <groupId>org.apache.maven.plugins</groupId>
 4        <artifactId>maven-compiler-plugin</artifactId>
 5    </plugin>
 6    <plugin>
 7        <groupId>org.codehaus.mojo</groupId>
 8        <artifactId>build-helper-maven-plugin</artifactId>
 9    </plugin>
10    <plugin>
11        <groupId>org.codehaus.gmavenplus</groupId>
12        <artifactId>gmavenplus-plugin</artifactId>
13    </plugin>
14    <plugin>
15        <groupId>org.bsc.maven</groupId>
16        <artifactId>maven-processor-plugin</artifactId>
17    </plugin>
18    <plugin>
19        <groupId>org.apache.maven.plugins</groupId>
20        <artifactId>maven-surefire-plugin</artifactId>
21    </plugin>
22    <plugin>
23        <groupId>org.apache.maven.plugins</groupId>
24        <artifactId>maven-antrun-plugin</artifactId>
25    </plugin>
26    <plugin>
27        <groupId>org.apache.maven.plugins</groupId>
28        <artifactId>maven-site-plugin</artifactId>
29    </plugin>
30    <plugin>
31        <groupId>org.apache.maven.plugins</groupId>
32        <artifactId>maven-checkstyle-plugin</artifactId>
33    </plugin>
34    <plugin>
35        <groupId>org.codehaus.mojo</groupId>
36        <artifactId>findbugs-maven-plugin</artifactId>
37    </plugin>
38    <plugin>
39        <groupId>org.codehaus.mojo</groupId>
40        <artifactId>versions-maven-plugin</artifactId>
41    </plugin>
42    <plugin>
43        <groupId>org.apache.maven.plugins</groupId>
44        <artifactId>maven-release-plugin</artifactId>
45    </plugin>
46    <plugin>
47        <groupId>org.apache.maven.plugins</groupId>
48        <artifactId>maven-dependency-plugin</artifactId>
49    </plugin>
50</plugins>

15.4.2. Dependencies

All Griffon core dependencies have benn added using a <dependencyManagement> block as shown next

  1<dependencyManagement>
  2    <dependencies>
  3        <dependency>
  4            <groupId>org.codehaus.griffon</groupId>
  5            <artifactId>griffon-core</artifactId>
  6            <version>${griffon.version}</version>
  7            <scope>compile</scope>
  8        </dependency>
  9        <dependency>
 10            <groupId>org.codehaus.griffon</groupId>
 11            <artifactId>griffon-core-compile</artifactId>
 12            <version>${griffon.version}</version>
 13            <scope>provided</scope>
 14        </dependency>
 15        <dependency>
 16            <groupId>org.codehaus.griffon</groupId>
 17            <artifactId>griffon-core-test</artifactId>
 18            <version>${griffon.version}</version>
 19            <scope>test</scope>
 20        </dependency>
 21        <dependency>
 22            <groupId>org.codehaus.griffon</groupId>
 23            <artifactId>griffon-groovy</artifactId>
 24            <version>${griffon.version}</version>
 25            <scope>compile</scope>
 26        </dependency>
 27        <dependency>
 28            <groupId>org.codehaus.griffon</groupId>
 29            <artifactId>griffon-groovy-compile</artifactId>
 30            <version>${griffon.version}</version>
 31            <scope>provided</scope>
 32        </dependency>
 33        <dependency>
 34            <groupId>org.codehaus.griffon</groupId>
 35            <artifactId>griffon-swing</artifactId>
 36            <version>${griffon.version}</version>
 37            <scope>compile</scope>
 38        </dependency>
 39        <dependency>
 40            <groupId>org.codehaus.griffon</groupId>
 41            <artifactId>griffon-swing-groovy</artifactId>
 42            <version>${griffon.version}</version>
 43            <scope>compile</scope>
 44        </dependency>
 45        <dependency>
 46            <groupId>org.codehaus.griffon</groupId>
 47            <artifactId>griffon-fest-test</artifactId>
 48            <version>${griffon.version}</version>
 49            <scope>test</scope>
 50        </dependency>
 51        <dependency>
 52            <groupId>org.codehaus.griffon</groupId>
 53            <artifactId>griffon-javafx</artifactId>
 54            <version>${griffon.version}</version>
 55            <scope>compile</scope>
 56        </dependency>
 57        <dependency>
 58            <groupId>org.codehaus.griffon</groupId>
 59            <artifactId>griffon-javafx-groovy</artifactId>
 60            <version>${griffon.version}</version>
 61            <scope>compile</scope>
 62        </dependency>
 63        <dependency>
 64            <groupId>org.codehaus.griffon</groupId>
 65            <artifactId>griffon-lanterna</artifactId>
 66            <version>${griffon.version}</version>
 67            <scope>compile</scope>
 68        </dependency>
 69        <dependency>
 70            <groupId>org.codehaus.griffon</groupId>
 71            <artifactId>griffon-lanterna-groovy</artifactId>
 72            <version>${griffon.version}</version>
 73            <scope>compile</scope>
 74        </dependency>
 75        <dependency>
 76            <groupId>org.codehaus.griffon</groupId>
 77            <artifactId>griffon-pivot</artifactId>
 78            <version>${griffon.version}</version>
 79            <scope>compile</scope>
 80        </dependency>
 81        <dependency>
 82            <groupId>org.codehaus.griffon</groupId>
 83            <artifactId>griffon-pivot-groovy</artifactId>
 84            <version>${griffon.version}</version>
 85            <scope>compile</scope>
 86        </dependency>
 87        <dependency>
 88            <groupId>org.codehaus.griffon</groupId>
 89            <artifactId>griffon-pivot-test</artifactId>
 90            <version>${griffon.version}</version>
 91            <scope>test</scope>
 92        </dependency>
 93        <dependency>
 94            <groupId>org.codehaus.griffon</groupId>
 95            <artifactId>griffon-guice</artifactId>
 96            <version>${griffon.version}</version>
 97            <scope>runtime</scope>
 98        </dependency>
 99        <dependency>
100            <groupId>org.codehaus.groovy</groupId>
101            <artifactId>groovy-all</artifactId>
102            <version>${groovy.version}</version>
103            <scope>test</scope>
104        </dependency>
105        <dependency>
106            <groupId>org.spockframework</groupId>
107            <artifactId>spock-core</artifactId>
108            <version>${spock.version}</version>
109            <scope>test</scope>
110        </dependency>
111    </dependencies>
112</dependencyManagement>

It’s very important you take special note of the dependencies ending with -compile. As an application developer these dependencies belong to the provided scope, as these dependencies contain functionality that should not be exposed at runtime, such as compile-time metadata generation via Jipsy, Gipsy and other AST transformations.

You must exclude these dependencies from the maven-surefire-plugin. The following is the default configuration provided by the master pom

 1<plugin>
 2    <groupId>org.apache.maven.plugins</groupId>
 3    <artifactId>maven-surefire-plugin</artifactId>
 4    <version>${plugin.surefire.version}</version>
 5    <inherited>true</inherited>
 6    <configuration>
 7        <includes>
 8            <include>**/*Test.*</include>
 9            <include>**/*Spec.*</include>
10        </includes>
11        <classpathDependencyExcludes>
12            <classpathDependencyExclude>
13                org.codehaus.griffon:griffon-core-compile
14            </classpathDependencyExclude>
15            <classpathDependencyExclude>
16                org.codehaus.griffon:griffon-groovy-compile
17            </classpathDependencyExclude>
18        </classpathDependencyExcludes>
19    </configuration>
20</plugin>

15.4.3. Repositories

The following repositories are added by default

1<repositories>
2    <repository>
3        <id>jcenter</id>
4        <url>http://jcenter.bintray.com/</url>
5    </repository>
6</repositories>

15.4.4. Profiles

The master pom enables a few profiles to take care of special tasks.

Run

This profile compiles and runs the application. Enable it with maven -Prun.

 1<profile>
 2    <id>run</id>
 3    <build>
 4        <defaultGoal>process-classes</defaultGoal>
 5        <plugins>
 6            <plugin>
 7                <groupId>org.codehaus.mojo</groupId>
 8                <artifactId>exec-maven-plugin</artifactId>
 9                <version>${plugin.exec.version}</version>
10                <inherited>true</inherited>
11                <configuration>
12                    <mainClass>${application.main.class}</mainClass>
13                </configuration>
14                <executions>
15                    <execution>
16                        <id>run-app</id>
17                        <phase>process-classes</phase>
18                        <goals>
19                            <goal>java</goal>
20                        </goals>
21                    </execution>
22                </executions>
23            </plugin>
24        </plugins>
25    </build>
26</profile>
Distribution

This profile packages the application using the maven-assembly-plugin. Enable it with maven -Pdistribution.

 1<profile>
 2    <id>distribution</id>
 3    <build>
 4        <defaultGoal>package</defaultGoal>
 5        <plugins>
 6            <plugin>
 7                <groupId>org.apache.maven.plugins</groupId>
 8                <artifactId>maven-assembly-plugin</artifactId>
 9                <version>${plugin.assembly.version}</version>
10                <configuration>
11                    <descriptors>
12                        <descriptor>maven/assembly-descriptor.xml</descriptor>
13                    </descriptors>
14                    <outputDirectory>${project.build.directory}/distributions</outputDirectory>
15                    <workDirectory>${project.build.directory}/assembly/work</workDirectory>
16                </configuration>
17                <executions>
18                    <execution>
19                        <id>make-distribution</id>
20                        <phase>package</phase>
21                        <goals>
22                            <goal>assembly</goal>
23                        </goals>
24                    </execution>
25                </executions>
26            </plugin>
27        </plugins>
28    </build>
29</profile>
Izpack

This profile creates an universal installer using IzPack. Enable it with maven -izpack. You must execute the distribution profile before running the izpack profile. You CAN NOT combine both profiles.

 1<profile>
 2    <id>izpack</id>
 3    <dependencies>
 4        <dependency>
 5            <groupId>org.codehaus.izpack</groupId>
 6            <artifactId>izpack-standalone-compiler</artifactId>
 7            <version>${izpack-standalone.version}</version>
 8            <optional>true</optional>
 9            <scope>provided</scope>
10        </dependency>
11    </dependencies>
12    <build>
13        <defaultGoal>process-sources</defaultGoal>
14        <plugins>
15            <plugin>
16                <groupId>org.apache.maven.plugins</groupId>
17                <artifactId>maven-antrun-plugin</artifactId>
18                <version>${plugin.antrun.version}</version>
19                <executions>
20                    <execution>
21                        <id>process-sources</id>
22                        <phase>process-sources</phase>
23                        <goals>
24                            <goal>run</goal>
25                        </goals>
26                        <configuration>
27                            <target>
28                                <ant antfile="maven/prepare-izpack.xml"/>
29                            </target>
30                        </configuration>
31                    </execution>
32                </executions>
33            </plugin>
34        </plugins>
35    </build>
36</profile>

15.5. IntelliJ IDEA

There is no need to install a plugin in IntelliJ Idea in order to develop Griffon applications, as every Griffon project is a valid Gradle/Maven project.

There’s however special support for code suggestions when dealing with Groovy artifacts or Groovy classes annotated with a special set of annotations. This support is delivered using the GDSL feature found in IntelliJ. The following table summarizes the enhancements delivered by this feature

Table 13. Artifacts
Path Type

griffon-app/controllers/**/*Controller.groovy

griffon.core.artifact.GriffonController

griffon-app/models/**/*Model.groovy

griffon.core.artifact.GriffonModel

griffon-app/services/**/*Service.groovy

griffon.core.artifact.GriffonService

griffon-app/views/**/*View.groovy

griffon.core.artifact.GriffonView

Table 14. Annotations
Annotation Type

@griffon.transform.EventPublisher

griffon.core.event.EventPublisher

@griffon.transform.MVCAware

griffon.core.mvc.MVCHandler

@griffon.transform.ThreadingAware

griffon.core.threading.ThreadingHandler

@griffon.transform.ResourcesAware

griffon.core.resources.ResourceHandler

@griffon.transform.MessageSourceAware

 griffon.core.i18n.MessageSource

@griffon.transform.ResourceResolverAware

 griffon.core.resources.ResourceResolver

@griffon.transform.Observable

 griffon.core.Observable

@griffon.transform.Vetoable

griffon.core.Vetoable

IntelliJ Community Edition has a plugin for developing Griffon 1.x applications. This plugin is not needed for Griffon 2.x.

15.6. Eclipse

There is no need to install a plugin in Eclipse in order to develop Griffon applications, as every Griffon project is a valid Gradle/Maven project.

There’s however special support for code suggestions when dealing with Groovy artifacts or Groovy classes annotated with a special set of annotations. This support is delivered using the DSLD feature found in Eclipse if the Groovy Eclipse plugin is installed. The following table summarizes the enhancements delivered by this feature

Table 15. Artifacts
Path Type

griffon-app/controllers/**/*Controller.groovy

griffon.core.artifact.GriffonController

griffon-app/models/**/*Model.groovy

griffon.core.artifact.GriffonModel

griffon-app/services/**/*Service.groovy

griffon.core.artifact.GriffonService

griffon-app/views/**/*View.groovy

griffon.core.artifact.GriffonView

Table 16. Annotations
Annotation Type

@griffon.transform.EventPublisher

griffon.core.event.EventPublisher

@griffon.transform.MVCAware

griffon.core.mvc.MVCHandler

@griffon.transform.ThreadingAware

griffon.core.threading.ThreadingHandler

@griffon.transform.ResourcesAware

griffon.core.resources.ResourceHandler

@griffon.transform.MessageSourceAware

 griffon.core.i18n.MessageSource

@griffon.transform.ResourceResolverAware

 griffon.core.resources.ResourceResolver

@griffon.transform.Observable

 griffon.core.Observable

@griffon.transform.Vetoable

griffon.core.Vetoable

16. Contributing

Griffon is an open source project with an active community and we rely heavily on that community to help make Griffon better. As such, there are various ways in which people can contribute to Griffon. One of these is by writing plugins and making them publicly available. In this chapter, we’ll look at some of the other options.

16.1. Issues

Griffon uses JIRA to track issues in the ore framework, its documentation, its website, and many of the public plugins. If you’ve found a bug or wish to see a particular feature added, this is the place to start. You’ll need to create a (free) JIRA account in order to either submit an issue or comment on an existing one.

When submitting issues, please provide as much information as possible and in the case of bugs, make sure you explain which versions of Griffon and various plugins you are using. Also, an issue is much more likely to be dealt with if you attach a reproducible sample application.

16.1.1. Reviewing issues

There are quite a few old issues in JIRA, some of which may no longer be valid. The core team can’t track down these alone, so a very simple contribution that you can make is to verify one or two issues occasionally.

Which issues need verification? A shared JIRA filter will display all issues that haven’t been resolved. Just pick one or two of them and check whether they are still relevant.

Once you’ve verified an issue, simply edit it by adding a "Last Reviewed" comment. If you think the issue can be closed, then also add a "Flagged" comment and a short explanation why.

16.2. Build

If you’re interested in contributing fixes and features to the core framework, you will have to learn how to get hold of the project’s source, build it and test it with your own applications. Before you start, make sure you have:

  • A JDK (1.7 or above)

  • A git client

Once you have all the pre-requisite packages installed, the next step is to download the Griffon source code, which is hosted at GitHub in several repositories owned by the griffon GitHub user. This is a simple case of cloning the repository you’re interested in. For example, to get the core framework run:

$ git clone https://github.com/griffon/griffon.git

This will create a griffon directory in your current working directory containing all the project source files. The next step is to get a Griffon installation from the source.

16.2.1. Running the test suite

All you have to do to run the full suite of tests is:

$ ./gradlew test

These will take a while, so consider running individual tests using the command line. For example, to run the test case @MappingDslTests@ simply execute the following command:

$ ./gradlew -Dtest.single=EnvironmentTests :griffon-core:test

Note that you need to specify the sub-project that the test case resides in, because the top-level "test" target won’t work….

16.2.2. Developing in IntelliJ IDEA

You need to run the following gradle task:

$ ./gradlew idea

Then open the project file which is generated in IDEA. Simple!

16.3. Patches

If you want to submit patches to the project, you simply need to fork the repository on GitHub rather than clone it directly. Then you will commit your changes to your fork and send a pull request for a core team member to review.

16.3.1. Forking and Pull Requests

One of the benefits of GitHub is the way that you can easily contribute to a project by forking the repository and sending pull requests with your changes.

What follows are some guidelines to help ensure that your pull requests are speedily dealt with and provide the information we need. They will also make your life easier!

Create a local branch for your changes

Your life will be greatly simplified if you create a local branch to make your changes on. For example, as soon as you fork a repository and clone the fork locally, execute

$ git checkout -b mine

This will create a new local branch called mine based off the master branch. Of course, you can name the branch whatever you like - you don’t have to use mine.

Create JIRAs for non-trivial changes

For any non-trivial changes, raise a JIRA issue if one doesn’t already exist. That helps us keep track of what changes go into each new version of Griffon.

Include JIRA issue ID in commit messages

This may not seem particularly important, but having a JIRA issue ID in a commit message means that we can find out at a later date why a change was made. Include the ID in any and all commits that relate to that issue. If a commit isn’t related to an issue, then there’s no need to include an issue ID.

Make sure your fork is up to date

Since the core developers must merge your commits into the main repository, it makes life much easier if your fork on GitHub is up to date before you send a pull request.

Let’s say you have the main repository set up as a remote called upstream and you want to submit a pull request. Also, all your changes are currently on the local mine branch but not on master. The first step involves pulling any changes from the main repository that have been added since you last fetched and merged:

$ git checkout master
$ git pull upstream

This should complete without any problems or conflicts. Next, rebase your local branch against the now up-to-date master:

$ git checkout mine
$ git rebase master

What this does is rearrange the commits such that all of your changes come after the most recent one in master. Think adding some cards to the top of a deck rather than shuffling them into the pack.

You’ll now be able to do a clean merge from your local branch to master:

$ git checkout master
$ git merge mine

Finally, you must push your changes to your remote repository on GitHub, otherwise the core developers won’t be able to pick them up:

$ git push

You’re now ready to send the pull request from the GitHub user interface.

Say what your pull request is for

A pull request can contain any number of commits and it may be related to any number of issues. In the pull request message, please specify the IDs of all issues that the request relates to. Also give a brief description of the work you have done, such as: "I refactored the resources injector and added support for custom number editors (GRIFFON-xxxx)". :leveloffset: 1

Appendix A: Migrating from Griffon 1.x

Griffon 2.x has tried to be as close as possible to Griffon 1.x in terms of concepts and APIs, however some changes were introduced that require your attention when migrating an application from Griffon 1.x

1.1. Build Configuration

There is no longer a specific Griffon buildtime tool nor configuration settings. You must pick a build tool (we recommend Gradle) and use that tool’s configuration to match your needs. The simplest way to get started is to select an appropriate Lazybones template to create an empty project, then copy the files you need.

Build time plugins and scripts are now within the realm of a particular build tool.

1.1.1. Dependencies

Dependencies used to be configured inside griffon-app/conf/BuildConfig.groovy. Now that the file is gone you must configured dependencies using the native support of the build tool you choose. Of particular note is that all griffon dependencies are now standard JAR archives available from Maven compatible repositories, so they should work no matter which dependency resolution you pick.

1.2. Runtime Configuration

1.2.1. Application and Config Scripts

The files Application.groovy and Config.groovy have been merged into a single file : Config.groovy. The log4j DSL is no longer used, so please move your logging settings to src/resources/log4j.properties; you can also use the XML variant if you want. Griffon 2.x does not forces Log4j on you either, you’re free to pick a suitable Slf4j binding of your choice.

1.2.2. Builder Script

The Builder.groovy is no longer required. Its functions are now handled by BuilderCustomizer classes bound in a Module. You must add griffon-groovy-2.0.0.RC1.jar as a compile dependency in order to use BuilderCustomizers. Here’s an example for adding a Miglayout customization

src/main/java/griffon/builder/swing/MiglayoutSwingBuilderCustomizer.java
 1package griffon.builder.swing;
 2
 3import griffon.inject.DependsOn;
 4import groovy.swing.factory.LayoutFactory;
 5import net.miginfocom.swing.MigLayout;
 6import groovy.util.Factory;
 7import org.codehaus.griffon.runtime.groovy.view.AbstractBuilderCustomizer;
 8
 9import javax.inject.Named;
10import java.util.LinkedHashMap;
11import java.util.Map;
12
13@DependsOn({"swing"})
14@Named("miglayout-swing")
15public class MiglayoutSwingBuilderCustomizer extends AbstractBuilderCustomizer {
16    public MiglayoutSwingBuilderCustomizer() {
17        Map<String, Factory> factories = new LinkedHashMap<>();
18        factories.put("migLayout", new LayoutFactory(MigLayout.class));
19        setFactories(factories);
20    }
21}
src/main/java/org/codehaus/griffon/runtime/miglayout/MiglayoutSwingGroovyModule.java
 1package org.codehaus.griffon.runtime.miglayout;
 2
 3import griffon.builder.swing.MiglayoutSwingBuilderCustomizer;
 4import griffon.core.injection.Module;
 5import griffon.inject.DependsOn;
 6import griffon.util.BuilderCustomizer;
 7import org.codehaus.griffon.runtime.core.injection.AbstractModule;
 8import org.kordamp.jipsy.ServiceProviderFor;
 9
10import javax.inject.Named;
11
12@DependsOn("swing-groovy")
13@Named("miglayout-swing-groovy")
14@ServiceProviderFor(Module.class)
15public class MiglayoutSwingGroovyModule extends AbstractModule {
16    @Override
17    protected void doConfigure() {
18        // tag::bindings[]
19        bind(BuilderCustomizer.class)
20            .to(MiglayoutSwingBuilderCustomizer.class)
21            .asSingleton();
22        // end::bindings[]
23    }
24}

1.2.3. Events Script

The Events script usually placed in griffon-app/conf should be moved to the main source directory (src/main/java or src/main/groovy depending on your preferences). The following snippet shows an skeleton implementation

src/main/groovy/com/acme/ApplicationEventHandler.groovy
1package com.acme
2
3import griffon.core.event.EventHandler
4
5class ApplicationEventHandler implements EventHandler {
6    // event handlers as public methods
7}

You must add event handlers as public methods following the conventions explained in the consuming events section. Don’t forget to register this class using a module. The default ApplicationModule class provided by all basic project templates is a good start.

src/main/groovy/com/acme/ApplicationModule.groovy
 1package com.acme
 2
 3import griffon.core.event.EventHandler
 4import griffon.core.injection.Module
 5import org.codehaus.griffon.runtime.core.injection.AbstractModule
 6import org.kordamp.jipsy.ServiceProviderFor
 7
 8@ServiceProviderFor(Module)
 9class ApplicationModule extends AbstractModule {
10    @Override
11    protected void doConfigure() {
12        bind(EventHandler)
13            .to(ApplicationEventHandler)
14            .asSingleton()
15    }
16}

1.3. Artifacts

All artifacts must be annotated with @ArtifactProviderFor without exception. Failure to follow this rule will make Griffon miss the artifact during bootstrap. The value for this annotation must be the basic interface that defines the artifact’s type, for example

griffon-app/models/sample/SampleModel.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonModel
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonModel)
 7class SampleModel {
 8    ...
 9}

1.3.1. Controllers

Closure properties as actions are no longer supported. All actions must be defined as public methods.

1.3.2. Views

View scripts have been upgraded to classes. You can use the following skeleton View class as an starting point

griffon-app/views/sample/SampleView.groovy
 1package sample
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class SampleView {
 8    def builder
 9    def model
10
11    void initUI() {
12        builder.with {
13            (1)
14        }
15    }
16}
1 UI components

Next, place the contents of your old View script inside 1.

1.4. Lifecycle Scripts

These scripts must be migrated to full classes too. Here’s the basic skeleton code for any lifecycle handler

griffon-app/lifecycle/Initialize.groovy
 1import griffon.core.GriffonApplication
 2import org.codehaus.griffon.runtime.core.AbstractLifecycleHandler
 3
 4import javax.annotation.Nonnull
 5import javax.inject.Inject
 6
 7class Initialize extends AbstractLifecycleHandler {
 8    @Inject
 9    Initialize(@Nonnull GriffonApplication application) {
10        super(application)
11    }
12
13    @Override
14    void execute() {
15        // do the work
16    }
17}

1.5. Tests

Griffon 2.x no longer segregates tests between unit and functional. You must use your build tool’s native support for both types (this is quite simple with Gradle). Move all unit tests under src/test/java or src/test/groovy depending on your choice of main language. The base GriffonUnitTestCase class no longer exist. Use any testing framework you’re comfortable with to write unit tests (Junit4, Spock, etc). Use the following template if you need to write artifact tests

src/test/sample/SampleControllerTest.groovy
 1package sample
 2
 3import griffon.core.test.GriffonUnitRule
 4import griffon.core.test.TestFor
 5import org.junit.Rule
 6import org.junit.Test
 7
 8import static com.jayway.awaitility.Awaitility.await
 9import static java.util.concurrent.TimeUnit.SECONDS
10
11@TestFor(SampleController)
12class SampleControllerTest {
13    private SampleController controller
14
15    @Rule
16    public final GriffonUnitRule griffon = new GriffonUnitRule()
17
18    @Test
19    void testControllerAction() {
20        // given:
21        // setup collaborators
22
23        // when:
24        // stimulus
25        controller.invokeAction('nameOfTheAction')
26
27        // then:
28        // use Awaitility to successfully wait for async processing to finish
29        await().atMost(2, SECONDS)
30        assert someCondition
31    }
32}

Appendix B: Module Bindings

The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.

1.1. Core

Module name: core

bind(ApplicationClassLoader.class)
    .to(DefaultApplicationClassLoader.class)
    .asSingleton();

bind(ApplicationConfigurer.class)
    .to(DefaultApplicationConfigurer.class)
    .asSingleton();

bind(ResourceHandler.class)
    .to(DefaultResourceHandler.class)
    .asSingleton();

bind(CompositeResourceBundleBuilder.class)
    .to(DefaultCompositeResourceBundleBuilder.class)
    .asSingleton();

bind(ResourceBundle.class)
    .withClassifier(named("applicationResourceBundle"))
    .toProvider(new ResourceBundleProvider("Config"))
    .asSingleton();

bind(Configuration.class)
    .to(DefaultConfiguration.class)
    .asSingleton();

bind(ExecutorServiceManager.class)
    .to(DefaultExecutorServiceManager.class)
    .asSingleton();

bind(EventRouter.class)
    .withClassifier(named("applicationEventRouter"))
    .to(DefaultEventRouter.class)
    .asSingleton();

bind(EventRouter.class)
    .to(DefaultEventRouter.class);

bind(ResourceResolver.class)
    .withClassifier(named("applicationResourceResolver"))
    .toProvider(new ResourceResolverProvider("resources"))
    .asSingleton();

bind(MessageSource.class)
    .withClassifier(named("applicationMessageSource"))
    .toProvider(new MessageSourceProvider("messages"))
    .asSingleton();

bind(ResourceInjector.class)
    .withClassifier(named("applicationResourceInjector"))
    .to(DefaultApplicationResourceInjector.class)
    .asSingleton();

bind(UIThreadManager.class)
    .to(DefaultUIThreadManager.class)
    .asSingleton();

bind(MVCGroupManager.class)
    .to(DefaultMVCGroupManager.class)
    .asSingleton();

for (Lifecycle lifecycle : Lifecycle.values()) {
    bind(LifecycleHandler.class)
        .withClassifier(named(lifecycle.getName()))
        .toProvider(new LifecycleHandlerProvider(lifecycle.getName()))
        .asSingleton();
}

bind(WindowManager.class)
    .to(NoopWindowManager.class)
    .asSingleton();

bind(ActionManager.class)
    .to(DefaultActionManager.class)
    .asSingleton();

bind(ArtifactManager.class)
    .to(DefaultArtifactManager.class)
    .asSingleton();

bind(ArtifactHandler.class)
    .to(ModelArtifactHandler.class)
    .asSingleton();

bind(ArtifactHandler.class)
    .to(ViewArtifactHandler.class)
    .asSingleton();

bind(ArtifactHandler.class)
    .to(ControllerArtifactHandler.class)
    .asSingleton();

bind(ArtifactHandler.class)
    .to(ServiceArtifactHandler.class)
    .asSingleton();

bind(PlatformHandler.class)
    .toProvider(PlatformHandlerProvider.class)
    .asSingleton();

bind(AddonManager.class)
    .to(DefaultAddonManager.class)
    .asSingleton();

bind(EventHandler.class)
    .to(DefaultEventHandler.class)
    .asSingleton();

bind(GriffonExceptionHandler.class)
    .asSingleton();

1.2. Groovy

Module name: groovy

bind(ConfigReader.class)
    .toProvider(ConfigReader.Provider.class)
    .asSingleton();

bind(CompositeResourceBundleBuilder.class)
    .to(GroovyAwareCompositeResourceBundleBuilder.class)
    .asSingleton();

bind(GriffonAddon.class)
    .to(GroovyAddon.class)
    .asSingleton();

bind(MVCGroupManager.class)
    .to(GroovyAwareMVCGroupManager.class)
    .asSingleton();

bind(BuilderCustomizer.class)
    .to(CoreBuilderCustomizer.class)
    .asSingleton();

1.3. Swing

Module name: swing

bind(SwingWindowDisplayHandler.class)
    .withClassifier(named("defaultWindowDisplayHandler"))
    .to(DefaultSwingWindowDisplayHandler.class)
    .asSingleton();

bind(SwingWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(ConfigurableSwingWindowDisplayHandler.class)
    .asSingleton();

bind(WindowManager.class)
    .to(DefaultSwingWindowManager.class)
    .asSingleton();

bind(UIThreadManager.class)
    .to(SwingUIThreadManager.class)
    .asSingleton();

bind(ActionManager.class)
    .to(SwingActionManager.class)
    .asSingleton();

bind(GriffonAddon.class)
    .to(SwingAddon.class)
    .asSingleton();

1.4. Swing Builder

Module name: swing-groovy
Depends on: swing

bind(BuilderCustomizer.class)
    .to(SwingBuilderCustomizer.class)
    .asSingleton();
bind(SwingWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(GroovyAwareConfigurableSwingWindowDisplayHandler.class)
    .asSingleton();

1.5. JavaFX

Module name: javafx

bind(JavaFXWindowDisplayHandler.class)
    .withClassifier(named("defaultWindowDisplayHandler"))
    .to(DefaultJavaFXWindowDisplayHandler.class)
    .asSingleton();

bind(JavaFXWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(ConfigurableJavaFXWindowDisplayHandler.class)
    .asSingleton();

bind(WindowManager.class)
    .to(DefaultJavaFXWindowManager.class)
    .asSingleton();

bind(UIThreadManager.class)
    .to(JavaFXUIThreadManager.class)
    .asSingleton();

bind(ActionManager.class)
    .to(JavaFXActionManager.class)
    .asSingleton();

1.6. JavaFX Builder

Module name: javafx-groovy
Depends on: javafx

bind(BuilderCustomizer.class)
    .to(JavafxBuilderCustomizer.class)
    .asSingleton();
bind(JavaFXWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(GroovyAwareConfigurableJavaFXWindowDisplayHandler.class)
    .asSingleton();

1.7. Lanterna

Module name: lanterna

bind(GUIScreen.class)
    .toProvider(GUIScreenProvider.class)
    .asSingleton();

bind(LanternaWindowDisplayHandler.class)
    .withClassifier(named("defaultWindowDisplayHandler"))
    .to(DefaultLanternaWindowDisplayHandler.class)
    .asSingleton();

bind(LanternaWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(ConfigurableLanternaWindowDisplayHandler.class)
    .asSingleton();

bind(WindowManager.class)
    .to(DefaultLanternaWindowManager.class)
    .asSingleton();

bind(UIThreadManager.class)
    .to(LanternaUIThreadManager.class)
    .asSingleton();

bind(ActionManager.class)
    .to(LanternaActionManager.class)
    .asSingleton();

1.8. Lanterna Builder

Module name: lanterna-groovy
Depends on: lanterna

bind(BuilderCustomizer.class)
    .to(LanternaBuilderCustomizer.class)
    .asSingleton();
bind(LanternaWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(GroovyAwareConfigurableLanternaWindowDisplayHandler.class)
    .asSingleton();

1.9. Pivot

Module name: pivot

bind(PivotWindowDisplayHandler.class)
    .withClassifier(named("defaultWindowDisplayHandler"))
    .to(DefaultPivotWindowDisplayHandler.class)
    .asSingleton();

bind(PivotWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(ConfigurablePivotWindowDisplayHandler.class)
    .asSingleton();

bind(WindowManager.class)
    .to(DefaultPivotWindowManager.class)
    .asSingleton();

bind(UIThreadManager.class)
    .to(PivotUIThreadManager.class)
    .asSingleton();

bind(ActionManager.class)
    .to(PivotActionManager.class)
    .asSingleton();

1.10. Pivot Builder

Module name: pivot-groovy
Depends on: pivot

bind(BuilderCustomizer.class)
    .to(PivotBuilderCustomizer.class)
    .asSingleton();
bind(PivotWindowDisplayHandler.class)
    .withClassifier(named("windowDisplayHandler"))
    .to(GroovyAwareConfigurablePivotWindowDisplayHandler.class)
    .asSingleton();

Appendix C: AST Transformations

The following list summarizes all AST transformations available to Groovy based projects when the griffon-groovy-compile-2.0.0.RC1.jar dependency is added to a project

Appendix D: Sample Applications

This appendix showcases the same application implemented with different languages and different UI toolkits. The application presents a very simple form where a user is asked for his or her name. Once a button is clicked a reply will appear within the same window. In order to achieve this Models hold 2 observable properties: the first to keep track of the input, the second to do the same for the output. Views are only concerned with values coming from the model and as such never interact directly with Controllers. Controllers in turn only interact with Models and a Service used to transform the input value into the output value. The single controller action observes the rules for invoking computations outside of the UI thread and updating UI components inside the UI thread.

These are some screenshots of each one of the applications we’ll cover next.

Swing
Figure 1. Swing Sample
JavaFX
Figure 2. JavaFx Sample
JavaFX
Figure 3. Lanterna Sample
JavaFX
Figure 4. Pivot Sample

The goal of these applications is to showcase the similarities and differences of each one of them given their implementation language and UI toolkit.

1.1. Swing

Let’s begin with Swing, as it’s probably the most well known Java UI toolkit. First we’ll show the Java version of an artifact, then we’ll show it’s Groovy counterpart.

1.1.1. Model

Instances of GriffonModel implement the Observable interface which means they know how to handle observable properties out of the box. We only need to be concerned in triggering a java.beans.PropertyChangeEvent when a property changes value.

sample-swing-java/griffon-app/models/sample/swing/java/SampleModel.java
 1package sample.swing.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonModel;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonModel.class)
12public class SampleModel extends AbstractGriffonModel {
13    private String input;                                                (1)
14    private String output;                                               (1)
15
16    @Inject
17    public SampleModel(@Nonnull GriffonApplication application) {
18        super(application);
19    }
20
21    public String getInput() {                                           (2)
22        return input;
23    }
24
25    public void setInput(String input) {
26        firePropertyChange("input", this.input, this.input = input);     (3)
27    }
28
29    public String getOutput() {                                          (2)
30        return output;
31    }
32
33    public void setOutput(String output) {
34        firePropertyChange("output", this.output, this.output = output); (3)
35    }
36}
1 Define a private field for the property
2 Property accessor
3 Property mutator must fire a PropertyChangeEvent

The code is quite straight forward, there’s nothing much to see here other than making sure to follow the rules for creating observable properties. The Groovy version sports a short hand thanks to the usage of the @Observable AST transformation.

One key difference between the Java and the Groovy version is that the Groovy Model does not extend a particular class. It also This is due to Griffon being aware of its own conventions and applying the appropriate byte code manipulation (via AST transformations). The compiled Model class does implement the GriffonModel interface as required by the framework. This type of byte code manipulation is expected to work for every Groovy based artifact.

sample-swing-groovy/griffon-app/models/sample/swing/groovy/SampleModel.groovy
 1package sample.swing.groovy
 2
 3import griffon.core.artifact.GriffonModel
 4import griffon.metadata.ArtifactProviderFor
 5import griffon.transform.Observable
 6
 7@ArtifactProviderFor(GriffonModel)
 8class SampleModel {
 9    @Observable String input                                             (1)
10    @Observable String output                                            (1)
11}
1 Observable property

Properties become observable by simply annotating them with @Observable. The Groovy compiler will generate the required boilerplate code, which so happens to be functionally equivalent to what we showed in the Java version.

1.1.2. Controller

Controllers provide actions that are used to fill up the application’s interaction. They usually manipulate values coming from Views via Model properties. Controllers may rely on additional components, such as Services, to do they work. This is exactly our case as there’s a SampleService instance injected into our controllers.

sample-swing-java/griffon-app/controllers/sample/swing/java/SampleController.java
 1package sample.swing.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonController;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonController.class)
12public class SampleController extends AbstractGriffonController {
13    private SampleModel model;                                             (1)
14
15    @Inject
16    private SampleService sampleService;                                   (2)
17
18    @Inject
19    public SampleController(@Nonnull GriffonApplication application) {
20        super(application);
21    }
22
23    public void setModel(SampleModel model) {
24        this.model = model;
25    }
26
27    public void sayHello() {                                               (3)
28        final String result = sampleService.sayHello(model.getInput());
29        runInsideUIAsync(new Runnable() {                                  (4)
30            @Override
31            public void run() {
32                model.setOutput(result);
33            }
34        });
35    }
36}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

Of particular note is the fact that actions are always executed outside off the UI thread unless otherwise configured with an @Threading annotation. Once we have computed the right output we must inform the View of the new value. This is done by updating the model inside the UI thread 4.

sample-swing-groovy/griffon-app/controllers/sample/swing/groovy/SampleController.groovy
 1package sample.swing.groovy
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.metadata.ArtifactProviderFor
 5
 6import javax.inject.Inject
 7
 8@ArtifactProviderFor(GriffonController)
 9class SampleController {
10    SampleModel model                                                      (1)
11
12    @Inject
13    private SampleService sampleService                                    (2)
14
15    void sayHello() {                                                      (3)
16        String result = sampleService.sayHello(model.input)
17        model.output = result                                              (4)
18    }
19}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

The Groovy version of the Controller is much terser of course however there’s a nice feature available to Groovy Swing: Model properties bound to UI components are always updated inside the UI thread.

1.1.3. Service

Services are tasked to work with raw data and I/O, they should never interact with Views and Models directly, though you may have additional components injected to them. The following service shows another facility provide by the GriffonApplication interface: MessageSource, capable of resolving i18n-able resources.

sample-swing-java/griffon-app/services/sample/swing/java/SampleService.java
 1package sample.swing.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonService;
 5import griffon.core.i18n.MessageSource;
 6import griffon.metadata.ArtifactProviderFor;
 7import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService;
 8
 9import javax.annotation.Nonnull;
10import javax.inject.Inject;
11
12import static griffon.util.GriffonNameUtils.isBlank;
13import static java.util.Arrays.asList;
14
15@ArtifactProviderFor(GriffonService.class)
16public class SampleService extends AbstractGriffonService {
17    @Inject
18    public SampleService(@Nonnull GriffonApplication application) {
19        super(application);
20    }
21
22    public String sayHello(String input) {
23        MessageSource messageSource = getApplication().getMessageSource();
24        if (isBlank(input)) {
25            return messageSource.getMessage("greeting.default");
26        } else {
27            return messageSource.getMessage("greeting.parameterized", asList(input));
28        }
29    }
30}
sample-swing-groovy/griffon-app/services/sample/swing/groovy/SampleService.groovy
 1package sample.swing.groovy
 2
 3import griffon.core.artifact.GriffonService
 4import griffon.core.i18n.MessageSource
 5import griffon.metadata.ArtifactProviderFor
 6
 7import static griffon.util.GriffonNameUtils.isBlank
 8
 9@ArtifactProviderFor(GriffonService)
10class SampleService {
11    String sayHello(String input) {
12        MessageSource ms = application.messageSource
13        isBlank(input) ? ms.getMessage('greeting.default') : ms.getMessage('greeting.parameterized', [input])
14    }
15}

1.1.4. View

We come to the final piece of the puzzle: the View. Components are arranged in a one column vertical grid

sample-swing-java/griffon-app/views/sample/swing/java/SampleView.java
  1package sample.swing.java;
  2
  3import griffon.core.GriffonApplication;
  4import griffon.core.artifact.GriffonView;
  5import griffon.metadata.ArtifactProviderFor;
  6import org.codehaus.griffon.runtime.swing.artifact.AbstractSwingGriffonView;
  7
  8import javax.annotation.Nonnull;
  9import javax.inject.Inject;
 10import javax.swing.*;
 11import javax.swing.event.DocumentEvent;
 12import javax.swing.event.DocumentListener;
 13import java.awt.GridLayout;
 14import java.awt.Image;
 15import java.awt.Toolkit;
 16import java.beans.PropertyChangeEvent;
 17import java.beans.PropertyChangeListener;
 18import java.util.Collections;
 19
 20import static java.util.Arrays.asList;
 21import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
 22
 23@ArtifactProviderFor(GriffonView.class)
 24public class SampleView extends AbstractSwingGriffonView {
 25    private SampleController controller;                                         (1)
 26    private SampleModel model;                                                   (1)
 27
 28    @Inject
 29    public SampleView(@Nonnull GriffonApplication application) {
 30        super(application);
 31    }
 32
 33    public void setController(SampleController controller) {
 34        this.controller = controller;
 35    }
 36
 37    public void setModel(SampleModel model) {
 38        this.model = model;
 39    }
 40
 41    @Override
 42    public void initUI() {
 43        JFrame window = (JFrame) getApplication()
 44            .createApplicationContainer(Collections.<String, Object>emptyMap());
 45        window.setName("mainWindow");
 46        window.setTitle(getApplication().getConfiguration().getAsString("application.title"));
 47        window.setSize(320, 120);
 48        window.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
 49        window.setIconImage(getImage("/griffon-icon-48x48.png"));
 50        window.setIconImages(asList(
 51            getImage("/griffon-icon-48x48.png"),
 52            getImage("/griffon-icon-32x32.png"),
 53            getImage("/griffon-icon-16x16.png")
 54        ));
 55        getApplication().getWindowManager().attach("mainWindow", window);        (2)
 56
 57        window.getContentPane().setLayout(new GridLayout(4, 1));
 58        window.getContentPane().add(
 59            new JLabel(getApplication().getMessageSource().getMessage("name.label"))
 60        );
 61        final JTextField nameField = new JTextField();
 62        nameField.setName("inputField");
 63        nameField.getDocument().addDocumentListener(new DocumentListener() {     (3)
 64            @Override
 65            public void insertUpdate(DocumentEvent e) {
 66                model.setInput(nameField.getText());
 67            }
 68
 69            @Override
 70            public void removeUpdate(DocumentEvent e) {
 71                model.setInput(nameField.getText());
 72            }
 73
 74            @Override
 75            public void changedUpdate(DocumentEvent e) {
 76                model.setInput(nameField.getText());
 77            }
 78        });
 79        window.getContentPane().add(nameField);
 80
 81        Action action = toolkitActionFor(controller, "sayHello");                (4)
 82        final JButton button = new JButton(action);
 83        button.setName("sayHelloButton");
 84        window.getContentPane().add(button);
 85
 86        final JLabel outputLabel = new JLabel();
 87        outputLabel.setName("outputLabel");
 88        model.addPropertyChangeListener("output", new PropertyChangeListener() { (3)
 89            @Override
 90            public void propertyChange(PropertyChangeEvent evt) {
 91                outputLabel.setText(String.valueOf(evt.getNewValue()));
 92            }
 93        });
 94        window.getContentPane().add(outputLabel);
 95    }
 96
 97    private Image getImage(String path) {
 98        return Toolkit.getDefaultToolkit().getImage(SampleView.class.getResource(path));
 99    }
100}
1 MVC member injected by MVCGroupManager
2 Attach window to WindowManager
3 Apply component-to-model binding
4 Hook in controller action by name

Here we can appreciate at 3 how Model properties are bound to View components, also how controller actions can be transformed into toolkit actions that may be applied to buttons 4 for example.

sample-swing-groovy/griffon-app/views/sample/swing/groovy/SampleView.groovy
 1package sample.swing.groovy
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class SampleView {
 8    FactoryBuilderSupport builder                                                              (1)
 9    SampleModel model                                                                          (1)
10
11    void initUI() {
12        builder.with {
13            application(title: application.configuration['application.title'],                 (2)
14                id: 'mainWindow', size: [320, 160],
15                iconImage: imageIcon('/griffon-icon-48x48.png').image,
16                iconImages: [imageIcon('/griffon-icon-48x48.png').image,
17                    imageIcon('/griffon-icon-32x32.png').image,
18                    imageIcon('/griffon-icon-16x16.png').image]) {
19                gridLayout(rows: 4, cols: 1)
20                label(application.messageSource.getMessage('name.label'))
21                textField(id: 'inputField', text: bind(target: model, 'input'))                 (3)
22                button(sayHelloAction, id: 'sayHelloButton')                                    (4)
23                label(id: 'outputLabel', text: bind { model.output })                           (3)
24            }
25        }
26    }
27}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Apply component-to-model binding
4 Hook in controller action by name

The Groovy version is again much terser thanks to the SwingBuilder DSL. Notice how easy it’s to bind 3 model properties using the +bind+ node. The controller action is also transformed into an UI toolkit specific action however this time it’s easier to grab: by convention all controller actions are exposed as variables to the corresponding builder.

1.1.5. Resources

The last file we’ll touch is the one that holds de i18n-able content. Griffon supports several formats. Here we’re showing the standard one as found in many Java projects.

sample-swing-java/griffon-app/i18n/messages.properties
1name.label = Please enter your name
2greeting.default = Howdy stranger!
3greeting.parameterized = Hello {0}

1.2. JavaFX

JavaFx is a next generation UI toolkit and will eventually replace Swing, so it’s a good idea to get started with it now. Among the several features found in JavaFX you’ll find

  • observable properties

  • component styling with CSS

  • FXML: a declarative format for defining UIs

  • and more!

1.2.1. Model

sample-javafx-java/griffon-app/models/sample/javafx/java/SampleModel.java
 1package sample.javafx.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonModel;
 5import griffon.metadata.ArtifactProviderFor;
 6import javafx.beans.property.SimpleStringProperty;
 7import javafx.beans.property.StringProperty;
 8import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel;
 9
10import javax.annotation.Nonnull;
11import javax.inject.Inject;
12
13@ArtifactProviderFor(GriffonModel.class)
14public class SampleModel extends AbstractGriffonModel {
15    private StringProperty input;                                          (1)
16    private StringProperty output;                                         (1)
17
18    @Inject
19    public SampleModel(@Nonnull GriffonApplication application) {
20        super(application);
21    }
22
23    @Nonnull
24    public final StringProperty inputProperty() {                          (2)
25        if (input == null) {
26            input = new SimpleStringProperty(this, "input");
27        }
28        return input;
29    }
30
31    public void setInput(String input) {                                   (3)
32        inputProperty().set(input);
33    }
34
35    public String getInput() {                                             (3)
36        return input == null ? null : inputProperty().get();
37    }
38
39    @Nonnull
40    public final StringProperty outputProperty() {                         (2)
41        if (output == null) {
42            output = new SimpleStringProperty(this, "output");
43        }
44        return output;
45    }
46
47    public void setOutput(String output) {                                 (3)
48        outputProperty().set(output);
49    }
50
51    public String getOutput() {                                            (3)
52        return output == null ? null : outputProperty().get();
53    }
54}
1 Define a private field for the property
2 Property accessor
3 Pass-thru values to Property

The Model makes use of JavaFX’s observable properties .These properties are roughly equivalent in behavior to the ones we saw back in the Swing example, however they provide much more behavior than that; values may be cached automatically for example, avoiding bindings to be updated needlessly.

sample-javafx-groovy/griffon-app/models/sample/javafx/groovy/SampleModel.groovy
 1package sample.javafx.groovy
 2
 3import griffon.core.artifact.GriffonModel
 4import griffon.metadata.ArtifactProviderFor
 5import griffon.transform.FXObservable
 6
 7@ArtifactProviderFor(GriffonModel)
 8class SampleModel {
 9    @FXObservable String input                                           (1)
10    @FXObservable String output                                          (1)
11}
1 Observable property

Similarly to @Observable we find that Groovy based JavaFX models can use another AST transformation named @FXObservable. This transformation generates equivalent byte code to the Java based Model we saw earlier.

1.2.2. Controller

Have a look at the controller for this application. See if you can spot the differences between its Swing counterpart.

sample-javafx-java/griffon-app/controllers/sample/javafx/java/SampleController.java
 1package sample.javafx.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonController;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonController.class)
12public class SampleController extends AbstractGriffonController {
13    private SampleModel model;                                             (1)
14
15    @Inject
16    private SampleService sampleService;                                   (2)
17
18    @Inject
19    public SampleController(@Nonnull GriffonApplication application) {
20        super(application);
21    }
22
23    public void setModel(SampleModel model) {
24        this.model = model;
25    }
26
27    public void sayHello() {                                               (3)
28        final String result = sampleService.sayHello(model.getInput());
29        runInsideUIAsync(new Runnable() {                                  (4)
30            @Override
31            public void run() {
32                model.setOutput(result);
33            }
34        });
35    }
36}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

Did you spot any differences? No? That’s because the two controllers are 100% identical. How can this be? This is the power of separating concerns between MVC members. Because the Controller only talks to the View via the Model, and the Model exposes an identical contract for its properties we didn’t have to change the Controller at all.

sample-javafx-groovy/griffon-app/controllers/sample/javafx/groovy/SampleController.groovy
 1package sample.javafx.groovy
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.metadata.ArtifactProviderFor
 5
 6import javax.inject.Inject
 7
 8@ArtifactProviderFor(GriffonController)
 9class SampleController {
10    SampleModel model                                                      (1)
11
12    @Inject
13    private SampleService sampleService                                    (2)
14
15    void sayHello() {                                                      (3)
16        String result = sampleService.sayHello(model.input)
17        runInsideUIAsync {                                                 (4)
18            model.output = result
19        }
20    }
21}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

Opposed to its Swing counterpart here we have to add an explicit threading block when updating model properties. This is because the bind node for JavaFX components is not aware of the same rules than the bind node for Swing components. Nevertheless the code remains short and to the point.

1.2.3. Service

Given that the service operates with raw data and has no ties to the toolkit in use we’d expect no changes from the Swing example.

sample-javafx-java/griffon-app/services/sample/javafx/java/SampleService.java
 1package sample.javafx.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonService;
 5import griffon.core.i18n.MessageSource;
 6import griffon.metadata.ArtifactProviderFor;
 7import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService;
 8
 9import javax.annotation.Nonnull;
10import javax.inject.Inject;
11
12import static griffon.util.GriffonNameUtils.isBlank;
13import static java.util.Arrays.asList;
14
15@ArtifactProviderFor(GriffonService.class)
16public class SampleService extends AbstractGriffonService {
17    @Inject
18    public SampleService(@Nonnull GriffonApplication application) {
19        super(application);
20    }
21
22    public String sayHello(String input) {
23        MessageSource messageSource = getApplication().getMessageSource();
24        if (isBlank(input)) {
25            return messageSource.getMessage("greeting.default");
26        } else {
27            return messageSource.getMessage("greeting.parameterized", asList(input));
28        }
29    }
30}
sample-javafx-groovy/griffon-app/services/sample/javafx/groovy/SampleService.groovy
 1package sample.javafx.groovy
 2
 3import griffon.core.artifact.GriffonService
 4import griffon.core.i18n.MessageSource
 5import griffon.metadata.ArtifactProviderFor
 6
 7import static griffon.util.GriffonNameUtils.isBlank
 8
 9@ArtifactProviderFor(GriffonService)
10class SampleService {
11    String sayHello(String input) {
12        MessageSource ms = application.messageSource
13        isBlank(input) ? ms.getMessage('greeting.default') : ms.getMessage('greeting.parameterized', [input])
14    }
15}

1.2.4. View

Views are the artifacts that are most impacted by the choice of UI toolkit. You may remember we mentioned FXML as one of the strong features delivered by JavaFX and so we chose to implement the Java based View by reading an fxml file by convention.

sample-javafx-java/griffon-app/views/sample/javafx/java/SampleView.java
 1package sample.javafx.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonView;
 5import griffon.metadata.ArtifactProviderFor;
 6import javafx.fxml.FXML;
 7import javafx.scene.Group;
 8import javafx.scene.Node;
 9import javafx.scene.Scene;
10import javafx.scene.control.Label;
11import javafx.scene.control.TextField;
12import javafx.scene.paint.Color;
13import javafx.stage.Stage;
14import org.codehaus.griffon.runtime.javafx.artifact.AbstractJavaFXGriffonView;
15
16import javax.annotation.Nonnull;
17import javax.inject.Inject;
18import java.util.Collections;
19
20@ArtifactProviderFor(GriffonView.class)
21public class SampleView extends AbstractJavaFXGriffonView {
22    private SampleController controller;                                  (1)
23    private SampleModel model;                                            (1)
24
25    @FXML
26    private TextField input;                                              (2)
27    @FXML
28    private Label output;                                                 (2)
29
30    @Inject
31    public SampleView(@Nonnull GriffonApplication application) {
32        super(application);
33    }
34
35    public void setController(SampleController controller) {
36        this.controller = controller;
37    }
38
39    public void setModel(SampleModel model) {
40        this.model = model;
41    }
42
43    @Override
44    public void initUI() {
45        Stage stage = (Stage) getApplication()
46            .createApplicationContainer(Collections.<String,Object>emptyMap());
47        stage.setTitle(getApplication().getConfiguration().getAsString("application.title"));
48        stage.setWidth(400);
49        stage.setHeight(120);
50        stage.setScene(init());
51        getApplication().getWindowManager().attach("mainWindow", stage);  (3)
52    }
53
54    // build the UI
55    private Scene init() {
56        Scene scene = new Scene(new Group());
57        scene.setFill(Color.WHITE);
58
59        Node node = loadFromFXML();
60        model.inputProperty().bindBidirectional(input.textProperty());
61        model.outputProperty().bindBidirectional(output.textProperty());
62        ((Group) scene.getRoot()).getChildren().addAll(node);
63        connectActions(node, controller);                                 (4)
64
65        return scene;
66    }
67}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Injected by FXMLLoader
4 Hook actions by convention

FXMLLoader can inject components to an instance as long as that instance exposes fields annotated with @FXML; fields names must match component ids 2 as defined in the fxml file, which is shown next:

sample-javafx-java/griffon-app/resources/sample/javafx/java/sample.fxml
 1<?xml version="1.0" encoding="UTF-8"?>
 2
 3<?import javafx.scene.control.Button?>
 4<?import javafx.scene.control.Label?>
 5<?import javafx.scene.control.TextField?>
 6<?import javafx.scene.layout.AnchorPane?>
 7<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity"
 8            minHeight="-Infinity" minWidth="-Infinity"
 9            prefHeight="80.0" prefWidth="384.0"
10            xmlns:fx="http://javafx.com/fxml"
11            fx:controller="sample.javafx.java.SampleController">
12    <children>
13        <Label layoutX="14.0" layoutY="14.0" text="Please enter your name:"/>
14        <TextField fx:id="input" layoutX="172.0" layoutY="11.0"
15                   prefWidth="200.0"/>
16        <Button layoutX="172.0" layoutY="45.0"
17                mnemonicParsing="false"
18                prefWidth="200.0"
19                text="Say hello!"
20                fx:id="sayHelloActionTarget" />                         (1)
21        <Label layoutX="14.0" layoutY="80.0" prefWidth="360.0" fx:id="output"/>
22    </children>
23</AnchorPane>
1 Naming convention for automatic action binding

Please pay special attention to the fx:id given to the button. Griffon applies a naming convention to match controller actions to JavaFX components that can handle said actions. Let’s review what we have here:

  • SampleController exposes an action named sayHello

  • the button has an fx:id value of sayHelloActionTarget

Given this we infer that the fx:id value must be of the form <actionName>ActionTarget. The naming convention is one of two steps, you must also connect the controller using a helper method 4 as shown in the View.

sample-javafx-groovy/griffon-app/views/sample/javafx/groovy/SampleView.groovy
 1package sample.javafx.groovy
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class SampleView {
 8    FactoryBuilderSupport builder                                                              (1)
 9    SampleModel model                                                                          (1)
10
11    void initUI() {
12        builder.application(title: application.configuration['application.title'],
13            sizeToScene: true, centerOnScreen: true) {                                         (2)
14            scene(fill: WHITE, width: 400, height: 120) {
15                anchorPane {
16                    label(leftAnchor: 14, topAnchor: 11,
17                          text: application.messageSource.getMessage('name.label'))
18                    textField(leftAnchor: 172, topAnchor: 11, prefWidth: 200,
19                              text: bind(model.inputProperty()))                               (3)
20                    button(leftAnchor: 172, topAnchor: 45, prefWidth: 200,
21                           sayHelloAction)                                                     (4)
22                    label(leftAnchor: 14, topAnchor: 80, prefWidth: 200,
23                        text: bind(model.outputProperty()))                                    (3)
24                }
25            }
26        }
27    }
28}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Apply component-to-model binding
4 Hook actions by convention

The Groovy version of the View uses the GroovyFX DSL instead of FXML. You’ll find that this DSL is very similar to SwingBuilder.

1.2.5. Resources

Finally the resources for this application are identical to the Swing version.

sample-javafx-java/griffon-app/i18n/messages.properties
1name.label = Please enter your name
2greeting.default = Howdy stranger!
3greeting.parameterized = Hello {0}

1.3. Lanterna

Lanterna is a Java library allowing you to write easy semi-graphical user interfaces in a text-only environment, very similar to the C library curses but with more functionality. Lanterna supports xterm compatible terminals and terminal emulators such as konsole, gnome-terminal, putty, xterm and many more. One of the main benefits of lanterna is that it’s not dependent on any native library but runs 100% in pure Java.

1.3.1. Model

Even though Lanterna UI components do not expose observable properties in any way it’s a good thing to use observable properties in the Model, and so the following Model is identical to the Swing version.

sample-lanterna-java/griffon-app/models/sample/lanterna/java/SampleModel.java
 1package sample.lanterna.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonModel;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonModel.class)
12public class SampleModel extends AbstractGriffonModel {
13    private String input;                                                (1)
14    private String output;                                               (1)
15
16    @Inject
17    public SampleModel(@Nonnull GriffonApplication application) {
18        super(application);
19    }
20
21    public String getInput() {                                           (2)
22        return input;
23    }
24
25    public void setInput(String input) {
26        firePropertyChange("input", this.input, this.input = input);     (3)
27    }
28
29    public String getOutput() {                                          (2)
30        return output;
31    }
32
33    public void setOutput(String output) {
34        firePropertyChange("output", this.output, this.output = output); (3)
35    }
36}
1 Define a private field for the property
2 Property accessor
3 Property mutator must fire a PropertyChangeEvent

For reasons we’ll see in the Groovy View and Controller we decided to skip a Model for the Groovy version. This also demonstrates that even though and MVC group is the smallest building block you can still configure how it’s assembled. Have a look at the application’s configuration to find out how

sample-lanterna-groovy/griffon-app/conf/sample/lanterna/groovy/Config.groovy
 1package sample.lanterna.groovy
 2
 3application {
 4    title = 'Lanterna + Groovy'
 5    startupGroups = ['sample']
 6    autoShutdown = true
 7}
 8mvcGroups {
 9    // MVC Group for "sample"
10    'sample' {
11        view       = 'sample.lanterna.groovy.SampleView'
12        controller = 'sample.lanterna.groovy.SampleController'
13    }
14}

1.3.2. Controller

We find that for the Java version the Controler it’s identical to the Swing and JavaFX versions. For the Groovy one we notice that both input and output view components are accessed directly. We know we’ve said in the past that a Controller should never do this but because Lanterna exposes no bind mechanism the Groovy binding implementation would look as verbose as the Java version; we decided to take a shortcut for demonstrations purposes.

sample-lanterna-java/griffon-app/controllers/sample/lanterna/java/SampleController.java
 1package sample.lanterna.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonController;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonController.class)
12public class SampleController extends AbstractGriffonController {
13    private SampleModel model;                                             (1)
14
15    @Inject
16    private SampleService sampleService;                                   (2)
17
18    @Inject
19    public SampleController(@Nonnull GriffonApplication application) {
20        super(application);
21    }
22
23    public void setModel(SampleModel model) {
24        this.model = model;
25    }
26
27    public void sayHello() {                                               (3)
28        final String result = sampleService.sayHello(model.getInput());
29        runInsideUIAsync(new Runnable() {                                  (4)
30            @Override
31            public void run() {
32                model.setOutput(result);
33            }
34        });
35    }
36}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread
sample-lanterna-groovy/griffon-app/controllers/sample/lanterna/groovy/SampleController.groovy
 1package sample.lanterna.groovy
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.metadata.ArtifactProviderFor
 5
 6import javax.inject.Inject
 7
 8@ArtifactProviderFor(GriffonController)
 9class SampleController {
10    FactoryBuilderSupport builder                                          (1)
11
12    @Inject
13    private SampleService sampleService                                    (2)
14
15    void sayHello() {                                                      (3)
16        String result = sampleService.sayHello(builder.input.text)
17        runInsideUIAsync {                                                 (4)
18            builder.output.text = result
19        }
20    }
21}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

1.3.3. Service

Again, services should not be affected by the choice of UI tookit so the following Service definitions are identical to the previous ones be saw earlier.

sample-lanterna-java/griffon-app/services/sample/lanterna/java/SampleService.java
 1package sample.lanterna.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonService;
 5import griffon.core.i18n.MessageSource;
 6import griffon.metadata.ArtifactProviderFor;
 7import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService;
 8
 9import javax.annotation.Nonnull;
10import javax.inject.Inject;
11
12import static griffon.util.GriffonNameUtils.isBlank;
13import static java.util.Arrays.asList;
14
15@ArtifactProviderFor(GriffonService.class)
16public class SampleService extends AbstractGriffonService {
17    @Inject
18    public SampleService(@Nonnull GriffonApplication application) {
19        super(application);
20    }
21
22    public String sayHello(String input) {
23        MessageSource messageSource = getApplication().getMessageSource();
24        if (isBlank(input)) {
25            return messageSource.getMessage("greeting.default");
26        } else {
27            return messageSource.getMessage("greeting.parameterized", asList(input));
28        }
29    }
30}
sample-lanterna-groovy/griffon-app/services/sample/lanterna/groovy/SampleService.groovy
 1package sample.lanterna.groovy
 2
 3import griffon.core.artifact.GriffonService
 4import griffon.core.i18n.MessageSource
 5import griffon.metadata.ArtifactProviderFor
 6
 7import static griffon.util.GriffonNameUtils.isBlank
 8
 9@ArtifactProviderFor(GriffonService)
10class SampleService {
11    String sayHello(String input) {
12        MessageSource ms = application.messageSource
13        isBlank(input) ? ms.getMessage('greeting.default') : ms.getMessage('greeting.parameterized', [input])
14    }
15}

1.3.4. View

We’d expect the View to follow the same pattern we’ve seen in the previous examples, that is, create two components that will be bound to model properties and a button that’s connected to a Controller action. Here we see that the Java version is rather verbose due to the fact that Lanterna has no observable UI components. This is the reason for which we must explicitly mode the value of the input component into the input Model property as soon as the button is clicked.

sample-lanterna-java/griffon-app/views/sample/lanterna/java/SampleView.java
 1package sample.lanterna.java;
 2
 3import com.googlecode.lanterna.gui.Window;
 4import com.googlecode.lanterna.gui.component.Label;
 5import com.googlecode.lanterna.gui.component.Panel;
 6import com.googlecode.lanterna.gui.component.TextBox;
 7import griffon.core.GriffonApplication;
 8import griffon.core.artifact.GriffonView;
 9import griffon.lanterna.support.LanternaAction;
10import griffon.lanterna.widgets.MutableButton;
11import griffon.metadata.ArtifactProviderFor;
12import org.codehaus.griffon.runtime.lanterna.artifact.AbstractLanternaGriffonView;
13
14import javax.annotation.Nonnull;
15import javax.inject.Inject;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.Collections;
19
20@ArtifactProviderFor(GriffonView.class)
21public class SampleView extends AbstractLanternaGriffonView {
22    private SampleController controller;                                         (1)
23    private SampleModel model;                                                   (1)
24
25    @Inject
26    public SampleView(@Nonnull GriffonApplication application) {
27        super(application);
28    }
29
30    public void setController(SampleController controller) {
31        this.controller = controller;
32    }
33
34    public void setModel(SampleModel model) {
35        this.model = model;
36    }
37
38    @Override
39    public void initUI() {
40        Window window = (Window) getApplication()
41            .createApplicationContainer(Collections.<String, Object>emptyMap());
42        getApplication().getWindowManager().attach("mainWindow", window);        (2)
43        Panel panel = new Panel(Panel.Orientation.VERTICAL);
44
45        panel.addComponent(new Label(getApplication().getMessageSource().getMessage("name.label")));
46
47        final TextBox input = new TextBox();
48        panel.addComponent(input);
49
50        LanternaAction sayHelloAction = toolkitActionFor(controller, "sayHello");
51        final Runnable runnable = sayHelloAction.getRunnable();
52        sayHelloAction.setRunnable(new Runnable() {                              (3)
53            @Override
54            public void run() {
55                model.setInput(input.getText());
56                runnable.run();
57            }
58        });
59        panel.addComponent(new MutableButton(sayHelloAction));                   (4)
60
61        final Label output = new Label();
62        panel.addComponent(output);
63        model.addPropertyChangeListener("output", new PropertyChangeListener() { (3)
64            @Override
65            public void propertyChange(PropertyChangeEvent evt) {
66                output.setText(String.valueOf(evt.getNewValue()));
67            }
68        });
69
70        window.addComponent(panel);
71    }
72}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Apply component-to-model binding
4 Hook actions by convention

Fortunately we can rely on the observable output Model property to write back the value to the output component as soon as said property gets updated.

sample-lanterna-groovy/griffon-app/views/sample/lanterna/groovy/SampleView.groovy
 1package sample.lanterna.groovy
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class SampleView {
 8    FactoryBuilderSupport builder                                                              (1)
 9
10    void initUI() {
11        builder.with {
12            application(id: 'mainWindow') {                                                    (2)
13                verticalLayout()
14                label(application.messageSource.getMessage('name.label'))
15                textBox(id: 'input')
16                button(sayHelloAction)                                                         (3)
17                label(id: 'output')
18            }
19        }
20    }
21}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Hook actions by convention

Here we can certify there’s no binding code in the View, hence why the Controller has to access UI components directly.

1.3.5. Resources

The resources file is identical to the ones found in the other applications. There’s in fact no changes brought by the choice of UI toolkit here.

sample-lanterna-java/griffon-app/i18n/messages.properties
1name.label = Please enter your name
2greeting.default = Howdy stranger!
3greeting.parameterized = Hello {0}

1.4. Pivot

Apache Pivot is an open-source platform for building installable Internet applications (IIAs). It combines the enhanced productivity and usability features of a modern user interface toolkit with the robustness of the Java platform.

We decided to implement this application in the same fashion as the Lanterna application because of the same reason: Pivot UI components are not observable. You’ll notice that the Java Model is identical and there’s no Groovy Model

1.4.1. Model

sample-pivot-java/griffon-app/models/sample/pivot/java/SampleModel.java
 1package sample.pivot.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonModel;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonModel.class)
12public class SampleModel extends AbstractGriffonModel {
13    private String input;                                                (1)
14    private String output;                                               (1)
15
16    @Inject
17    public SampleModel(@Nonnull GriffonApplication application) {
18        super(application);
19    }
20
21    public String getInput() {                                           (2)
22        return input;
23    }
24
25    public void setInput(String input) {
26        firePropertyChange("input", this.input, this.input = input);     (3)
27    }
28
29    public String getOutput() {                                          (2)
30        return output;
31    }
32
33    public void setOutput(String output) {
34        firePropertyChange("output", this.output, this.output = output); (3)
35    }
36}
1 Define a private field for the property
2 Property accessor
3 Property mutator must fire a PropertyChangeEvent

1.4.2. Controller

The Pivot Controllers follow the same rules than the Lanterna ones, and as such you’ll see there are no differences between one another.

sample-pivot-java/griffon-app/controllers/sample/pivot/java/SampleController.java
 1package sample.pivot.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonController;
 5import griffon.metadata.ArtifactProviderFor;
 6import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController;
 7
 8import javax.annotation.Nonnull;
 9import javax.inject.Inject;
10
11@ArtifactProviderFor(GriffonController.class)
12public class SampleController extends AbstractGriffonController {
13    private SampleModel model;                                             (1)
14
15    @Inject
16    private SampleService sampleService;                                   (2)
17
18    @Inject
19    public SampleController(@Nonnull GriffonApplication application) {
20        super(application);
21    }
22
23    public void setModel(SampleModel model) {
24        this.model = model;
25    }
26
27    public void sayHello() {                                               (3)
28        final String result = sampleService.sayHello(model.getInput());
29        runInsideUIAsync(new Runnable() {                                  (4)
30            @Override
31            public void run() {
32                model.setOutput(result);
33            }
34        });
35    }
36}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread
sample-pivot-groovy/griffon-app/controllers/sample/pivot/groovy/SampleController.groovy
 1package sample.pivot.groovy
 2
 3import griffon.core.artifact.GriffonController
 4import griffon.metadata.ArtifactProviderFor
 5
 6import javax.inject.Inject
 7
 8@ArtifactProviderFor(GriffonController)
 9class SampleController {
10    FactoryBuilderSupport builder                                          (1)
11
12    @Inject
13    private SampleService sampleService                                    (2)
14
15    void sayHello() {                                                      (3)
16        String result = sampleService.sayHello(builder.input.text)
17        runInsideUIAsync {                                                 (4)
18            builder.output.text = result
19        }
20    }
21}
1 MVC member injected by MVCGroupManager
2 Injected by JSR 330
3 Automatically run off the UI thread
4 Get back inside the UI thread

1.4.3. Service

Services remain constant again, what a surprise, right?

sample-pivot-java/griffon-app/services/sample/pivot/java/SampleService.java
 1package sample.pivot.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonService;
 5import griffon.core.i18n.MessageSource;
 6import griffon.metadata.ArtifactProviderFor;
 7import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService;
 8
 9import javax.annotation.Nonnull;
10import javax.inject.Inject;
11
12import static griffon.util.GriffonNameUtils.isBlank;
13import static java.util.Arrays.asList;
14
15@ArtifactProviderFor(GriffonService.class)
16public class SampleService extends AbstractGriffonService {
17    @Inject
18    public SampleService(@Nonnull GriffonApplication application) {
19        super(application);
20    }
21
22    public String sayHello(String input) {
23        MessageSource messageSource = getApplication().getMessageSource();
24        if (isBlank(input)) {
25            return messageSource.getMessage("greeting.default");
26        } else {
27            return messageSource.getMessage("greeting.parameterized", asList(input));
28        }
29    }
30}
sample-pivot-groovy/griffon-app/services/sample/pivot/groovy/SampleService.groovy
 1package sample.pivot.groovy
 2
 3import griffon.core.artifact.GriffonService
 4import griffon.core.i18n.MessageSource
 5import griffon.metadata.ArtifactProviderFor
 6
 7import static griffon.util.GriffonNameUtils.isBlank
 8
 9@ArtifactProviderFor(GriffonService)
10class SampleService {
11    String sayHello(String input) {
12        MessageSource ms = application.messageSource
13        isBlank(input) ? ms.getMessage('greeting.default') : ms.getMessage('greeting.parameterized', [input])
14    }
15}

1.4.4. View

If you squint your eyes you’ll see that the View is almost identical to the Swing and Lanterna Views. Besides using toolkit specific components we notice that both input and output Model properties have to be explicitly bound using the native support exposed by the toolkit.

sample-pivot-java/griffon-app/views/sample/pivot/java/SampleView.java
 1package sample.pivot.java;
 2
 3import griffon.core.GriffonApplication;
 4import griffon.core.artifact.GriffonView;
 5import griffon.metadata.ArtifactProviderFor;
 6import griffon.pivot.support.PivotAction;
 7import griffon.pivot.support.adapters.TextInputContentAdapter;
 8import org.apache.pivot.serialization.SerializationException;
 9import org.apache.pivot.wtk.*;
10import org.codehaus.griffon.runtime.pivot.artifact.AbstractPivotGriffonView;
11
12import javax.annotation.Nonnull;
13import javax.inject.Inject;
14import java.beans.PropertyChangeEvent;
15import java.beans.PropertyChangeListener;
16import java.util.Collections;
17
18@ArtifactProviderFor(GriffonView.class)
19public class SampleView extends AbstractPivotGriffonView {
20    private SampleController controller;                                         (1)
21    private SampleModel model;                                                   (1)
22
23    @Inject
24    public SampleView(@Nonnull GriffonApplication application) {
25        super(application);
26    }
27
28    public void setController(SampleController controller) {
29        this.controller = controller;
30    }
31
32    public void setModel(SampleModel model) {
33        this.model = model;
34    }
35
36    @Override
37    public void initUI() {
38        Window window = (Window) getApplication()
39            .createApplicationContainer(Collections.<String, Object>emptyMap());
40        window.setTitle(getApplication().getConfiguration().getAsString("application.title"));
41        window.setMaximized(true);
42        getApplication().getWindowManager().attach("mainWindow", window);        (2)
43
44        BoxPane vbox = new BoxPane(Orientation.VERTICAL);
45        try {
46            vbox.setStyles("{horizontalAlignment:'center', verticalAlignment:'center'}");
47        } catch (SerializationException e) {
48            // ignore
49        }
50
51        vbox.add(new Label(getApplication().getMessageSource().getMessage("name.label")));
52
53        TextInput input = new TextInput();
54        input.setName("inputField");
55        input.getTextInputContentListeners().add(new TextInputContentAdapter() {  (3)
56            @Override
57            public void textChanged(TextInput arg0) {
58                model.setInput(arg0.getText());
59            }
60        });
61        vbox.add(input);
62
63        PivotAction sayHelloAction = toolkitActionFor(controller, "sayHello");
64        final Button button = new PushButton(sayHelloAction.getName());
65        button.setName("sayHelloButton");
66        button.setAction(sayHelloAction);                                        (4)
67        vbox.add(button);
68
69        final TextInput output = new TextInput();
70        output.setName("outputField");
71        output.setEditable(false);
72        model.addPropertyChangeListener("output", new PropertyChangeListener() { (3)
73            @Override
74            public void propertyChange(PropertyChangeEvent evt) {
75                output.setText(String.valueOf(evt.getNewValue()));
76            }
77        });
78        vbox.add(output);
79
80        window.setContent(vbox);
81    }
82}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Apply component-to-model binding
4 Hook actions by convention
sample-pivot-groovy/griffon-app/views/sample/pivot/groovy/SampleView.groovy
 1package sample.pivot.groovy
 2
 3import griffon.core.artifact.GriffonView
 4import griffon.metadata.ArtifactProviderFor
 5
 6@ArtifactProviderFor(GriffonView)
 7class SampleView {
 8    FactoryBuilderSupport builder                                                              (1)
 9
10    void initUI() {
11        builder.with {
12            application(title: application.configuration['application.title'],
13                        id: 'mainWindow', maximized: true) {                                   (2)
14                vbox(styles: "{horizontalAlignment:'center', verticalAlignment:'center'}") {
15                    label(application.messageSource.getMessage('name.label'))
16                    textInput(id: 'input')
17                    button(id: 'sayHelloButton', sayHelloAction)                               (3)
18                    textInput(id: 'output', editable: false)
19                }
20            }
21        }
22    }
23}
1 MVC member injected by MVCGroupManager
2 Create window and attach it to WindowManager
3 Hook actions by convention

1.4.5. Resources

The resource file, left untouched once more.

sample-pivot-java/griffon-app/i18n/messages.properties
1name.label = Please enter your name
2greeting.default = Howdy stranger!
3greeting.parameterized = Hello {0}

Appendix E: Builder Nodes

The following tables summarizes all builder nodes supplied by the default UI Toolkit dependencies

1.1. Swing

1.2. JavaFX

Table 17. griffon/builder/javafx/JavaFXBuilderCustomizer

 Node

Type

accordion

javafx.scene.control.Accordion

action

griffon.javafx.support.JavaFXAction

affine

anchorPane

javafx.scene.layout.AnchorPane

application

javafx.scene.Stage

arc

javafx.scene.shape.Arc

arcTo

javafx.scene.shape.ArcTo

areaChart

javafx.builders.AreaChartBuilder

barChart

javafx.builders.BarChartBuilder

blend

javafx.scene.effect.Blend

bloom

javafx.scene.effect.Bloom

borderPane

javafx.scene.layout.BorderPane

bottom

groovyx.javafx.factory.BorderPanePosition

bottomInput

boxBlur

javafx.scene.effect.BoxBlur

bubbleChart

javafx.builders.BubbleChartBuilder

bumpInput

button

javafx.scene.control.Button

categoryAxis

javafx.scene.chart.CategoryAxis

center

groovyx.javafx.factory.BorderPanePosition

checkBox

javafx.scene.control.CheckBox

checkMenuItem

javafx.scene.control.MenuBar

choiceBox

javafx.scene.control.ChoiceBox

circle

javafx.scene.shape.Circle

clip

closePath

javafx.scene.shape.ClosePath

colorAdjust

javafx.scene.effect.ColorAdjust

colorInput

javafx.scene.effect.ColorInput

column

groovyx.javafx.factory.GridRowColumn

constraint

groovyx.javafx.factory.GridConstraint

container

javafx.scene.Parent

content

groovyx.javafx.factory.Titled

contentInput

contextMenu

javafx.scene.control.ContextMenu

cubicCurve

javafx.scene.shape.CubicCurve

cubicCurveTo

javafx.scene.shape.CubicCurveTo

customMenuItem

javafx.scene.control.MenuBar

displacementMap

javafx.scene.effect.DisplacementMap

distant

javafx.scene.effect.Light.Distant

dividerPosition

javafx.scene.control.DividerPosition

dropShadow

javafx.scene.effect.DropShadow

effect

javafx.scene.effect.Effect

ellipse

javafx.scene.shape.Ellipse

fadeTransition

javafx.animation.FadeTransition

fileChooser

javafx.stage.FileChooser

fill

javafx.scene.paint.Paint

fillTransition

javafx.animation.FadeTransition

filter

javafx.stage.FilterChooser.ExtensionFilter

flowPane

javafx.scene.layout.FlowPane

fxml

javafx.scene.Node

gaussianBlur

javafx.scene.effect.GaussianBlur

glow

javafx.scene.effect.Glow

graphic

groovyx.javafx.factory.Graphic

gridPane

javafx.scene.layout.GridPane

group

javafx.scene.Group

hLineTo

javafx.scene.shape.HLineTo

hbox

javafx.scene.layout.HBox

htmlEditor

javafx.scene.web.HTMLEditor

hyperlink

javafx.scene.control.Hyperlink

image

javafx.scene.image.Image

imageInput

javafx.scene.effect.ImageInput

imageView

javafx.scene.image.ImageView

innerShadow

javafx.scene.effect.InnerShadow

label

javafx.scene.control.Label

left

groovyx.javafx.factory.BorderPanePosition

lighting

javafx.scene.effect.Lighting

line

javafx.scene.shape.Line

lineChart

javafx.builders.LineChartBuilder

lineTo

javafx.scene.shape.LineTo

linearGradient

javafx.builders.LinearGradientBuilder

listView

javafx.scene.control.ListView

mediaPlayer

javafx.scene.media.MediaPlayer

mediaView

javafx.scene.media.MediaView

menu

javafx.scene.control.MenuBar

menuBar

javafx.scene.control.MenuBar

menuButton

javafx.scene.control.MenuBar

menuItem

javafx.scene.control.MenuBar

motionBlur

javafx.scene.effect.MotionBlur

moveTo

javafx.scene.shape.MoveTo

node

javafx.scene.Node

nodes

java.util.List

numberAxis

javafx.scene.chart.NumberAxis

onAction

javafx.event.EventHandler

onBranchCollapse

groovyx.javafx.ClosureEventHandler

onBranchExpand

groovyx.javafx.ClosureEventHandler

onChildrenModification

groovyx.javafx.ClosureEventHandler

onDragDetected

javafx.event.EventHandler

onDragDone

javafx.event.EventHandler

onDragDropped

javafx.event.EventHandler

onDragEntered

javafx.event.EventHandler

onDragExited

javafx.event.EventHandler

onDragOver

javafx.event.EventHandler

onEditCancel

groovyx.javafx.ClosureEventHandler

onEditCommit

groovyx.javafx.ClosureEventHandler

onEditStart

groovyx.javafx.ClosureEventHandler

onGraphicChanged

groovyx.javafx.ClosureEventHandler

onMouseClicked

javafx.event.EventHandler

onMouseDragged

javafx.event.EventHandler

onMouseEntered

javafx.event.EventHandler

onMouseExited

javafx.event.EventHandler

onMousePressed

javafx.event.EventHandler

onMouseReleased

javafx.event.EventHandler

onMouseWheelMoved

javafx.event.EventHandler

onTreeItemCountChange

groovyx.javafx.ClosureEventHandler

onTreeNotification

groovyx.javafx.ClosureEventHandler

onValueChanged

groovyx.javafx.ClosureEventHandler

pane

javafx.scene.layout.Pane

parallelTransition

javafx.animation.ParallelTransition

path

javafx.scene.shape.Path

pathTransition

javafx.animation.PathTransition

pauseTransition

javafx.animation.PauseTransition

perspectiveTransform

javafx.scene.effect.PerspectiveTransform

pieChart

javafx.scene.chart.PieChart

point

javafx.scene.effect.Light.Point

polygon

javafx.scene.shape.Polygon

polyline

javafx.scene.shape.Polyline

popup

javafx.stage.Popup

progressBar

javafx.scene.control.ProgressBar

progressIndicator

javafx.scene.control.ProgressIndicator

quadCurve

javafx.scene.shape.QuadCurve

quadCurveTo

javafx.scene.shape.QuadCurveTo

radialGradient

javafx.builders.RadialGradientBuilder

radioButton

javafx.scene.control.RadioButton

radioMenuItem

javafx.scene.control.MenuBar

rectangle

javafx.scene.shape.Rectangle

reflection

javafx.scene.effect.Reflection

right

groovyx.javafx.factory.BorderPanePosition

rotate

rotateTransition

javafx.animation.RotateTransition

row

groovyx.javafx.factory.GridRowColumn

scale

scaleTransition

javafx.animation.ScaleTransition

scatterChart

javafx.builders.ScatterChartBuilder

scene

javafx.scene.Scene

scrollBar

javafx.scene.control.ScrollBar

scrollPane

javafx.scene.control.ScrollPane

separator

javafx.scene.control.Separator

separatorMenuItem

javafx.scene.control.MenuBar

sepiaTone

javafx.scene.effect.SepiaTone

sequentialTransition

javafx.animation.SequentialTransition

series

javafx.scene.chart.XYChart.Series

shadow

javafx.scene.effect.Shadow

shear

slider

javafx.scene.control.Slider

splitMenuButton

javafx.scene.control.MenuBar

splitPane

javafx.scene.control.SplitPane

spot

javafx.scene.effect.Light.Spot

stackPane

javafx.scene.layout.StackPane

stage

javafx.scene.Stage

stop

javafx.scene.paint.Stop

stroke

javafx.scene.paint.Paint

strokeTransition

javafx.animation.StrokeTransition

stylesheets

java.util.List

svgPath

javafx.scene.shape.SVGPath

tab

javafx.scene.control.Tab

tabPane

javafx.scene.control.TabPane

tableColumn

javafx.scene.control.TableColumn

tableRow

javafx.scene.control.TableRow

tableView

javafx.scene.control.TableView

text

javafx.scene.text.Text

textArea

javafx.scene.control.TextArea

textField

javafx.scene.control.TextField

tilePane

javafx.scene.layout.TilePane

title

groovyx.javafx.factory.Titled

titledPane

javafx.scene.control.TitledPane

toggleButton

javafx.scene.control.ToggleButton

toolBar

javafx.scene.control.ToolBar

tooltip

javafx.scene.control.Tooltip

top

groovyx.javafx.factory.BorderPanePosition

topInput

transition

javafx.animation.Transition

translate

translateTransition

javafx.animation.TranslateTransition

treeItem

javafx.scene.control.TreeItem

treeView

javafx.scene.control.TreeView

vLineTo

javafx.scene.shape.VLineTo

vbox

javafx.scene.layout.VBox

webEngine

javafx.scene.web.WebEngine

webView

javafx.scene.web.WebView

1.3. Lanterna

Table 18. griffon/builder/lanterna/LanternaBuilderCustomizer
Node Type

action

griffon.lanterna.support.LanternaAction

actionListBox

com.googlecode.lanterna.gui.component.ActionListBox

actions

java.util.ArrayList

application

com.googlecode.lanterna.gui.Window

bean

java.lang.Object

borderLayout

com.googlecode.lanterna.gui.layout.BorderLayout

button

griffon.lanterna.widgets.MutableButton

checkBox

com.googlecode.lanterna.gui.component.CheckBox

container

com.googlecode.lanterna.gui.Component

emptySpace

com.googlecode.lanterna.gui.component.EmptySpace

hbox

com.googlecode.lanterna.gui.component.Panel

horisontalLayout

com.googlecode.lanterna.gui.layout.HorisontalLayout

horizontalLayout

com.googlecode.lanterna.gui.layout.HorisontalLayout

label

com.googlecode.lanterna.gui.component.Label

list

java.util.ArrayList

panel

com.googlecode.lanterna.gui.component.Panel

passwordBox

com.googlecode.lanterna.gui.component.PasswordBox

progressBar

com.googlecode.lanterna.gui.component.ProgressBar

table

com.googlecode.lanterna.gui.component.Table

textArea

com.googlecode.lanterna.gui.component.TextArea

textBox

com.googlecode.lanterna.gui.component.TextBox

vbox

com.googlecode.lanterna.gui.component.Panel

verticalLayout

com.googlecode.lanterna.gui.layout.VerticalLayout

widget

com.googlecode.lanterna.gui.Component

1.4. Pivot

Table 19. griffon/builder/pivot/PivotBuilderCustomizer
Node Type

accordion

org.apache.pivot.wtk.Accordion

action

griffon.pivot.imlp.DefaultAction

actions

java.util.ArrayList

activityIndicator

org.apache.pivot.wtk.ActivityIndicator

application

org.apache.pivot.wtk.Window

baselineDecorator

org.apache.pivot.wtk.effects.BaselineDecorator

bean

java.lang.Object

blurDecorator

org.apache.pivot.wtk.effects.BlurDecorator

border

org.apache.pivot.wtk.Border

bounds

org.apache.pivot.wtk.Bounds

box

org.apache.pivot.wtk.BoxPane

boxPane

org.apache.pivot.wtk.BoxPane

button

org.apache.pivot.wtk.PushButton

buttonData

org.apache.pivot.wtk.content.ButtonData

buttonDataRenderer

org.apache.pivot.wtk.content.ButtonDataRenderer

buttonGroup

org.apache.pivot.wtk.ButtonGroup

bxml

org.apache.pivot.wtk.Component

calendar

org.apache.pivot.wtk.Calendar

calendarButton

org.apache.pivot.wtk.CalendarButton

calendarButtonDataRenderer

org.apache.pivot.wtk.content.CalendarButtonDataRenderer

calendarDateSpinnerData

org.apache.pivot.wtk.content.CalendarDateSpinnerData

cardPane

org.apache.pivot.wtk.CardPane

checkbox

org.apache.pivot.wtk.Checkbox

clipDecorator

org.apache.pivot.wtk.effects.ClipDecorator

colorChooser

org.apache.pivot.wtk.ColorChooser

colorChooserButton

org.apache.pivot.wtk.ColorChooserButton

container

org.apache.pivot.wtk.Container

dialog

org.apache.pivot.wtk.Dialog

dimensions

org.apache.pivot.wtk.Dimensions

dropShadowDecorator

org.apache.pivot.wtk.effects.DropShadowDecorator

easingCircular

org.apache.pivot.wtk.effects.Circular

easingCubic

org.apache.pivot.wtk.effects.Cubic

easingExponential

org.apache.pivot.wtk.effects.Exponential

easingLinear

org.apache.pivot.wtk.effects.Linear

easingQuadratic

org.apache.pivot.wtk.effects.Quadratic

easingQuartic

org.apache.pivot.wtk.effects.Quartic

easingQuintic

org.apache.pivot.wtk.effects.Quintic

easingSine

org.apache.pivot.wtk.effects.Sine

expander

org.apache.pivot.wtk.Expander

fadeDecorator

org.apache.pivot.wtk.effects.FadeDecorator

fileBrowser

org.apache.pivot.wtk.FileBrowser

fileBrowserSheet

org.apache.pivot.wtk.FileBrowserSheet

flowPane

org.apache.pivot.wtk.FlowPane

form

org.apache.pivot.wtk.Form

formFlag

org.apache.pivot.wtk.From.Flag

formSection

org.apache.pivot.wtk.Form.Section

frame

org.apache.pivot.wtk.Frame

grayscaleDecorator

org.apache.pivot.wtk.effects.GrayscaleDecorator

gridFiller

org.apache.pivot.wtk.GridPane.Filler

gridPane

org.apache.pivot.wtk.GridPane

gridRow

org.apache.pivot.wtk.GridPane.Row

hbox

org.apache.pivot.wtk.BoxPane

imageView

org.apache.pivot.wtk.ImageView

insets

org.apache.pivot.wtk.Insets

label

org.apache.pivot.wtk.Label

linkButton

org.apache.pivot.wtk.LinkButton

linkButtonDataRenderer

org.apache.pivot.wtk.content.LinkButtonDataRenderer

listButton

org.apache.pivot.wtk.ListButton

listButtonColorItemRenderer

org.apache.pivot.wtk.content.ListButtonColorItemRenderer

listButtonDataRenderer

org.apache.pivot.wtk.content.ListButtonDataRenderer

listView

org.apache.pivot.wtk.ListView

menu

org.apache.pivot.wtk.Menu

menuBar

org.apache.pivot.wtk.MenuBar

menuBarItem

org.apache.pivot.wtk.MenuBar.Item

menuBarItemDataRenderer

org.apache.pivot.wtk.content.MenuBarItemDataRenderer

menuButton

org.apache.pivot.wtk.MenuButton

menuButtonDataRenderer

org.apache.pivot.wtk.content.MenuButtonDataRenderer

menuItem

org.apache.pivot.wtk.Menu.Item

menuItemDataRenderer

org.apache.pivot.wtk.content.MenuItemDataRenderer

menuPopup

org.apache.pivot.wtk.MenuPopup

meter

org.apache.pivot.wtk.Meter

noparent

java.util.ArrayList

numericSpinnerData

org.apache.pivot.wtk.content.NumericSpinnerData

overlayDecorator

org.apache.pivot.wtk.effects.OverlayDecorator

palette

org.apache.pivot.wtk.Palette

panel

org.apache.pivot.wtk.Panel

panorama

org.apache.pivot.wtk.Panorama

picture

org.apache.pivot.wtk.media.Picture

point

org.apache.pivot.wtk.Point

pushButton

org.apache.pivot.wtk.PushButton

radioButton

org.apache.pivot.wtk.RadioButton

reflectionDecorator

org.apache.pivot.wtk.effects.ReflectionDecorator

rollup

org.apache.pivot.wtk.Rollup

rotationDecorator

org.apache.pivot.wtk.effects.RotationDecorator

saturationDecorator

org.apache.pivot.wtk.effects.SaturationDecorator

scaleDecorator

org.apache.pivot.wtk.effects.ScaleDecorator

scrollBar

org.apache.pivot.wtk.ScrollBar

scrollBarScope

org.apache.pivot.wtk.ScrollBar.Scope

scrollPane

org.apache.pivot.wtk.ScrollPane

separator

org.apache.pivot.wtk.Separator

shadeDecorator

org.apache.pivot.wtk.effects.ShadeDecorator

sheet

org.apache.pivot.wtk.Sheet

slider

org.apache.pivot.wtk.Slider

span

org.apache.pivot.wtk.Span

spinner

org.apache.pivot.wtk.Spiner

splitPane

org.apache.pivot.wtk.SplitPane

stackPane

org.apache.pivot.wtk.StackPane

tabPane

org.apache.pivot.wtk.TabPane

tablePane

org.apache.pivot.wtk.TablePane

tablePaneColumn

org.apache.pivot.wtk.TablePane.Column

tablePaneFiller

org.apache.pivot.wtk.TablePane.Filler

tablePaneRow

org.apache.pivot.wtk.TablePane.Row

tagDecorator

org.apache.pivot.wtk.effects.TagDecorator

textArea

org.apache.pivot.wtk.TextArea

textInput

org.apache.pivot.wtk.TextInput

tooltip

org.apache.pivot.wtk.Tooltip

translationDecorator

org.apache.pivot.wtk.effects.TranslationDecorator

vbox

org.apache.pivot.wtk.BoxPane

watermarkDecorator

org.apache.pivot.wtk.effects.WatermarkDecorator

widget

org.apache.pivot.wtk.Component

window

org.apache.pivot.wtk.Window