Loading Class from Path - java

I want to create 'modules' which extend a 'Module' interface, these would be separate jars that get loaded on runtime - essentially adding extra features. I want to have these loaded as the 'Module' object (from the interface).
I've tried the below code, and while my code prints out the Found module, it doesn't do anything after that.
try {
Collection<URL> urlList = new ArrayList<>();
Path pluginsDir = Paths.get(Common.getPlugin().getDataFolder().getPath(), "modules");
try (DirectoryStream<Path> jars = Files.newDirectoryStream(pluginsDir, "*.jar")) {
for (Path jar : jars) {
System.out.println("Found module (" + jar.toFile().getName() + ").");
urlList.add(jar.toUri().toURL());
}
}
URL[] urls = urlList.toArray(new URL[0]);
ClassLoader pluginClassLoader = new URLClassLoader(urls, this.getClass().getClassLoader());
ServiceLoader<Module> loader = ServiceLoader.load(Module.class, pluginClassLoader);
for (Module module : loader) {
System.out.println("module.getName() = " + module.getName());
}
} catch (IOException ex) {
ex.printStackTrace();
}
I'd expect it to print out the modules name (as an interface method) but it doesn't loop.
For example, a module would do;
public class ExampleModule implements Module {
#Override
public String getName() {
return "Example";
}
#Override
public void load() {
Bukkit.getLogger().info("Hello!");
}
}
Thanks.

Related

How to load ResourceBundles within loaded JAR

I have a problem with loading resource bundles in loaded jars. The main program is loading jars from a folder with a plugin manager. When an object of the main class of a jar is initialized by the plugin manager, resource bundles of this jar can be loaded. By this, I mean in a static block or in a constructor. Otherwise, an MissingResourceException is thrown. Like when you call a method on that object, that tries to load an existing resource-bundle
Currently, I use a static block at the beginning of the main class of a jar to load all resource bundles of the plugin with possible locales. Because of this, the resource bundles will be cached for some time. Also, my current way seems to work out for sub-loaded jars the same way as for the loaded jar
public class PluginMain implements PluginInterface {
static {
for (Locale availableLocale : getAvailableLocales()) {
try {
ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_PATH, availableLocale);
} catch (MissingResourceException e) {
e.printStackTrace();
}
}
}
...
}
I think it's about the classLoader that is loading the resource-bundle. Still i cannot find a good solution.
I already tried to find some solutions. The best i could find fitting is Loading with ResourceBundle from inside a jar, but that did not work out.
Edit: I load my jars like this
public class PluginManagerImpl implements PluginManager {
private final List<PluginInterface> loadedPlugins = new ArrayList<>();
private final String path;
public PluginManagerImpl(String path) {
File pluginsDir = new File(path, "plugins");
this.path = pluginsDir.getPath();
if (pluginsDir.exists()) {
//pluginsfolder exists
File[] files = pluginsDir.listFiles();
if (files != null) {
for (File f : files)
if (!f.isDirectory()) {
loadPlugin(f);
}
}
} else {
//pluginsfolder does not exist
if (pluginsDir.mkdir()) {
Output.WriteLine("Dictionary created: " + pluginsDir.getPath());
}
}
}
#Override
public void loadPlugin(File file) {
URL urlFile;
//trying to load file, convert it first to URI and then to URL
try {
urlFile = file.toURI().toURL();
} catch (MalformedURLException e) {
Output.WriteLineProblem(e.getMessage(), 4);
return;
}
//trying to create JAR-file from file
try (
//use JarFIle and URLClassLoader as auto-closable
JarFile jarFile = new JarFile(file);
//use classloader of this class as parent classLoader
URLClassLoader classLoader = new URLClassLoader(new URL[]{urlFile}, this.getClass().getClassLoader())
) {
//load manifest
Manifest manifest = jarFile.getManifest();
//read attributes from manifest
Attributes attributes = manifest.getMainAttributes();
//get main class from attributes
String main = attributes.getValue(Attributes.Name.MAIN_CLASS);
if (main == null) {
Output.WriteLineProblem(file.getName() + " has no main specified");
return;
}
String title = attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
if (title == null) {
//https://maven.apache.org/shared/maven-archiver/index.html
Output.WriteLineProblem(file.getName() + " has no implementation title specified");
return;
}
//https://javapapers.com/core-java/java-class-loader/
//load class with classLoader of jarFile
Class<?> cl = classLoader.loadClass(main);
//get implemented interfaces of class
Class<?>[] interfaces = cl.getInterfaces();
//iterate over interfaces and check for PluginInterface.class
boolean isPlugin = false;
for (Class<?> anInterface : interfaces) {
if (anInterface.equals(PluginInterface.class)) {
isPlugin = true;
break;
}
}
if (isPlugin) {
//load all classes in jar file
loadClassesOfjarFile(jarFile, cl.getClassLoader());
//add the pluginfile
PluginInterface plugin = (PluginInterface) cl.getConstructor().newInstance();
plugin.calledAfterInstancing(new File(path, title).getPath());
Output.WriteLine("Loaded Plugin " + title);
loadedPlugins.add(plugin);
}
} catch (Exception e) {
Output.WriteLineProblem("Error on checking " + file.getName() + " for plugin");
e.printStackTrace();
}
}
public static void loadClassesOfjarFile(JarFile jarFile, ClassLoader classLoader) {
jarFile.entries().asIterator().forEachRemaining(jarEntry -> {
String jarEntryName = jarEntry.getName();
if ((jarEntryName.endsWith(".class"))) {
String className = jarEntry.getName().replaceAll("/", "\\.");
String myClass = className.substring(0, className.lastIndexOf('.'));
try {
Class<?> clazz = classLoader.loadClass(myClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else if (jarEntryName.endsWith(".xml")) {
String resourceName = jarEntry.getName().replaceAll("/", "\\.");
classLoader.getResourceAsStream(jarEntry.getName());
}
});
}
}
Edit 2: Here a sample project to test
The resource bundles are contained in the the resource folder of the plugin.
Hierarchy of the project
Sample for the main program:
package main;
public class Main {
public static final String DEFAULT_PATH = FileSystems.getDefault().getPath("").toAbsolutePath().toString();
public static void main(String[] args) {
PluginManager plugins = new PluginManager(DEFAULT_PATH);
List<PluginInterface> loadedPlugins = plugins.getLoadedplugins();
for (PluginInterface loadedPlugin : loadedPlugins) {
loadedPlugin.loadResourceBundle(Locale.ENGLISH);
}
}
}
Sample for plugin:
package plugin;
public class Main implements PluginInterface {
static {
Locale locale = Locale.ENGLISH;
ResourceBundle main = ResourceBundle.getBundle("mainLoadedInStatic", locale);
//only uncomment to check, that it would work if loaded in static
// ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
}
#Override
public void loadResourceBundle(Locale locale) {
ResourceBundle mainLoadedInStatic = ResourceBundle.getBundle("mainLoadedInStatic", locale);
ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
}
}
The error should be:
Exception in thread "main" java.util.MissingResourceException: Can't find bundle for base name mainNotLoadedInStatic, locale en
at java.base/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2045)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1683)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1586)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1549)
at java.base/java.util.ResourceBundle.getBundle(ResourceBundle.java:932)
at plugin.Main.loadResourceBundle(Main.java:19)
at main.Main.main(Main.java:18)
I discovered that closing the URLClassLoader (as autocloseable) in loadPlugin of PluginManagerImpl was causing the Problem.
The Resources are tried to be loaded with that URLClassLoader and if it is closed, it will fail.
Which effect would occur, if the URLClassLoader doesn't get closed at all? As far as i understand this could have a negativ effect because of an unclosed JarFile.

User URLClassLoader to load jar file "on the fly"

Ok, basically, I try to use the method described here JarFileLoader to load a jar containing a class that will be used the same as if it was on the classpath (the class name will be dynamic so that we can just add any jar with any class and the program will load it through parsing a text file, in the main line).
Problem is that when I debug and check the URLClassLoader object
protected Class<?> findClass(final String name)
Line :
Resource res = ucp.getResource(path, false);
the getResource() does not find the class name in parameter.
Does someone already try loading a jar file this way ?
Thanks.
Loader :
public class JarFileLoader extends URLClassLoader {
public JarFileLoader() {
super(new URL[] {});
}
public JarFileLoader withFile(String jarFile) {
return withFile(new File(jarFile));
}
public JarFileLoader withFile(File jarFile) {
try {
if (jarFile.exists())
addURL(new URL("file://" + jarFile.getAbsolutePath() + "!/"));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return this;
}
public JarFileLoader withLibDir(String path) {
Stream.of(new File(path).listFiles(f -> f.getName().endsWith(".jar"))).forEach(this::withFile);
return this;
}
}
Main :
public static void main(String[] args) {
new Initializer();
JarFileLoader cl = new JarFileLoader();
cl = cl.withFile(new File("libs/dpr-common.jar"));
try {
cl.loadClass("com.*****.atm.dpr.common.util.DPRConfigurationLoader");
System.out.println("Success!");
} catch (ClassNotFoundException e) {
System.out.println("Failed.");
e.printStackTrace();
} finally {
try {
cl.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here the test class I used. When I debug URLClassLoader I can see in the third loop the path of the jar file(loop on the classpath and the URL you add here), but still does not find ressource (and cannot debug the class URLClassPath so do not know what getRessource does exactly).
Ok I take the answer from this question : How to load all the jars from a directory dynamically?
And changing the URL part at the beginning with the way it is done in the long part it works.
So an example could be :
String path = "libs/dpr-common.jar";
if (new File(path).exists()) {
URL myJarFile = new File(path).toURI().toURL();
URL[] urls = { myJarFile };
URLClassLoader child = new URLClassLoader(urls);
Class DPRConfLoad = Class.forName("com.thales.atm.dpr.common.util.DPRConfigurationLoader", true, child);
Method method = DPRConfLoad.getDeclaredMethod("getInstance");
final Object dprConf = method.invoke(DPRConfLoad);
}
All my time wasted in search while it was the example which was wrong... Still does not understand why they use a stupid URL like "jar:file..." etc.
Thanks everyone.

How can i do to get all class of a given package with guava

I am working on a maven project and I added to my pom.xml file the guava dependency and the dependency of an other project. I want to get all class of a given package of that project which I have added to my pom as a dependency.
So I tried with this :
public void getClassOfPackage() {
final ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
try {
for (final ClassPath.ClassInfo info : ClassPath.from(loader)
.getTopLevelClasses()) {
System.out.println(info.getSimpleName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TestMain().getClassOfPackage();
}
And I know that is wrong because it gives that in console:
HierarchicalConfigurationConverter
HierarchicalConfigurationXMLReader
HierarchicalINIConfiguration
SubsetConfiguration
SystemConfiguration...
I don't know where I can specify the project where I am talking about and how I can just pass the package name and it gives me all class which it contains.
I can show all the classes whitch are in the same project but for an other project added as a dependency in pom.xml not yet :
The solution for what we i have in the same project:
public void getClassOfPackage() {
final ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
try {
ClassPath classpath = ClassPath.from(loader); // scans the class path used by classloader
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClasses("org.mypackage")) {
System.out.println(classInfo.getSimpleName()+" <==> "+classInfo.getPackageName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TestMain().getClassOfPackage();
}
I found tow solutions :
With guava :
public void getClassOfPackage(String packagenom) {
final ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
try {
ClassPath classpath = ClassPath.from(loader); // scans the class path used by classloader
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClasses(packagenom)) {
if(!classInfo.getSimpleName().endsWith("_")){
System.out.println(classInfo.getSimpleName());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TestMain().getClassOfPackage("org.myproject");
}
With Java.util:
public List<Class> getClassOfPackage(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes;
}
private static List<Class> findClasses(File directory, String packageName)
throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file,
packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName
+ '.'
+ file.getName().substring(0,
file.getName().length() - 6)));
}
}
return classes;
}
public static void main(String[] args) throws ClassNotFoundException,
IOException {
for (Class className : new ClassOfPackage()
.getClassOfPackage("org.mypackage")) {
if (!className.getSimpleName().endsWith("_")) {
System.out.println(className.getSimpleName());
}
}
}
This is why you're seeing it print out sun.misc.Launcher$AppClassLoader:
} else {
System.out.println(loader.getClass().getName());
}
Every class on your classpath that isn't in your package is causing your program to print out the name of the ClassLoader implementation class, which you probably don't want.
Also, while info.getName().startsWith("org.mypackage") should work, it might be preferable to write info.getPackageName().equals("org.mypackage") (or startsWith if you want to include subpackages too).
It is easier to solve this problem with the help of Google reflections.
Please check the following question for the correct answer - note that the checked answer does not provide a working solution, the second one (Aleksander Blomskøld) does!

URLClassLoader keeps throwing ClassNotFoundException. What am I doing wrong?

I'm working on a university java project that requires me to implement a simple plugin architecture. A main application in Project A would have to load plugins from a specified directory located in another project B. After having done some research on the topic (including stackoverflow), I decided to go with URLClassLoader to load the classes which I might then instatiate. Project B references Project A and all plugins extend a common plugin class (might as well be an interface but should not make any difference). This is what I have got so far:
The source for the plugin class I try to load:
package plugin;
import de.uks.student.pluginpattern.model.EditorPlugin;
public class Circle extends EditorPlugin
{
#Override
public void init()
{
}
}
and the code that is supposed to load the class:
public void init(String[] args)
{
editorPane = new EditorPane().withWidth(500).withHeight(500);
toolBar = new ToolBar();
plugins = new EditorPluginSet();
// load plugins
File pluginDir = new File(PLUGIN_PATH);
if (!pluginDir.exists())
{
System.err.println("Plugin path not found!!");
return;
}
String[] plugins = pluginDir.list();
if (plugins == null)
{
System.err.println("Plugin path points to a file!!");
return;
}
for (String pluginString : plugins)
{
for (String argString : args)
{
if (pluginString.contains(argString))
{
System.out.println("Loading plugin from " + pluginString);
File pluginFile = new File(PLUGIN_PATH + "/");
// as getAbsolutePath embeds the relative path ... /../pluginProject ...
// which may not be processed correctly by the OS (Windows at least),
// we correct the path manually (just to be on the safe side)
String absolutePath = pluginFile.getAbsolutePath();
String[] split = absolutePath.split("\\\\");
List<String> splitsimpleList = Arrays.asList(split);
ArrayList<String> splitList = new ArrayList<>(splitsimpleList);
for (int i = 0; i < splitList.size() - 1; ++i)
{
if (splitList.get(i + 1).equals(".."))
{
splitList.remove(i);
splitList.remove(i);
break;
}
}
StringBuilder b = new StringBuilder();
for (String string : splitList)
{
b.append(string);
b.append("/");
}
File pluginFileForRealThisTime = new File(b.toString());
URL pluginURL = null;
try
{
pluginURL = pluginFileForRealThisTime.toURI().toURL();
} catch (MalformedURLException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
URL[] urls = {pluginURL};
ClassLoader parentClassLoader = this.getClass().getClassLoader();
URLClassLoader uLoader = new URLClassLoader(urls, parentClassLoader);
Class<?> pluginClass = null;
try
{
String className = "plugin." + argString;
pluginClass = uLoader.loadClass(className);
} catch (ClassNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Now I always end up with a ClassNotFoundException:
java.lang.ClassNotFoundException: plugin.Circle at
java.net.URLClassLoader$1.run(Unknown Source) at
java.net.URLClassLoader$1.run(Unknown Source) at
java.security.AccessController.doPrivileged(Native Method) at
java.net.URLClassLoader.findClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
de.uks.student.pluginpattern.model.EditorSystem.init(EditorSystem.java:412)
at
de.uks.student.pluginpattern.EditorSystem.main(EditorSystem.java:13)
I've already checked that
the generated URL can be correctly resolved by any web browser and Windows Explorer.
Circle.class has been built and is present in the directory referenced by the used URL
className resolves to "plugin.Circle" which should be the correct binary name to use
removing inheritance from plugin.Circle does not make any difference.
I've pretty much run out of ideas on what else to try. What am I doing wrong?
I had to point the ClassLoader to the bin folder instead of the bin/plugins folder

ClassCastException when creating an instance of a class using reflection and ClassLoaders

First of all, this is Java 1.4 (project restrictions).
I'm trying to create a application manager.
It loads each application's main class using it's own instance of a custom classloader.
After that, it creates an instance of the main class using reflection.
Each application implements a common interface so after the instance is created, it runs a predefined method of the application.
However, I'm having some trouble at CRASH POINT 1 (see code). The class is not recognized as one implementation of it's interface.
If I coment this code chunk, I get ClassCastException at CRASH POINT 2.
I suppose both errors are related to the same issue (of course).
Can anyone help me?
The relevant part of the code follows (imports are removed)...
Thanks very much.
Marcus
// AppManager.java
public class AppManager {
public ThreadGroup threadGroup;
private Class appClass;
private AppInstance appInst;
public AppContextImpl context;
private AppManager(CustomClassLoader cl, String mainClass) throws ClassNotFoundException {
final String className = mainClass;
final CustomClassLoader finalLoader = cl;
appClass = cl.loadClass(mainClass);
// DEBUG CODE:
Class[] k1 = AppInstance.class.getInterfaces();
System.out.println(k1.length + " interfaces for AppInstance.class:");
for (int ii = 0; ii < k1.length; ii++) {
System.out.println(" " + ii + " - " + k1[ii].getName() + " (" + k1[ii].getClassLoader() + ")");
}
Class[] k2 = appClass.getInterfaces();
System.out.println(k2.length + " interfaces for appClass instance:");
for (int ii = 0; ii < k2.length; ii++) {
System.out.println(" " + ii + " - " + k2[ii].getName() + " (" + k2[ii].getClassLoader() + ")");
}
// CRASH POINT 1
if (!(AppInstance.class.isAssignableFrom(appClass))) {
throw new IllegalArgumentException("Attempt to run a non-AppInstance class: " + appClass);
}
context = new AppContextImpl(mainClass, this);
cl.setAppManager(this);
Constructor m;
try {
m = appClass.getConstructor(new Class[0]);
// CRASH POINT 2
appInst = (AppInstance) m.newInstance(new Object[0]);
appInst.init(context);
} catch (Exception e) {
System.out.println("Got ClassCastException here!\n");
e.printStackTrace();
}
}
public static void main(String[] args) {
App app1;
String path1 = "/home/user/workspace/MultiTaskTest/bin/";
String app1Name = "App1";
Vector v1 = new Vector();
try {
v1.add(new URL(path1));
} catch (MalformedURLException e1) {
final File file1 = new File(path1);
try {
URL path1aux = (URL) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws IOException {
if (!file1.exists()) {
System.out.println("Warning: \"" + file1.getPath() + "\" not found");
return null;
}
return file1.toURI().toURL();
}
});
if (path1aux != null) {
v1.add(path1aux);
}
} catch (PrivilegedActionException e) {
e.getException().printStackTrace();
}
}
final URL[] array1 = (URL[]) v1.toArray(new URL[v1.size()]);
CustomClassLoader cl1 = (CustomClassLoader) AccessController.doPrivileged(
new PrivilegedAction() { public Object run() {
return new CustomClassLoader(array1);
}});
System.out.println("ClassLoader 1 created: " + cl1);
try {
app1 = new App(cl1, app1Name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("Cannot find class App1.");
}
}
}
// AppInstance.java
public interface AppInstance {
public void init(ContextImpl context);
}
// App1.java
public class App1 implements AppInstance {
private AppContextImpl contextObj;
public void init(AppContextImpl context) {
this.contextObj = context;
System.out.println("Running App1...");
}
}
// AppContextImpl.java
public class AppContextImpl {
public String mainClass;
public AppManager app;
public AppContextImpl(String mainClass, AppManager app) {
this.mainClass = mainClass;
this.app = app;
}
}
// CustomClassLoader.java
public class CustomClassLoader extends URLClassLoader {
AppManager appInst;
public CustomClassLoader(URL[] paths) { super(paths, null); }
public void setAppManager(AppManager app) { this.appInst = app; }
}
The output for the Debug code in the AppManager.java file is:
0 interfaces for AppInstance.class:
1 interfaces for appClass instance:
0 - AppInstance (CustomClassLoader#480457)
Your AppInstance class is probably loaded separately by each custom classloader. Since class objects depend on the actual class AND on the classloader, they are really different classes.
So AppInstance from classloader 1 is not the same as AppInstance from classloader 2.
What you need to do is using the standard classloader hierarchy: use a root classloader for your application, and male sure that AppInstance is loadable by the classloader. Then make your custom classloader children from the root. Whenever they need to access the AppInstance class, they will use what is loaded from the root.
So, instead of this:
public CustomClassLoader(URL[] paths) { super(paths, null); }
You need to give a parent to your CustomClassLoader

Categories