Cannot load custom class with URLClassLoader in JavaFX JavaScript - java

I'm trying to get my custom class loaded in FXML JS.
First of all I've added URLClassLoader with my JAR to the FXMLLoader instance:
FXMLLoader loader = new FXMLLoader();
loader.setController(TabController.this);
URLClassLoader fxmlClassLoader = (URLClassLoader) loader.getClassLoader();
loader.setClassLoader(URLClassLoader.newInstance((URL[]) ArrayUtils.addAll(new URL[]{ new File("/home/sk_/projects/mjolnirr/.hive/static/calculator/origJar.jar").toURI().toURL() }, fxmlClassLoader.getURLs())));
And then in FXML JavaScript:
importClass(com.mjolnirr.sample.SomeTestClass);
It fails with error:
sun.org.mozilla.javascript.internal.EvaluatorException: Function importClass must be called with a class; had "[JavaPackage com.mjolnirr.sample.SomeTestClass]" instead. (<Unknown source>#2) in <Unknown source> at line number 2
Anyone faced that?

Okay, I've found one hack solution there. In short - I've just added my URL to the system classloader dynamically, like this:
public static void addURLToSystemClassLoader(URL url) throws IntrospectionException {
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> classLoaderClass = URLClassLoader.class;
try {
Method method = classLoaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(systemClassLoader, new Object[]{url});
} catch (Throwable t) {
t.printStackTrace();
throw new IntrospectionException("Error when adding url to system ClassLoader ");
}
}
And then in my render method
FXMLLoader loader = new FXMLLoader();
loader.setController(TabController.this);
try {
addURLToSystemClassLoader(new URL("hive://" + pageURL.getHost() + ":" + pageURL.getPort() + "/" + pageURL.getApplicationName() + "/origJar.jar"));
} catch (Exception e) {
e.printStackTrace();
}
But it looks like a bad practise.
I think that the original problem is that - JavaFX scripting engine has differect class loader, FXML loader doesn't pass it's own to the script engine. Anyone knows how to set the classloader for the script engine?

How about explicitly setting the class loaded for the current thread to the new class loader?
For example:
URL[] urlsForJarFiles = getUrls();
ClassLoader myClassLoader = new URLClassLoader(urlsForJarFiles, ClassLoader.getSystemClassLoader();
Thread.currentThread.setContextClassLoader(myClassLoader);

Related

Load an external library inside a jar

I have a jar file that I load dynamically,
inside it there is in lib/ another jar (external library) that I use in main (import it.xxx.xx).
How do I load also this external library dynamically in classpath?
My code doesn't work:
public static void runOne(String jar, String class_name, Optional<String> test_name,
TestExecutionListener listener) throws Exception {
Launcher launcher = LauncherFactory.create();
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { new File(pathJars+"/"+jar).toURI().toURL() },
ServiceUtil.class.getClassLoader()
);
loader.getClass();
addURL(loader); <--here i want add a jar to classpath!
Class cls=loader.loadClass(class_name);
Constructor constructor = cls.getConstructor();
constructor.newInstance();
LauncherDiscoveryRequest request;
if (test_name.isPresent()) {
Method m = cls.getMethod(test_name.get());
request = LauncherDiscoveryRequestBuilder.request()
.selectors(selectMethod(cls,m))
.build();
}
else{
request = LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(cls))
.build();
}
TestPlan testPlan = launcher.discover(request);
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
//launcher.execute(request);
loader=null;
System.gc();
}
public static void addURL(ClassLoader loader) throws IOException {
URL u=loader.getResource("lib/sem-platform-sdk-1.0-SNAPSHOT.jar");
Class[] parameters = new Class[]{URL.class};
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[]{u});
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
Thanks
This is generally done with a build tool (e.g. maven or gradle). I don't know if you are using one of these. They make life so much easier.
We use Maven with the Apache Shade plugin to do exactly this. Maven has commands to set up the configuration for you automatically, then you add the Shade plugin to the resulting configuration file (pom.xml).
https://maven.apache.org/plugins/maven-shade-plugin/index.html
If I understand your problem correctly, you want the classes loaded from the jar file to be able to access the classes in the nested jar file. You can accomplish this by creating a ClassLoader with one entry for the jar file and another entry for the nested jar file.
Java has a special URL scheme, jar:, for referring to a jar entry directly. (This scheme and syntax is described in the documentation of JarURLConnection.) So you can construct your ClassLoader this way:
URL jarURL = new File(pathJars+"/"+jar).toURI().toURL();
URL semURL = new URL("jar:" + jarURL + "!/"
+ "lib/sem-platform-sdk-1.0-SNAPSHOT.jar");
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { jarURL, semURL },
ServiceUtil.class.getClassLoader()
);

Dynamically loading and instantiating a .class that is not in the classpath

I have two java projects MASTER and PLUGIN. PLUGIN has dependencies to MASTER and its intent is to extend a class found in MASTER, called SCRIPT.
Once I have declared a SCRIPT (myScript), I want to move the .class file to a folder that MASTER can access. I want MASTER to dynamically load and instantiate that class as a SCRIPT.
I've looked for quite a bit and tried different solutions, but I always get a ClassNotFoundException exception.
I would prefer to do this without passing arguments to the JVM at startup.
Is it even possible? This is my current solution: "currentPath" is "etc/etc/myScript.class
try {
OUT.ln("initiating script " + currentPath);
File file = new File(currentPath);
File parent = file.getParentFile();
String name = file.getName().split(".class")[0];
// Convert File to a URL
URL url = parent.toURI().toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
#SuppressWarnings("resource")
ClassLoader cl = new URLClassLoader(urls);
current = (SCRIPT) cl.loadClass("main.script." + name).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script " + currentPath);
}
if the class you want to load is defined within a package like:
main.script.myScript
and you want to load this class from a folder like c:/myclasses,
then you have to put this class to c:/myclasses/main/script/myScript.class
and then instantate the classloader with the basefolder like:
URL[] urls = new URL[]{new URL("file://c:/myclasses")};
ClassLoader cl = new URLClassLoader(urls);
then the class can be loaded by using the qualified class name:
cl.loadClass("main.script.myScript").getDeclaredConstructor().newInstance()
if you want to keep the class at a specific folder without considering the package structure you could do something like this:
public static void main(String[] args) {
try {
File file = new File("etc/etc/myScript.class");
String className = file.getName().split(".class")[0];
String packageName = "main.script.";
byte[] bytes = Files.readAllBytes(Path.of(file.getPath()));
MyClassLoader myClassLoader = new MyClassLoader(Thread.currentThread().getContextClassLoader());
Object o = myClassLoader.getClass(packageName+className, bytes).getDeclaredConstructor().newInstance();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script ");
}
}
public static class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> getClass(String name, byte[] code) {
return defineClass(name, code, 0, code.length);
}
}

How to load class files into a project to create an object

With the following code snippet, I am trying to load some class files into my project to create an object. Unfortunately, it seems that something is missing, because there are no classes loaded. What is it?
private static void myClassloader() throws Exception
{
File file = new File(pathGeneratedClasses);
try
{
#SuppressWarnings("deprecation")
URL url = file.toURL();
URL[] urls = new URL[]{url};
ClassLoader sqlQuery = new URLClassLoader(urls);
Class myClass = sqlQuery.loadClass("de.cimt.jaxb.JaxCodeGen");
}
catch (Exception e)
{
System.out.println(e.toString());
}
}
sqlQuery.loadClass() will be returning the class you requested for, try assigning the returned value to something.
If no class is found then ClassNotFoundException will be thrown.

dalvik.system.PathClassLoader cannot be cast to java.net.URLClassLoader

Android - Adding jar file dynamically to a ClassLoader like:
String strJarfile = "/data/data/com.example.classloader/files/apps/Calc/SumAndSub.jar";
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
try
{
File f = new File(strJarfile);
URL u = f.toURI().toURL();
Class[] parameters = new Class[] { URL.class };
Method method = URLClassLoader.class.getDeclaredMethod("addURL",
parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
}
catch (Exception e)
{
e.printStackTrace();
}
And, error:
Caused by: java.lang.ClassCastException: dalvik.system.PathClassLoader cannot be cast to java.net.URLClassLoader
This code work fine in java-core, but not with android.
What is solution?
Thanks,
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
ClassLoader.getSystemClassLoader()
will return the PathClassLoader as a ClassLoader, so the (URLClassLoader) will cause the exception.
Hey this is a bit late but I was having trouble with this and was able to solve it by creating my own DexClassLoader. Here's an example function that works for me now:
private SomeClass loadSomeClassInJarFile(String pathToJar, String mainClassName, String codeCacheDir) {
SomeClass instanceOfSomeClass = null;
try {
DexClassLoader classLoader = new DexClassLoader(
pathToJar,
codeCacheDir,
null,
mClassLoader);
Class<?> cls = classLoader.loadClass(mainClassName);
Constructor<?> cs = cls.getConstructor();
instanceOfSomeClass = (SomeClass) cs.newInstance();
} catch (Exception ex) {
// This isn't the right class
Log.w(Tag, "Class " + mainClassName + " could not load because " + ex.getMessage());
}
return instanceOfSomeClass;
}
A couple notes about the above:
mClassLoader needs to be the class loader from your Android application and NOT the default class loader. You can get it from your Application class (.getClassLoader()).
codeCacheDir I got from my ApplicationContext (.getCodeCacheDir().getPath()) although it can be null on the more recent versions of Android.
The solution is not to assume that the system class loader is a URLClassLoader. There's nothing anywhere that says it is. If your code ever worked you got lucky.

custom classLoader issue

the problem is next: i took the base classLoader code from here. but my classLoader is specific from a point, that it must be able to load classes from a filesystem(let's take WinOS), so in classLoader must be some setAdditionalPath() method, which sets a path(a directory on a filesystem), from which we'll load class(only *.class, no jars). here is code, which modifies the loader from a link(you can see, that only loadClass is modified), but it doesn't work properly:
public void setAdditionalPath(String dir) {
if(dir == null) {
throw new NullPointerException("");
}
this.Path = dir;
}
public Loader(){
super(Loader.class.getClassLoader());
}
public Class loadClass(String className) throws ClassNotFoundException {
if(Path.length() != 0) {
File file = new File(Path);
try {
// Convert File to an URL
URL url = file.toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
ClassLoader c = cl.getSystemClassLoader();
Class cls = c.loadClass(className);
return cls;
} catch (MalformedURLException e) {
} catch (ClassNotFoundException e) {
}
}
return findClass(Path);
}
I'd grateful if anyone helps :)
You can just use framework provided java.net.URLClassLoader. No need to write your own. It supports loading of classes from directories and JAR files.
Any URL that ends with a '/' is assumed to refer to a directory.
Otherwise, the URL is assumed to refer to a JAR file which will be
opened as needed.
It also supports a parent class loader. If this class loader does not suite your requirements, perhaps you can specify in more detail what you need. And in any case, you can look at the source and derive your own class loader class based on that.
Here is a short working snippet of code that should demostrate how to load a class by name from a URLClassLoader:
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// This URL for a directory will be searched *recursively*
URL classes =
new URL( "file:///D:/code/myCustomClassesAreUnderThisFolder/" );
ClassLoader custom =
new URLClassLoader( new URL[] { classes }, systemClassLoader );
// this class should be loaded from your directory
Class< ? > clazz = custom.loadClass( "my.custom.class.Name" );
// this class will be loaded as well, because you specified the system
// class loader as the parent
Class< ? > clazzString = custom.loadClass( "java.lang.String" );

Categories

Resources