I have implemented a Plugin mechanism and language packs using ResourceBundles in Java.
It works perfectly fine if I want to get a ResourceBundle
from the core program (not from a plugin).
The problem is that I want to add the possibility to create a ResourceBundle
that is in the plugin and only works within the plugin.
Plugins are loaded using URLClassLoader
s and Reflections
. I cannot access (I don't want to) plugin ClassLoader
s from the translation class.
So, the program loads the plugin and executes a method inside the plugin later (The plugin is not in the Classpath) and that plugin executes the translate method.
In order to archieve this, I want to get the ClassLoader Object from the calling method.
Somethng like this or this might be useful, but I don't see a way to get the Class/ClassLoader and not the name of the class.
I thought that I could use the Stacktrace to get the ClassLoader of the calling method but I can only get the name using .getClassName
and no Class
or ClassLoader
Object of the Caller.
This is what I have:
translate
public static String translate(Locale locale,String s) {
for (ResourceBundle bundle : getResourceBundles(locale/*,Thread.currentThread().getStackTrace()[1].getClassLoader();*/)) {
try {
return bundle.getString(s);
}catch (MissingResourceException e) {
//ignore/next iteration
}
}
return s;
}
getResourceBundles
private static Set<ResourceBundle> getResourceBundles(Locale locale,ClassLoader... loaders){
Set<ResourceBundle> bundles=new HashSet<>();
bundles.add(ResourceBundle.getBundle(BASE_NAME,locale,MyClass.class.getClassLoader()));
for (ClassLoader loader : loaders) {
ResourceBundle pluginBundle=getResourceBundle(g,loader);
if (pluginBundle!=null) {
bundles.add(pluginBundle);
}
}
return bundles;
}
I don’t think that this trial and error approach is a good idea. Neither is refetching all bundles for every single string. It doesn’t even seem that this translation service adds a value over the alternative of just letting the plugin read their bundle and call getString
on it, at least not a value that justifies the overhead and complexity of the code.
Since the standard ResourceBundle.getBundle
methods do already consider the caller’s context, the field declaration and acquisition expression would be a trivial single-liner when being placed within the plugin and invoking getString
on it, is not more complicated than invoking your translation service’s method.
For completeness, getting the caller class in a standard way, is possible starting with Java 9. Then, you can do it like
private static final StackWalker STACK_WALKER
= StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
public static String translate(Locale locale, String s) {
for(ResourceBundle bundle: getResourceBundles(locale,
STACK_WALKER.getCallerClass().getClassLoader())) {
try {
return bundle.getString(s);
}catch (MissingResourceException e) {
//ignore/next iteration
}
}
return s;
}