core
the foundation of the platform

[home] [documents] [downloads] [resources] [planning] [testing]

Message Bundles
Description

Standard Java ResourceBundles have quite inefficient space characteristics. Since a running Eclipse tends to have many externalized messages we have implemented a new message bundle story to be used in Eclipse. The mechanism is quite simple and completely generic - it can be used anywhere.

Summary of the new approach:

  • messages.properties - this file is same as before except all keys need to be valid Java identifiers.
  • Each message file has a corresponding Java class.
  • Each key/value pair in the file has a public static String field whose name is the same as the message key.
  • When message bundles are loaded, the values of the fields are set to be the values from the messages.properties files.
  • The message properties files are purged from memory.

When creating a new message:

  • create a field in your Messages.java file
  • create a key/value pair in your messages.properties file where the key name matches the field name
  • to reference the message, simply reference the field (e.g. Messages.my_key) rather than the standard lookup

 

File Formats

Client Code

Old Code:

public class MyClass {
   public void myMethod() {
      String message;
      ...
      // no args
      message = Messages.getString("key.one"); //$NON-NLS-1$
      ...
      // bind one arg
      message = MessageFormat.format(Messages.getString("key.two"), new Object[] {"example usage"}); //$NON-NLS-1$ //$NON-NLS-2$
      ...
   }
}

New Code:


public class MyClass {
   public void myMethod() {
      String message;
      ...
      // no args
      message = Messages.key_one;
      ...
      // bind one arg
      message = NLS.bind(Messages.key_two, "example usage"); //$NON-NLS-1$
      ...
   }
}

 

Messages.java

Old Code:
public class Messages {
   private static final String BUNDLE_NAME = "org.eclipse.core.utils.messages"; //$NON-NLS-1$
   private static final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME);
   
   public static String getString(String key) {
      try {
         return bundle.getString(key);
      } catch (MissingResourceException e) {
         return key;
      }
   }
}
New Code:

import org.eclipse.osgi.util.NLS;

public class Messages extends NLS {
   private static final String BUNDLE_NAME = "org.eclipse.core.utils.messages"; //$NON-NLS-1$
   
   public static String key_one;
   public static String key_two;
   ...
   static {
      NLS.initializeMessages(BUNDLE_NAME, Messages.class);
   }
}

 

messages.properties

Old Code:
key.one = Hello world.
key.two = This is an {0} of binding with one argument.
New Code:
key_one = Hello world.
key_two = This is an {0} of binding with one argument.

 

Performance

Time

  • Using a message is marginally faster since it is just a field access rather than a lookup in the resource bundle.
  • Time to load and initialize a bundle is VM dependant but we have seen 5% to 46% improvements.

 

Memory Footprint

  • This is very much a scalability play. The more you use, the more you save.
  • The rough space savings is 88 + 4N bytes per message where N is the number of characters in the key.
  • The absolute best-case scenerio for the Eclipse SDK, if every property file is loaded and every key referenced, is roughly 4.5M of memory. (based on the January 11, 2005 integration build)
  • Realistic scenarios for the Eclipse IDE should see savings on the order of 500KB

 

Other benefits
  • Easily catch missing keys - Message lookups are now field accesses so you cannot reference a key that doesn't exist or you will get a compile error.
  • Easily find typos in code when referencing keys - Each key is represented by a field in the class so if the referencing code makes a spelling error, then you will get a compile error.
  • Find unused keys - During the development cycle code is deleted, moved and messages are changed. As a result there are keys in the messages.properties file which are never referenced.These can easily be found now since you can just do a search for references to the field. If there are no references, then delete the field from the class and the key/value pair from the file.

 

Drawbacks
  • There are now 2 files to maintain - Now the messages.properties and the java file must be kept in sync. There is an opportunity for tooling to help with this; a validation tool could indicate problems with markers. Currently there are debug options which log entries which exsit in one file but not the other.

 

Tools
Conversion

We have written a tool which aids in converting from the basic Java resource bundle look-up mechanism, to the new format. There is no requirement to do this conversion unless you want to take advantage of the new mechanism.

Note that the message bundle access class is replaced when the tool is run. If you define extra code, constants, etc in that class then please read the notes below to ensure that you don't have problems.

Here are the steps to use when converting your code.

  • Download JDT/UI's new version 1.0.0 of the tool and install the plug-in.
  • Run Eclipse.
  • Synchronize with the repository. (you will be using the Synchronize view as your compare browser to view changes)
  • Select your message bundle access class. (e.g. the class which has the #getString(String) method in it)
  • From the context menu, choose "Convert Message Bundle".
  • Choose the class's associated .properties file from the resulting "Open Resource..." dialog.
  • Use the Synchronize view to review the changes.
  • Release the new code.

Notes:

When using the NLS tooling from previous Eclipse releases, the java file which was created had the format of the "old" Messages.java file as described above. (basic class with #getString(String) method) Some plug-in owners have created extra helper methods on the class to aid in their message bundle lookup. Since the conversion tool has only basic functionality, these plug-in developers must perform a couple of extra steps before running the tool.

Essentially what you want to do is get your java file into the basic template form. This is possible via using existing refactorings. For instance, if your class defines a method like this:

public String getString(String key, Object binding) {
   return MessageFormat.format(getString(key), new Object[] {binding});
}

Then you want to do the following:

  1. Change the method body to be: return NLS.bind(getString(key), binding);
  2. Select the method name and from the context menu choose the "Inline" refactoring. This will replace calls to this method in your code with calls to the code in step 1.
  3. Run the tool. Since only calls to #getString(String) exist now, the tool will run sucessfully.

Eclipse 3.1: Think Fast!