Level: Intermediate Eric Olson (eolson@imation.com), Software Engineer, Imation Corp.
01 May 2001 Creating a fully internationalized Java application using PropertyResourceBundles can present some interesting design and implementation problems, including concern over how to modularize the bundles to be used in different areas of the application. In this article, we will explore a solution based on PropertyResourceBundles, which should simplify the design and implementation problems, while promoting reuse of existing bundles.
PropertyResourceBundles are a Java mechanism for separating locale-specific text from actual Java code. When an application calls for one of these locale-specific properties, a text file associated with the given user's locale is opened and read.
The separation of locale-sensitive messages from Java code is the key to handling internationalization issues. As long as an application uses PropertyResourceBundles to obtain its locale-specific properties, the application can easily be translated to any number of languages by providing property text files for all of the supported locales. (See Resources for some great articles on internationalization.)
Maintenance of a large number of resource bundles can be an overwhelming task -- just as daunting as trying to maintain large pieces of code. So why not apply some of the same principles that are used in code to promote the reuse of resource bundles?
This article focuses on introducing the notion of inheritance to PropertyResourceBundles. In this way, common bundles can be shared while more specific bundles can be introduced without affecting other bundle users.
First, I'll outline the design strategies that are most commonly used.
Current strategies and their problems
Current designs using PropertyResourceBundles normally follow three strategies:
- Use one large, all-encompassing bundle that all application components share.
- Allow each application component its own separate bundle, which is not reused across the application.
- Allow a number of resource bundles with no duplicated property definitions.
Let's look at each of these strategies in more detail. One monolithic bundle: No reuse, high maintenance
The first approach leads to enormous contention on the resource bundle at both design time and run time. It does not support reuse because only one bundle exists and any change made to the bundle could ripple through the other components that use the bundle.
Any redefinition of properties in this approach either requires all components to use the new version or the addition of a new property. In any event, maintenance of a large resource bundle can quickly become unwieldy and more time-consuming than you want.
Smaller, independent bundles: No reuse, uniformity a problem
An alternative to the first approach is to split up the monolithic resource bundle into some subset of the original. This approach would make each bundle independent of the others; each bundle would be allowed to redefine properties for its own use.
This design does not support reuse because each bundle must define its own properties. Each bundle also remains independent in its definitions.
Maintaining uniformity across bundles is now more difficult because each bundle is separate. For instance each bundle may define a property with a mailing address. If this address now changes, then every bundle referring to the mailing address must also be changed.
Property-specific bundles: High maintenance shifted to code
The third approach relies on code developers knowing exactly which resource bundle to turn to for any given property.
Using the mailing address property example (in which an address property is only defined in one resource bundle), any application component that needs to include the address must go to the specific resource bundle where the address is defined. In this scheme, application components must determine which resource bundle to go to for a given key. Any changes made to resource bundles may require similar changes to the application components that use them.
In this approach, the maintenance issue is really just shifted from the resource bundles to the code that retrieves the properties.
For an efficient solution, we need to strike a balance among these three approaches.
 |
What is inheritance?
Inheritance is a concept used extensively in object-oriented programming
languages such as the Java language -- where a new class extends another. The new
class inherits certain properties and methods that were defined in the base class, which promotes reuse. Each inherited property and method can be used as it is or modified for the purposes of the new class.
|
|
Solution: Best of both worlds
The best solution would be one that minimizes the drawbacks of the approaches discussed above while emphasizing the good points of each. Introducing inheritance into the existing internationalization capabilities of Java PropertyResourceBundles is one approach to this problem.
This strategy will maximize reuse of bundles while giving enough freedom to each application component to define new properties or redefine existing properties for its own purposes. The concept builds on the existing Java facilities for defining internationalized bundles, which can be switched in and out for a given locale. Constructed this way, you don't need to duplicate any function; the software simply reuses the function provided.
Bundles can be reused across application components because they can be defined in a hierarchical manner. A component may choose to define some new properties to be used within the component while inheriting other properties from another bundle. A property needs to be defined only once. Other components can simply inherit the property.
Alternatively, a component may decide that an inherited property is inappropriate for its own use, and then redefine the property -- a notion similar to that of overriding a method or property in object-oriented programming.
Adding inheritance also reduces maintenance because each property will be defined in a well-known location in the hierarchy. A property that is changed will ripple through all bundles that inherit from the changed bundle, as shown in Figure 1.
Figure 1. An example of resource bundle inheritance hierarchy

Design goals
To implement our strategy, we first need to design the inheritance mechanism. Keep in mind when designing the mechanism that we will seek to minimize necessary changes to both the existing code and existing resource bundles.
For existing code to use inherited bundles, we'll need to incorporate some minimal changes, including converting from a PropertyResourceBundle to the new object type and using a new manager class (which I'll discuss later).
Do existing resource bundles need to be redefined? No. An attractive side effect of this approach is that existing resource bundles can become the basis for an entire inheritance hierarchy with no changes to the resource bundles. Existing resource bundles can be used as they are.
Another design goal is to be able to define the bundle relationship hierarchy both in the code and outside the code (in another file that will list relationships for a given bundle). This approach offers you the opportunity to define relationships outside of the code, which makes it easier to modify and maintain. The hierarchy may also be changed inside of the code to give fine-grained control to the developer at run time.
Implementing the design
The new implementation's main function is to provide the inheritance mechanism that describes the relationships among bundles.
Unlike class inheritance in the Java language, each new bundle will be allowed to logically inherit properties from more than one parent.
A new class: InheritedPropertyResourceBundle
To implement these ideas, we'll create a new class that extends the java.util.ResourceBundle class. The new class will maintain a single reference to a PropertyResourceBundle, as well as an optional reference to any number of parent bundles (which are bundles that are logically extended by this instance of the InheritedPropertyResourceBundle -- see Listing 1).
Listing 1. Opening of the InheritedPropertyResourceBundle class
/**
* The class for representing an inherited PropertyResourceBundle.
* This class incorporates all the features of the
* java.util.PropertyResourceBundle while adding the notion of
* inheritance. The inheritance relationships can be defined
* in-line, or they can be provided in a relationships file which
* is locatable by the given ClassLoader. This file must be
* named <resource_base_name>.relationships.
* @see java.util.PropertyResourceBundle
*/
public class InheritedPropertyResourceBundle extends ResourceBundle {
/**
* Describes the parent relationships to other
* InheritedPropertyResourceBundles. These parent bundles
* will be checked if any key cannot be found in this bundle.
*/
private Vector relationships = null;
/**
* The java.util.ResourceBundle that backs this instance.
* This bundle will always be checked first for any key.
*/
private ResourceBundle instance = null;
|
Note: See Resources to download the complete source code for this article.
When an InheritedPropertyResourceBundle is queried for a given key, the backing PropertyResourceBundle will first be checked for the resource, as shown in Listing 2.
Listing 2. Checking the PropertyResourceBundle for the given key
Object retVal = null;
try {
// check the PropertyResourceBundle for the resource.
retVal = instance.getObject(key);
} catch (MissingResourceException mre) {
//Do nothing here. This will cause a search of the parents
//of this inherited bundle.
}
|
If the backing PropertyResourceBundle cannot find the resource, the parent bundles will be checked in order, as shown in Listing 3.
Listing 3. Checking the parent bundles for the given key
if (retVal == null) {
// resource not found in PropertyResourceBundle, check all parents.
if (relationships != null) {
for (Enumeration e = relationships.elements(); e.hasMoreElements();) {
// attempt to get the resource from the current parent bundle.
retVal = ((InheritedPropertyResourceBundle)e.nextElement()).
handleGetObject(key);
if (retVal != null) return retVal;
}
}
}
|
Another of our design goals is to allow the relationship hierarchy to be modified in the code as well as outside of the code, in a separate file. (We'll deal with the second issue later.)
To allow the hierarchy to be changed, a set of modifier methods are provided, as shown in Listing 4. These methods allow the addition and removal of parent bundles at run time.
Listing 4. Relationship hierarchy modifier methods
/**
* Adds the InheritedPropertyResourceBundle at the end of the parent
* list.
* @param r the resource bundle which will be added to the end
* of the parent list.
*/
public void addRelationship(InheritedPropertyResourceBundle r) {
relationships.add(r);
}
/**
* Adds the InheritedPropertyResourceBundle at the given location. All
* subsequent parents will be pushed down one space.
* @param r the resource bundle which will be added.
* @param l the insertion location of the resource bundle. For example,
* a value of 0 will place this parent bundle at the beginning of the list
* - it will be checked for any key first.
*/
public void addRelationship(InheritedPropertyResourceBundle r, int l) {
relationships.add(l, r);
}
/**
* Removes the resource bundle from the parent list.
*/
public void removeRelationship(InheritedPropertyResourceBundle r) {
relationships.remove(r);
}
|
A new class: RelationshipLoader
We create another class to handle the details of loading parent bundles from a file. We do this to separate the logic for parsing the flat file from the implementation of the InheritedPropertyResourceBundle.
The new class, RelationshipLoader, handles the file-location logic as well as the actual parsing of the file.
The RelationshipLoader class looks for a file with the same name as the InheritedPropertyResourceBundle but with a .relationships file extension. For example, if a bundle was created with the base bundle name resources.ProductNames, then the RelationshipLoader class would search for a file named resources/ProductNames.relationships following the search rules governed by the class loader.
Because the relationships are defined on a global level and are independent of the locale used, the .relationships file does not need to be translated.
Ideally, the .relationships files are located in the same directory as the actual .properties files, so all resource bundle files are kept in a well-known location. If the RelationshipLoader does not find a .relationships file for the resource bundle, it assumes that the bundle does not inherit from any parent bundles. It will function the same as a PropertyResourceBundle. See Listing 5.
This separation of inheritance relationship definition from the actual resource bundles allows any current resource bundles to be used as they are -- no modification is necessary to use an existing resource bundle in this new scheme.
Listing 5. Initializing parent bundles from the relationships file in RelationshipLoader
/**
* Initializes all parent relationships defined in the
* relationships file. Subsequent to this method call,
* the createRelationships() method can be called to set
* up the parents in the resource bundle.
*/
protected void initRelationships() {
// all parent bundles defined in the relationships file.
relationships = new Vector();
// gets all parent bundle names from the file.
Enumeration e = getRelationshipNames();
while (e.hasMoreElements()) {
String curName = (String)e.nextElement();
// Gets the InheritedPropertyResourceBundle from the
// manager with parent bundle name and the locale
// and class loader used to initialize the bundle.
// Then adds it to the list of parent bundles.
relationships.add(InheritedBundleManager.getInstance().
getBundle(curName, locale, loader));
}
}
/**
* Adds relationships to the InheritedPropertyResourceBundle.
* @param source the InheritedPropertyResourceBundle.
*/
protected void createRelations(InheritedPropertyResourceBundle source) {
for (Enumeration e = relationships.elements(); e.hasMoreElements();) {
InheritedPropertyResourceBundle cur =
(InheritedPropertyResourceBundle)e.nextElement();
source.addRelationship(cur);
}
}
|
A new class: InheritedBundleManager
The last new class is a manager class for all created InheritedPropertyResourceBundles called the InheritedBundleManager. The InheritedBundleManager class is a singleton -- only one instance is alive in the Java virtual machine. The reason for this manager class is to minimize the resource bundles in memory. Think about it this way: if a new parent bundle was created every time another bundle extended it, the number of resource bundles in the VM could quickly grow out of hand.
For this reason, the InheritedBundleManager class manages all instances of created InheritedPropertyResourceBundles. Only one instance of an InheritedPropertyResourceBundle is allowed per bundle name, locale, and class loader (locale and class loader being optional parameters).
For example, the first time the resources.ProductNames bundle is requested with the default locale and class loader, the manager creates a new instance and caches it. When a subsequent request with the same parameters comes in, the cached version is returned.
All new code goes through the InheritedBundleManager to get an instance of an InheritedPropertyResourceBundle. It is important that a user of the inherited bundle mechanism do the same thing.
Pitfalls
This approach is no panacea; the drawbacks include cyclical inheritance, problems with obtaining a resource bundle reference, and having an unexpected class extended. Let's look at each of these in more detail. Cyclical inheritance
The current implementation does not check for cyclical inheritance. For example, if two InheritedPropertyResourceBundles are defined -- one is named resources.CompetitorProductNames and the other is named resources.ProductName -- and both are defined to logically extend the other, the solution will fail.
This check is intentionally left out of the design because it could degrade performance, especially if the inheritance tree is large.
The problem can be eliminated entirely if the inheritance hierarchy is clearly defined to make sure there are no cyclical inheritance dependencies. The check could be added to the code and an exception thrown if an added parent would cause a cyclical inheritance dependency. Because all parents are added through one of the two addRelationship() methods in the InheritedPropertyResourceBundle class, the check could be added there.
Obtaining a resource bundle reference
The normal method for obtaining a reference to a resource bundle -- through the ResourceBundle.getBundle() methods -- cannot be used in this approach, because these static methods are hard-coded to return a specific type. In other words, there is no way to tie into this mechanism to nicely return an instance of the new InheritedPropertyResourceBundle class.
To obtain a reference to an InheritedPropertyResourceBundle, a client must go through the InheritedBundleManager interface. For this reason, you may need to change some code in an existing application.
Unexpected class extension
The implementation of the InheritedPropertyResourceBundle class extends java.util.ResourceBundle rather than java.util.PropertyResourceBundle.
Why? The creation of a PropertyResourceBundle is tightly coupled to the ResourceBundle.getBundle() methods. If the implementation extended PropertyResourceBundle instead, much of the already existing logic from the ResourceBundle.getBundle() methods would need to be duplicated by the manager class. This is another reason why some code changes may be necessary.
If client code expects a java.util.ResourceBundle type, then this solution will work fine without modification. On the other hand, if client code expects a java.util.PropertyResourceBundle, then the code will need to be modified.
Wrapup
The inheritance approach can help alleviate some of the headaches associated with an internationalized development effort:
- We can preserve the current functionality associated with resource bundles while adding some inheritance semantics. Our approach allows separate resource bundles to be composed to fit in a specific application component while also allowing users to reuse other resource bundles to minimize duplication of resources.
- Maintenance is reduced because changes to resources at a higher level in the inheritance hierarchy will be reflected at lower levels.
- Finally, we minimize source code changes, while completely removing the need to modify existing resource bundles. To convert an existing resource bundle to an "inherited" version simply requires either the addition of a relationships file or a parent bundle definition in the client code.
Developing a fully internationalized Java application using the method described here can greatly simplify your design and implementation issues, and at the same time make it easier to reuse existing bundles.
Download | Name | Size | Download method |
|---|
| j-bundles.zip | | HTTP |
Resources
About the author  | 
|  |
Eric A. Olson is a Software Engineer at Imation Corp. in Rochester, MN. He holds a degree in Computer Science from the University of St. Thomas in St. Paul, MN, and is pursuing his Masters of Science in Computer Science through the University of Minnesota. Eric is now working on software solutions in the storage industry. In his spare time he enjoys reading, playing golf, and remodeling his house. Contact Eric at eolson@imation.com. |
Rate this page
|