Resources

Modern desktop applications often need to support multiple languages if they serve a multi-lingual user base. Therefore the texts to be displayed in the graphical user interface cannot be hard-coded in the source code, but have to be externalized in some way. But even if an application addresses only users speaking the same language, it is good practice to keep the texts separated from the rest of the program code. This simplifies changes and keeps a possible upgrade path: if the application turns out to be a big success, a multi-lingual version may be required sooner than the developers thought - you never know.

The Java SDK used to provide support for multi-lingual applications from the very beginning. Typically the ResourceBundle class is used for accessing data that may be translated into different languages. The actual texts are frequently stored in resource properties files, and ResourceBundle takes care that the correct texts for the currently active language (represented as a java.util.Locale object) are loaded. Many developers are familiar with this easy yet powerful approach.

The JGUIraffe library also supports resources in various forms. It is especially easy to integrate resource properties files as used by the well-known ResourceBundle class; so developers do not have to change their way of thinking about resources dramatically. However, by providing alternative implementations for some of the fundamental interfaces that deal with access to resources, it is possible to load resources from different sources, e.g. from configuration files or from a database.

In the following sub sections the interfaces and classes for loading and obtaining resource data are explained. Applications that need direct access to their resources can make use of these components. If the builder framework is used, it is often not necessary to deal with these classes directly, as builder scripts are inherently capable of accessing resources and configuring objects (e.g. GUI components or service beans) with them.

Fundamental interfaces

The interfaces and classes related to resources are located in the net.sf.jguiraffe.resources packages. In this section we will introduce the most important interfaces.

  • A group of logically connected resources is represented by the ResourceGroup interface. A ResourceGroup contains texts in a specific language. Single elements of the group can be accessed using the getResource() method passing in an arbitrary object as resource key. The getKeys() method returns a set with all known keys in this ResourceGroup. A ResourceGroup is similar to a resource bundle with a specific base name. An application can create an arbitrary number of resource groups organizing its resources in several logic chunks. For smaller applications it can also make sense to store all resources in a single group.
  • Resource groups are obtained by a ResourceLoader object. A ResourceLoader is rarely accessed by client code directly, it works behind the scenes. However, it plays an important role for the extensibility of the resources framework: By specifying an alternative ResourceLoader implementation a different strategy for locating resources can be activated. The ResourceLoader interface is pretty lean. It defines a single method for obtaining a ResourceGroup for a given name and Locale. An implementation can do whatever is necessary to retrieve the underlying resources and create a ResourceGroup object with this information. For instance, the default ResourceLoader implementation for resource bundles obtains the corresponding java.util.ResourceBundle for the group name and locale and creates a wrapper object around this bundle.
  • Finally, there is the ResourceManager interface, which is probably the most important interface from a client's point of view. ResourceManager provides several methods for accessing whole resource groups or single elements from specific resource groups. With these methods arbitrary resources can be obtained. The JGUIraffe library provides a default implementation of the ResourceManager interface. This implementation is fully functional, and it should hardly be necessary to provide a different implementation as customization can be done by injecting another ResourceLoader (ResourceManager has a setResourceLoader() for switching to a different ResourceLoader implementation).

This completes the introduction of the fundamental interfaces for dealing with resources. The following section is about making use of these interfaces.

Using the ResourceManager interface

The ResourceManager interface provides a low-level API for accessing resources. For making use of this API, a ResourceManager instance has to be obtained first. This can be achieved by calling the getResourceManager() method of the current ApplicationContext. (The ApplicationContext should be available to all interested components in the application. The ResourceManager has been created and initialized from the application's configuration during the startup phase.) Actually, the getResourceManager() method is defined in the TransformerContext interface, which is extended by ApplicationContext. Because a TransformerContext is passed to all validators and transformers these objects have convenient access to resources, too.

For accessing a specific resource element a combined key consisting of two components has to be provided:

  • The group ID specifies the resource group the desired element belongs to.
  • The actual resource key identifies the element in the resource group.
Both components of the resource key are of type java.lang.Object. So there is literally no constraint for resource keys. In practice however, most frequently strings will be used as keys. Of course, in addition to the key of the resource element the desired Locale must be passed in.

ResourceManager defines two methods for obtaining single resource elements:

  • Object getResource(Locale locale, Object group, Object key) returns a resource element as plain object.
  • String getText(Locale locale, Object group, Object key) works like getResource(), but performs a conversion of the resource object to string.
Both methods throw a (unchecked) java.util.MissingResourceException if the resource element cannot be resolved.

If most of the resources are contained in a single resource group, it is cumbersome to always specify the resource group ID when calling the getResource() or getText() methods. ResourceManager provides the setDefaultResourceGroup() method. Here the ID of a resource group can be set. This ID is always used if no group ID is provided when querying a resource element.

With the getResourceGroup() method a whole ResourceGroup can be obtained at once. From the ResourceGroup the single elements it contains can be queried later on. This can make sense if groups have a special meaning for the logic of an application. For instance, resource groups could be used as a kind of enumeration.

The Message class

Applications often have the use case to obtain an element from the resources, insert some parameters, and finally display it to the user. An example can be messages for validation errors: A validator might check whether an input field contains a valid number in a configurable range. If not, an error message like Please enter a number between 0 and 100! should be generated, where the numbers are obtained from the validator's configuration.

For use cases like this the Message class can be used. A Message instance stores the ID of a resource element (consisting of a resource group ID and a resource key) and an arbitrary number of parameters. Creating the instance does not actually involve a resource look-up, rather the resource ID and the parameters are stored. When the final text is needed the resolve() method has to be called. resolve() expects the current ResourceManager and the Locale as parameters. It obtains the resource text from the ResourceManager, processes the parameters, and returns the result.

Internally, Message uses the java.text.MessageFormat class for dealing with parameters. So the typical message format patterns used by this class can be placed in the resources. For instance, the error message of the numeric range validator could be defined in a resource properties file as follows:

ERR_RANGE = Please enter a number between {0} and {1}!
    
When creating the Message object two parameters for the minimum and maximum value must be passed in.
Message msg = new Message("validationerrors", "ERR_RANGE",
    getMinimum(), getMaximum());
    

There are multiple constructors for different use cases. The easiest constructor only expects a resource key and assumes that the default resource group is used. Another constructor takes the ID of the resource group, the resource key and an arbitrary number of parameter objects as a vararg argument. Once created, Message instances are immutable and thus can be shared safely between multiple threads. By the way: the default implementations of the resources interfaces shipped with the library are all thread-safe.

Convenience methods

Because GUI applications typically access resources frequently there are some convenience methods to simplify this task. They are defined by the ApplicationContext interface, which plays an important role for the whole application.

ApplicationContext defines some overloaded getResource() and getResourceText() methods which are thin wrappers around the corresponding methods of ResourceManager. Because the context already knows the current Locale and has a reference to the ResourceManager, all the caller has to specify is the ID of the resource to be obtained. This can be done in several ways:

  • as two objects for the ID of the resource group and the resource key,
  • as a Message object: in this case the resource ID is stored in the Message object, which can also define additional parameters to be inserted into the resource text,
  • as a single object: if this object happens to be a Message object, it is evaluated as described above; otherwise the object is interpreted as resource key, and the default resource group is assumed.

Another method of ApplicationContext that is related to resource handling is the messageBox() method. As the name implies, this method displays a typical message dialog, which can be used for instance for error or confirmation messages. The title and the text content of this dialog are also obtained from the application's resources. Again, both can be specified as resource keys or as Message objects. (Note: If the title or the text of the message box should not be obtained from resources but specified as plain text, the MessageOutput object maintained by the ApplicationContext can be used directly.)

Using Java resource bundles

JGUIraffe ships with an implementation of the ResourceLoader interface that obtains its data from the standard resource bundles available in Java (i.e. the java.util.ResourceBundle class). This implementation is used per default, so no additional configuration is needed. The implementation class is BundleResourceLoader. Using this default implementation is straight forward. The resource group names used by the ResourceManager are mapped directly to names of resource bundles that are passed to the getBundle() method of java.util.ResourceBundle. So resources can be created in the usual way, e.g. as properties files.

As an example we create a very simple resource bundle using properties files. testresources.properties (shown below) contains the default resource texts. (Note: when building the application the build process has to ensure that this file is copied to the class path on the root level, i.e. it must not be added to a specific package.)

# Test resources in English
test1 = Hello
test2 = OK
test3 = Cancel
test4 = Hello world!
    

Now texts for resource have been defined in English. For users in different countries additional properties files can be created that contains the texts in their specific language. The following listing shows a properties file with German resource texts. This file must be stored under the name testresources_de.properties. Note that the language code is appended to the name of the resource bundle. This has nothing to do with the implementation provided by JGUIraffe, but is a requirement of the java.util.ResourceBundle class; please refer to the documentation of this class for further information. Here is the content of the German resource properties file:

# Test resources in German
test1 = Guten Tag
test2 = Fertig
test3 = Abbrechen

Note that this file does not define the key test4 as does the default version of the resource file. This is totally valid, you can choose to provide translations for only a subset of the resource texts. If the application requests the resource for the key test4 for the German Locale, ResourceBundle finds out that this key is not defined in the specific properties file with the German translation; it then returns the value from the default properties file (i.e. the English text Hello world! in this example).

Accessing these resource texts from a JGUIraffe application is very easy: Just use the name of the resource bundle as resource group name. The bundle name is testresources, so we can write (provided that the variable appCtx refers to the ApplicationContext object):

String msg = appCtx.getResourceText("testresources", "test1");
For an English user the string msg should now contain the text Hello. If the major part of our resource texts was stored in the testresources bundle, we could make this bundle to the default resource group. Then we would only have to provide the resource key when accessing resources and could ommit the resource group name. This can be done either programmatically as in the following fragment;
appCtx.getResourceManager().setDefaultResourceGroup("testresources");
// now access to resources is simplified:
String msg = appCtx.getResourceText("test1");
Alternatively, the default resource group can be set in the application's configuration file. The corresponding fragment can look as follows:
<config>
  <framework>
    <appctx>
      <locale>en</locale>
      <defaultResourceGroup>testresources</defaultResourceGroup>
    </appctx>
Here also the default Locale is set.