views:

228

answers:

1

I maintain an applet that helps users to upload photos to our service. The applet jar file has a few .properties files:

>> jar -tf applet.jar | grep prop
res/messages.properties
res/messages_ca.properties
res/messages_es.properties
...

These are loaded during applet initialization.

messages = ResourceBundle.getBundle("res.messages");

This call however generates 4 to 5 requests to the server looking for files that aren't in the jar file, before falling back on to the .properties file included in the .jar.

From the server error log:

[error] File does not exist: /photos/res/messages.class
[error] File does not exist: /photos/res/messages_en.class
[error] File does not exist: /photos/res/messages_en.properties
[error] File does not exist: /photos/res/messages_en_US.class
[error] File does not exist: /photos/res/messages_en_US.properties

The documentation for ResourceManager.getBundle explains that this is the way it is done:

getBundle then iterates over the candidate bundle names to find the first one for which it can instantiate an actual resource bundle. For each candidate bundle name, it attempts to create a resource bundle:

  • First, it attempts to load a class using the candidate bundle name. If such a class can be found and loaded using the specified class loader, is assignment compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated, getBundle creates a new instance of this class and uses it as the result resource bundle.

  • Otherwise, getBundle attempts to locate a property resource file. It generates a path name from the candidate bundle name by replacing all "." characters with "/" and appending the string ".properties". It attempts to find a "resource" with this name using ClassLoader.getResource.

I am sure there are good reasons for doing it this way, but in my case it seems wasteful to me that there should be five failed requests for files which are known to be non-existent on the server.

Is there any way to teach the applet to look for these files in the .jar file only?

Note: I am not a Java programmer, so if there is a better way to load properties than ResourceManager.getBundle, please let me know.

+3  A: 

Java 1.6 introduced the ResourceBundle.Control class, which might have offered some help if you weren't supporting Java 1.4. As you are, it isn't rocket science to write your own bundle manager.

This demo code restricts bundle loading to properties files in a given set of languages:

public class CustomManager {
  private static final Map BUNDLES = new HashMap();

  public static ResourceBundle getBundle(String name, Set languages) {
    Locale locale = Locale.getDefault();
    if (languages.contains(locale.getLanguage())) {
      name = name + "_" + locale.getLanguage();
    }
    synchronized (BUNDLES) {
      ResourceBundle bundle = (ResourceBundle) BUNDLES.get(name);
      if (bundle == null) {
        ClassLoader loader = getContextClassLoader();
        bundle = loadBundle(loader, name.replace('.', '/') + ".properties");
        BUNDLES.put(name, BUNDLES);
      }
      return bundle;
    }
  }

  private static ClassLoader getContextClassLoader() {
    return Thread.currentThread().getContextClassLoader() != null ? Thread
        .currentThread().getContextClassLoader() : CustomManager.class
        .getClassLoader() != null ? CustomManager.class.getClassLoader()
        : ClassLoader.getSystemClassLoader();
  }

  private static ResourceBundle loadBundle(ClassLoader loader, String res) {
    try { InputStream in = loader.getResourceAsStream(res);
      try { return new PropertyResourceBundle(in);
      } finally { in.close(); }
    } catch (IOException e) { throw new IllegalStateException(e.toString()); }
  }
}

This code simulates a call to retrieve strings for the Spanish/Spain locale:

Set languages = new HashSet(Arrays.asList(new String[] { "es", "ca" }));
Locale.setDefault(new Locale("es", "ES"));
ResourceBundle bundle = CustomManager.getBundle("l10n.res.foo", languages);
System.out.println(bundle.getString("bar"));

Since the set of languages is es and ca and CustomManager supports only languages (not country codes or variants) only the following files could be loaded:

l10n/res/foo.properties
l10n/res/foo_es.properties
l10n/res/foo_ca.properties

How sophisticated you want to get with Locale and ClassLoader support, and where you want to manage your language list, is up to you.

Caveat: I don't think I violated any security restrictions with the implementation, but I only tested the code in a desktop app.

McDowell
@McDowell: Thanks for taking the time to reply. I will try this approach.
Prakash K