Separating localized resources from the source code where they are used is a well-known and approved design. Java's ResourceBundle class from the java.util package provides a straightforward way to handle this. However, there is a tight coupling between a resource bundle and a localized object, which you may not want to hard-code into your application. Avoiding such hard-coding makes it easier to modify the storage format and location of resources. You can achieve this by creating a custom ResourceBundle class. This article shows you how.
Customizing the standard ResourceBundle class allows you to abstract localized resources from their storage format and location and eases creation of new types of ResourceBundle objects. Normally, you would use ResourceBundle in the following way:
String localizedMsg = ResourceBundle.getBundle("resources.test1").getString(msg)
This approach has two drawbacks. First, you have to explicitly specify (hard-code into your application) a resource bundle name (resources.test1). This becomes complicated when the number of classes using the resource bundle grows. Second, the standard ResourceBundle class provides only two data storage alternatives: in a file (PropertyResourceBundle) or a class (ListResourceBundle). If you want to retrieve resources from elsewhere, such as a database or FTP server, you have to create your own ResourceBundle class. You can elegantly avoid both of these problems by using customized ResourceBundle classes from the very beginning. Let's look at the process of creating a customized ResourceBundle class.
Creating your own ResourceBundle
First, you need to create a class that keeps all the resource bundles your application will use. Listing A shows such a class.
The important part of this listing is the getBundle() method, which returns a CommonResourceBundle. The returned object knows where and how to retrieve a particular resource. Now you can get localized data like this:
String localizedMsg = CommonResourceBundle.getBundle().getString(msg)
Note that you do not specify a resource bundle name at all. The trick is to add resource bundles to CommonResourceBundle prior to using it. You can do this either by means of the addResourceBundle(...) method or with a configuration file.
As CommonResourceBundle extends the standard ResourceBundle, you can use the regular technique to get a localized object: CommonResourceBundle.getBundle("resources.test1").getString(msg)
To implement your custom ResourceBundle, you must override the standard ResourceBundle. You do this by overriding two methods: getKeys() and handleGetObject(String s). The latter method returns an object for the given key after searching through all available resource bundles; it returns null if the key is not found. The available resource bundles are the ones that were added to CommonResourceBundle.
When you have created the CommonResourceBundle that keeps all the bundles you want to use, you can create customized ResourceBundle classes to access localized resources at particular locations and in specified formats. As an example, let's create the DBResourceBundle class, which will get localized objects from a database. Listing B provides a sample.
The buildProperties() method fetches resources from a database and caches them locally into Properties from java.util package. This method is called only once during a DBResourceBundle object instantiation. The handleGetObject() method returns a resource object found in Properties; if it doesn't find the object, it returns null.
Configure your resource bundle
You must configure a CommonResourceBundle before you can use it. As we mentioned earlier, you can do this by calling the addResourceBundle(...) method or setting up a configuration file. The sample below adds two resource bundles to the CommonResourceBundle: the standard and customized ResourceBundles:
CommonResourceBundle.addResourceBundle(ResourceBundle.getBundle("resources.test1")); CommonResourceBundle.addResourceBundle(new DbResourceBundle("jdbc:oracle:thin:scott/tiger@mypc:1521:ORCL:table:ScottProp"));
You can achieve the same result by specifying configuration parameters, that is, adding them to system properties (calling System.setProperty(...)) or using an XML configuration file. Here are examples of FileResourceBundle and DBResourceBundle configuration parameters: file.resource.bundle.name=resources.test1,resources.test2
The first parameter contains a comma-separated list of resource files or class names. For each name, a ResourceBundle is created and added to the CommonResourceBundle. The second contains a list of database URLs and table names (ScottProp). A table has two columns (a key and a value) and contains localized resources. For each URL, a Properties object is created and loaded with data from a database.
Now you have everything you need to create your own customized ResourceBundle. You can find more information on the standard ResourceBundle specification and details of its simple usage at the Sun Web site. The complete code for this article is available in Listing C.