Skip to content

Java 9+ Dynamic Classpath #162

@viferga

Description

@viferga

As we talked today @ketangupta34 , I was going to do a simple PoC for supporting dynamic classpath for Java 9+ (and previous) versions. The final implementation for Java 9+ is very simple and fits perfectly our strategy. We won't be able to import the classes directly from JNI as we talked before, but the complexity will be less than before anyway.

import java.io.IOException;
import java.io.File;
import java.net.URLClassLoader;
import java.net.URL;
import java.lang.reflect.Method;
import java.nio.file.Paths;

class poc {
	private static final Class[] parameters = new Class[]{URL.class};

	private static void addClasspathBefore9(URL url) throws IOException {
		URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); // This fails after (included) Java 9 due to a change of the base class
		Class sysclass = URLClassLoader.class;

		try {
			Method method = sysclass.getDeclaredMethod("addURL", parameters);
			method.setAccessible(true);
			method.invoke(sysloader, new Object[]{url});
			String classpath = System.getProperty("java.class.path");
			classpath = classpath + System.getProperty("path.separator") + url.toString();
			System.setProperty("java.class.path", classpath);
		} catch (Throwable t) {
			t.printStackTrace();
			throw new IOException("Error, could not add URL to system classloader");
		}
	}

	private static void addClasspathAfter9(URL url) {
		// I could not found an equivalent version to implement this, but instead... check helperClassExistsExtended
		// By the way, in order to make it strictly compilant it would be cool do to something like this when adding a classpath:
		String classpath = System.getProperty("java.class.path");
		classpath = classpath + System.getProperty("path.separator") + url.toString();
		System.setProperty("java.class.path", classpath);

		// In our case, we should just use the set and join it into a string with System.getProperty("path.separator") as a separator 
	}

	private static boolean helperClassExists(String name) {
		try {
			return Class.forName(name) != null ? true : false;
		} catch (Exception e) {
			return false;
		}
	}

	private static boolean helperClassExistsExtended(String name, URL[] urls) {
		try {
			// This method can load dynamically a class adding an extra classpath,
			// we can use this trick in combination of our custom execution_path set
			// so it will be possible to dynamically load classes, specially the ones
			// compiled with metacall_load_from_file (no matter the folder where they are located)
			Class<?> cls = Class.forName(name, true, new URLClassLoader(urls));
			return cls != null ? true : false;
		} catch (Exception e) {
			return false;
		}
	}
	public static void main(String args[]) {
		try {
			System.out.println("version: " + System.getProperty("java.version")); // TODO: Detect correct version and select between methods
			URL url = Paths.get("/home/yeet/java_classpath_poc/random").toUri().toURL();
			System.out.println("url: " + url.toString());
			// addClasspathBefore9(url);
			// addClasspathAfter9(url);
			// System.out.println("yeet: " + helperClassExists("yeet"));
			System.out.println("yeet: " + helperClassExistsExtended("yeet", new URL[] { url }));
		} catch (Exception e) {
			System.out.println("VERY BAD");
			System.out.println(e);
			e.printStackTrace();
		}
	}
}

When implementing the FindClass and NewObjectA for the constructor, or newly created classes (previously loaded from metacall), we can simply use this:

Class<?> cls = Class.forName(name, true, new URLClassLoader(urls));

We should do a call to bootstrap but this will provide us the reference to the correct class and allow us to use NewObjectA from JNI in order to simplify the constructors. Basically, instead of doing FindClass, we will call to a bootstrap method which accepts the string as a parameter and returns the proper class. In this method, urls is an array of strings (converted to urls) which can be get from our Set used in execution_path.

You can convert a folder path to an url like this:

URL url = Paths.get("/home/koyanisqaatsi/java_classpath_poc/random").toUri().toURL();

If you have any doubt, please, feel free to ask. I would recommend to use both versions, pre/post 9 so we can support different versions of JDK. If you feel this is complex, we can implement only the Java 9+ version. It would be cool to check if it works on previous versions too, so we can get rid of addURL reflection magic. I have Java 11 so I can't test. But if we can verify this, we can have a single implementation without checking versions, and make things much more simple.

The class I was loading is this one:

class yeet {
	public static void you() {
        System.out.println("yeettttt");
    }
}

It works.

Once you have tested this and you have everything clear, we can close this issue. I hope this can simplify the implementation a lot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions