(Quick Reference)

11 Resource Management - Reference Documentation

Authors: Andres Almiray

Version: 1.2.0

11 Resource Management

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

11.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, both GriffonApplication and GriffonArtifact have a set of methods that simply working with resources. Those methods are provided by ResourceHandler:

  • URL getResourceAsURL(String resourceName)
  • InputStream getResourceAsStream(String resourceName)
  • List<URL> getResources(String resourceName)

Thus, the previous example can be rewritten in this way

scrollPane {
    textArea(columns: 40, rows: 20,
        text: app.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.

11.2 Resolving Configured Resources

Parameterized resources may be resolved by an application by leveraging the behavior exposed by griffon.core.resources.ResourceResolver which every single griffon.core.GriffonApplication implements. 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)

The first set throws NoSuchResourceException if a resource 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, Object defaultValue, Locale locale)
  • Object resolveResource(String key, Object[] args, Object defaultValue)
  • Object resolveResource(String key, Object[] args, Object defaultValue, Locale locale)
  • Object resolveResource(String key, List args, Object defaultValue)
  • Object resolveResource(String key, List args, Object defaultValue, Locale locale)
  • Object resolveResource(String key, Map args, Object defaultValue)
  • Object resolveResource(String key, Map args, Object defaultValue, Locale locale)

The simplest way to resolve a resource thus results like this

app.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

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

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

import static java.util.Arrays.asList;
…
app.resolveResource("hybrid.icon.resource", asList("medium"));

Resource 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

menu.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 largeIcon = app.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

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

Which may be resolved in this manner

Object largeIcon = app.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, generate a resource on the fly or simply define the resource instance in place. In order to accomplish this feat resources must be defined as closures. The following example shows two ways to compute java.awt.Rectangle resources

import java.awt.Rectangle

direct.instance = new Rectangle(10i, 20i, 30i, 40i) computed.instance = { x, y, w, h -> new Rectangle(x, y, w, h) }

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 make use of 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 = app.resolveResource('menu.icon', ['large'])
PropertyEditor propertyEditor = PropertyEditorManager.findEditor(Icon)
propertyEditor.setAsText(String.valueOf(iconValue))
Icon icon = propertyEditor.getValue()

11.3 Configuration

Resources may be configured in either properties files or Groovy scripts. Properties files have precedence over Groovy scripts 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.groovy
  • resources.properties

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 flag in Config.groovy


resources.basenames = ['resources', 'icons']

Both properties files and Groovy scripts are subject to the same locale aware loading mechanism described in Runtime Configuration, that is, the following resources will be searched for and loaded for a Locate set to de_CH_Basel

  • resources.groovy
  • resources.properties
  • resources_de.groovy
  • resources_de.properties
  • resources_de_CH.groovy
  • resources_de_CH.properties
  • resources_de_CH_Basel.groovy
  • resources_de_CH_Basel.properties

Properties files and Groovy scripts used for internationalization purposes are usually placed under griffon-app/i18n as these files are automatically processed with native2ascii when packaging is executed. The default resources.properties file is placed in this directory upon creating an application with create-app.

11.4 Automatically Injected Resources

Resources may be automatically injected to any instance created using the application's facilities (by calling newInstance() on the application instance or any Griffon artifact instance). Injection points must be annotated with @griffon.core.resources.InjectedResource which can only be set on properties (Groovy) or fields (Java and Groovy). @InjectedResource is a perfect companion to models as the following example shows

resources.properties

sample.SampleModel.griffonLogo = /griffon-logo-48x48.png
logo = /griffon-logo-{0}x{0}.png

SampleModel.groovy

package sample

import griffon.core.resources.InjectedResource import javax.swing.Icon

class SampleModel { @InjectedResource Icon griffonLogo @InjectedResource(key='logo', args=['16']) Icon smallGriffonLogo @InjectedResource(key='logo', args=['64']) Icon largeGriffonLogo }

@InjectedResource assumes a naming convention in order to determine the resource key to use. These are the rules applied by the default 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.

11.5 Injecting Resource Resolution Behavior

Any component may gain the ability to resolve messages through the application's ResourceResolver. 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.

11.6 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. Plugins such as swing and javafx may register additional editors.

TypeEditor Class
java.lang.Stringgriffon.core.resources.editors.StringPropertyEditor
java.io.Filegriffon.core.resources.editors.FilePropertyEditor
java.net.URLgriffon.core.resources.editors.URLPropertyEditor
java.net.URIgriffon.core.resources.editors.URIPropertyEditor

The configuration (/META-INF/services/java.beans.PropertyEditor inside griffon-rt-1.2.0.jar) for these editors is thus

java.lang.String = griffon.core.resources.editors.StringPropertyEditor
java.io.File = griffon.core.resources.editors.FilePropertyEditor
java.net.URL = griffon.core.resources.editors.URLPropertyEditor
java.net.URI = griffon.core.resources.editors.URIPropertyEditor