Search |
||
Discovering ResourceBundles at runtimePosted by joconner on March 6, 2009 at 2:34 AM PST
Yesterday a friend asked me a question about Java resource bundles: how can I get my application to discover resource bundles dynamically? It seemed like a simple question. I answered in my typical fashon: Well, everytime you need a new bundle, just add that bundle's jar or directory to your classpath and run the application. Or if it's just a single properties file, add that file to your existing classpath, maybe drop it into the same directory as your other properties files. My friend wasn't content with that. He had done some homework before asking me. He said, "My application is in a jar file. I launch it on the command line like this, I explained that when you use Of course, he still needed a solution. Hmmm...how could I help him? It turns out that you actually can add classes to your classpath dynamically at runtime. Your application can discover new jar files and use the functionality it finds in them. The Let's look at how this might work for loading resource bundles. In my examples, I'm going to create an application that displays three greetings in a panel. I'll package the application as a jar file. The application will load its resource bundles from jar files that I'll place into a "plugins" subdirectory directly under the application jar. The big deal about this is that the application will discover the jar files that are in the First, let's start with the
package com.joconner.hello;
import com.joconner.resplugin.ResourceLoader;
import java.util.Locale;
import java.util.ResourceBundle;
/**
*
* @author JOConner
*/
public class GreetingDialog extends javax.swing.JDialog {
/** A return status code - returned if Cancel button has been pressed */
public static final int RET_CANCEL = 0;
/** A return status code - returned if OK button has been pressed */
public static final int RET_OK = 1;
private ResourceBundle res = null;
/** Creates new form GreetingDialog */
public GreetingDialog(java.awt.Frame parent, boolean modal) {
super(parent, modal);
ClassLoader resourceLoader = ResourceLoader.createForDirectory("plugins/");
res = ResourceBundle.getBundle("com.joconner.hello.resources.messages",
Locale.getDefault(), resourceLoader);
initComponents();
}
/** @return the return status of this dialog - one of RET_OK or RET_CANCEL */
public int getReturnStatus() {
return returnStatus;
}
private void initComponents() {
tfMorning = new javax.swing.JTextField();
tfAfternoon = new javax.swing.JTextField();
tfEvening = new javax.swing.JTextField();
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
closeDialog(evt);
}
});
tfMorning.setText(res.getString("greeting.morning"));
tfAfternoon.setText(res.getString("greeting.afternoon"));
tfEvening.setText(res.getString("greeting.evening"));
As you can see, the above dialog doesn't do anything surprising with the bundles; it retrieves a bundle, pulls strings from it, and uses those strings in 3 text fields. However, notice that I've instructed the ResourceBundle class to use a special class loader. The class loader code is here:
public class ResourceLoader {
private ResourceLoader() {}
private ResourceLoader(File dir) {
this.directory = dir;
}
/**
* Create a ResourceLoader for a specific file system location.
* All JAR files and subdirectories in the location will be added
* to the classpath
*/
public static ClassLoader createForDirectory(File dir) {
ResourceLoader loader = null;
if (dir.isDirectory()) {
loader = new ResourceLoader(dir);
}
loader.addJarsToPath();
return loader.getClassLoader();
}
public static ClassLoader createForDirectory(String dir) {
File f = new File(dir);
return createForDirectory(f);
}
private File[] addJarsToPath() {
File[] jarFiles = directory.listFiles();
List
This ClassLoader puts all jar files that are in my application-defined "plugins" subdirectory onto the classpath. See the Finally, just create your ResourceBundles as you normally would. Create jar files for each set of localized bundles for a specific language. Then drop them into a "plugins" directory immediately under your application's main directory...the place your applications sits on the file system. Now you can add new localized bundles to your application without having to recompile or jar your primary application. You don't even have to change its classpath since new "plugins" jar files will automatically be added to the classpath for resource bundle creation. There you go. Enjoy. »
Related Topics >>
Programming Comments
Comments are listed in date ascending order (oldest first)
Submitted by rdawes on Fri, 2009-03-06 13:57.
Here is an equivalent for loading classes from jars at runtime:
Usage is as follows:
Collection plugins = PluginLoader.loadPlugins("./plugins/", Iface.class);
for (Iface plugin : plugins) {
plugin.doSomething();
}
The convention is that each JAR in the plugins/ directory should have a file META-INF/services/ which contains a list of classes which implement the specified interface, one per line.
public class PluginLoader {
private static FilenameFilter JAR_FILTER = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
};
private static FilenameFilter RECURSIVE_FILTER = new FilenameFilter() {
public boolean accept(File dir, String name) {
return JAR_FILTER.accept(dir, name)
|| new File(dir, name).isDirectory();
}
};
public static Collection loadPlugins(String dir, Class iface)
throws MalformedURLException, IOException, ClassNotFoundException,
InstantiationException, IllegalAccessException {
return loadPlugins(new File(dir), iface);
}
public static Collection loadPlugins(File dir, Class iface)
throws MalformedURLException, IOException, ClassNotFoundException,
InstantiationException, IllegalAccessException {
return loadPlugins(dir, iface, false, false);
}
public static Collection loadPlugins(File dir, Class iface,
boolean recursive, boolean unpacked) throws MalformedURLException,
IOException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
Collection urls = new HashSet();
if (unpacked && dir.isDirectory())
urls.add(dir.toURI().toURL());
collectJars(dir, urls, recursive);
URL[] u = urls.toArray(new URL[urls.size()]);
ClassLoader parent = Thread.currentThread().getContextClassLoader();
URLClassLoader loader = new URLClassLoader(u, parent);
Collection classes = findImplementations(loader, iface);
return loadClasses(loader, iface, classes);
}
private static void collectJars(File dir, Collection jars,
boolean recursive) {
if (!dir.isDirectory())
return;
FilenameFilter filter = recursive ? RECURSIVE_FILTER : JAR_FILTER;
String[] items = dir.list(filter);
if (items == null)
return;
for (int i = 0; i Collection findImplementations(
URLClassLoader loader, Class iface) throws IOException {
Collection names = new HashSet();
try {
String service = "META-INF/services/" + iface.getName();
Enumeration e = loader.findResources(service);
while (e.hasMoreElements()) {
URL url = e.nextElement();
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(
new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
int i = line.indexOf('#');
if (i > -1)
line = line.substring(0, i).trim();
if (!"".equals(line))
names.add(line);
}
br.close();
}
} catch (IOException e) {
IOException ioe = new IOException("Error finding resources");
ioe.initCause(e);
throw ioe;
}
return names;
}
private static Collection loadClasses(ClassLoader loader,
Class iface, Collection names)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Iterator it = names.iterator();
Collection impls = new HashSet();
while (it.hasNext()) {
Class c = loader.loadClass(it.next());
if (!iface.isAssignableFrom(c))
throw new ClassCastException("Class " + c.getName()
+ " is not an implementation of " + iface.getName());
impls.add(iface.cast(c.newInstance()));
}
return impls;
}
}
|
||
|
|