sbt issues with my ClassBuilder implementation (Scala) - java

What is Intellij doing for me that sbt isn't?
I am working on a Scala project for which I'd like to support user-defined plugin classes. I've implemented a subclass of ClassLoader:
class PluginLoader(directory: String) extends ClassLoader {
override def findClass(className: String): Class[_] = {
val bytes = loadClassData(className)
defineClass(className,bytes,0,bytes.length)
}
def loadClassData(className: String): Array[Byte] = {
val fileName = className.replace('.',File.separatorChar) + ".class"
val file = new File(directory,fileName)
val bytes = new Array[Byte](file.length.toInt)
val inputStream = new DataInputStream(new FileInputStream(file))
inputStream.readFully(bytes)
inputStream.close()
bytes
}
}
and I call this with user-defined directory and name of a pre-compiled scala class:
new PluginLoader(directory).loadClass(className).newInstance()
findClass appears to succeed at finding the class the user provides. However, it is then called again looking for scala.runtime.java8.JFunction2$mcIII$sp.class. It looks in the user-provided directory, so it cannot find the loaded class.
Here's the error I get: (run-main-0) java.io.FileNotFoundException: ./scala/runtime/java8/JFunction2$mcIII$sp.class (No such file or directory)
I have successfully run this application in Intellij, which makes me think I am missing some configuration in sbt, and Intellij is catching my mistake for me.
Do I need to do additional configuration? Should I be loading in an entire jar file instead of just passing a class file?

When you compile a class - not only Scala class, Java class as well - then it not necessarily mean that you will get a single .class file.
Internal classes, lambdas/closures, etc will result with additional bytecode, that is required to run, and which doesn't live in the same file as your "original" class.
As far as I can tell here Java created some class because it is the body of some lambda, and you did not loaded it.
Bottom line is: you'd better load everything. IntelliJ might have simply passed a whole classpath to JVM, so your classloader might not have even used your extensions (though you'd have to attach debugger in order to be sure).

Related

How to extract java preview class files from jdk 15?

I am currently trying to extract from the jdk the preview class files such as java.lang.Record from jrt-fs.jar (in libs folder), but it does not find the preview classes such as Record when iterating over it. This is the code I am using:
Path jrtFsJar = jdk15Home.resolve("lib").resolve("jrt-fs.jar");
jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()});
FileSystem jrtFs = FileSystems.newFileSystem(
URI.create("jrt:/"),
Collections.emptyMap(),
jrtFsJarLoader);
Files.walk(jrtFs.getPath("/modules")).forEach(path ->
// Here is walks over classes such as "modules/java.base/java/lang/Object.class"
// but not over "modules/java.base/java/lang/Record.class"
)
I have also tried a more direct approach:
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
// Object works, I get the bytes.
byte[] object = Files.readAllBytes(jrtFs.getPath("modules", "java.base",
"java/lang/Object.class"));
// Record fails, NoSuchFile.
byte[] object = Files.readAllBytes(jrtFs.getPath("modules", "java.base",
"java/lang/Record.class"));
Now when I'm running the exact same jdk with --enable-preview, I can use records.
How do I extract the Record class from the jdk linux/lib? Are the preview class inside of it or should I look for them elsewhere? Do I need a specific flag to access them?
Any help is appreciated.
When you are running on a JDK that has a jrt file system already, you can access the jrt file system of another JDK much easier.
FileSystem jrtFs = FileSystems.newFileSystem(
URI.create("jrt:/"), Map.of("java.home", jdk15Home.toString()));
byte[] object = Files.readAllBytes(
jrtFs.getPath("modules", "java.base", "java/lang/Record.class"));
The built-in file system will create a special class loader for the foreign JDK’s jrt-fs.jar that does not delegate to the parent loader for the classes of this jar. So it does not end up at its own imple­men­tation again.
Since it uses the implementation provided by the other JDK, it will be able to handle newer features or even JDKs using an entirely different data format for its module storage.

Loading .class files via URLClassLoader - NoClassDefFoundError

I'm aware this question has been asked multiple times (such as here), but none of the answers appear to work for me.
This is a homework assignment, I'm supposed to "hack" several class files via the reflection API, but I can't even get them to load.
There are three .class files (Inscription.class, Decoder.class, Safe.class) I put in D:\class\. Then I try to load them via an URLClassLoader:
public void Load() throws MalformedURLException {
ClassLoader loader = this.getClass().getClassLoader();
File classFolder = new File("D://class//");
// classFolder.exists() returns true at this point.
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{classFolder.toURI().toURL()},loader);
// urlClassLoader.classes is empty at this point, which is suspicous
urlClassLoader.loadClass("Safe");
// throws NoClassDefFoundError (wrong name: ea_6_1/Safe)
// Interestingly, it seems to find a package name (ea_6_1)
urlClassLoader.loadClass("ea_6_1.Safe");
// throws ClassNotFoundException
}
I also tried to load the files one by one, but apparently this shouldn't work, since URLClassLoader only accepts directories or JAR files:
URL inscription = loader.getResource("Inscription.class");
URL safe = loader.getResource("Safe.class");
URL decoder = loader.getResource("Decoder.class");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{inscription, safe, decoder});
// Exact same behavior as above.
My classpath configuration looks like this:
Is this a configuration issue or am I using the URLClassLoader wrong? Is there maybe another way of loading class files?
It appears someone has moved .class files around, without preserving the required directory structure.
A Java class declared with package ea_6_1; must reside in a directory named ea_6_1 (in every Java implementation I know of, at least).

Given only the full path to a .class file, how can I load its Class object?

I'm working on an application that needs to be able to do some analysis on Java code; some through the text and some through reflection. The user should be able to input a directory and the program will pull all .java and .class files from it. I have no problem with the .java files but when I try getting the class of the .class file it doesn't work.
I've looked at using ClassLoader with URL. What I've been able to find so far has given me this
URL url = this.openFile().toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass(this.path);
return cls;
path is just a string containing the actual path of the .class file in question, e.g. Users/me/Documents/Application/out/production/MyPackage/MyClass.class. From what I understand from my own reading, this method ties me to knowing the package structure of the input, but in general I don't. All I have is the absolute path of the .class file. Is there a way, just using this path (or some simple transformation of it) that I can load into my program the actual MyClass class object and start doing reflection on it?
You have 2 options:
Use a byte code library to first read the file, so you can find out what the actual class name is.
E.g. in your example it is probably MyPackage.MyClass, but you can't know that from the fully qualified file name.
Implement your own ClassLoader subclass, and call defineClass(byte[] b, int off, int len).
Recommend using option 2.

Know what methods are in .jar files

I know that there is a plugin for ImageJ that handles NIfTI-1 files (http://rsb.info.nih.gov/ij/plugins/nifti.html).
But all its instructions on that page is to use ImageJ as a standalone program, however I am using its API. How can I know what methods are available in this jar file without its source?
I couldn't find the source code either.
For the supported archives in imageJ (such as DICOM) is quite easy :
public class ImageJTest {
public static void main(){
String path = "res/vaca.dcm";
FileInputStream fis;
ImageView image;
try {
fis = new FileInputStream(path);
DICOM d = new DICOM(fis);
d.run(path);
// Stretches the histogram because the pictures were being
// displayed too dark.
(new ij.plugin.ContrastEnhancer()).stretchHistogram(d, 0.1);
Image picture = SwingFXUtils.toFXImage(d.getBufferedImage(),
null);
// Makes the size standard for thumbnails
image = new ImageView(picture);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
How can I load the NIfTI-1 files in imageJ ?
Once you have the class files, which are embedded in the jar file (as #Alvin Thompson pointed out, these are just zip files by a different name), you can use the reflection API to mine the class files to get their methods. A sample follows for one class, cribbed from here:
Method[] methods = thisClass.getClass().getMethods(); // thisClass is an instance of the class you're working with
for(Method method : methods){
System.out.println("method = " + method.getName());
}
JAR files are just fancy ZIP files. You can rename the file to foo.zip, then use any unzip utility to expand its contents. You should be able to at least see what the class files are, and the javadocs may be bundled with it (unlikely these days but possible).
However, if you just want to know what methods are available, probably the best way is to add the JAR to the class path of a project in NetBeans, Eclipse, or IntelliJ and use their code completion features to figure out the API methods and classes.
you can do even a decompile of the jar and see the code that is behind each class using a decompiler.
A good one i found: Java Decompiler
JD-GUI home page: http://java.decompiler.free.fr
It does really good it's job. At least in the tasks i had.

java code to instantiate another java file

I want to write a program to dynamically invoke a method inside another Java class (uncompiled) whose file name with location is given. For this I've used the following code but it wasn't working.
//folder location of my java file to be loaded
String url = "C:/Temp/testcases/test.java";
//name of the java file to be loaded
String classname = "test.java";
this.class.classLoader.rootLoader.addURL(new URL(url+str));
Class.forName(str).newInstance();
The above instance is unable to invoke the method inside the java file that I want to load dynamically. What is the error in it?
The class loader is only able to load compiled classes. It's not able to open Java source files, compile them on the fly, and load their class.
Moreover, a class name is not the same as a file name.
I agree with your answer. The error in the above code is with the new URL(C://...). Had I mentioned the package name wrt path correctly, it should have worked. Anyways I was dealing with groovy files so I found this code more efficient than the normal class.forname that I've mentioned above.
def sourceString = (new File(C:/xyz.groovy)).getText()
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(sourceString);
template = groovyClass.newInstance()

Categories