1

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 URLClassLoaders and Reflections. I cannot access (I don't want to) plugin ClassLoaders 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;
}
dan1st
  • 12,568
  • 8
  • 34
  • 67

1 Answers1

2

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;
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Is StackWalker a class from an external library? Eclipse doesn't show any possible import statements. – dan1st Jun 18 '19 at 11:25
  • As said, it’s Java 9. See [`StackWalker`](https://docs.oracle.com/javase/9/docs/api/?java/lang/StackWalker.html). Since it’s in `java.lang`, it should work without an `import` statement. – Holger Jun 18 '19 at 11:45
  • A dependency requires the project to be in Java 8. – dan1st Jun 18 '19 at 12:25
  • Is there an equivalent to this in Java 1.8? – dan1st Jun 18 '19 at 12:27
  • Not a clean one. You already linked to it, e.g. [this answer](https://stackoverflow.com/a/421821/2711488) mentions the discouraged `sun.reflect.Reflection`. If you know the limitations and the recommended future way, you may design your software such that the migration will be easy. You could even have conditional code under the hood, to use the recommended way when running under Java 9 and only use the internals when running on Java 8. Then, [this answer](https://stackoverflow.com/a/2924426/2711488) mentions a `SecurityManager` based trick, which seems expensive as it collects *all* callers. – Holger Jun 18 '19 at 12:33