Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Classpath not working when compiling class internally?

I'm working on a project where users can store plugins in raw Java code. My application will then take those plugins, compile them, and import them. The classes are based off of an interface that's stored within my jar. However, when I try to run it using a JavaCompiler.CompilationTask it refuses to allow me to add the current jar to the classpath of the compiler. This being the case, when it tries to compile it it acts as though the interface isn't available to be implemented.

Here is the structure of my files:

The main .jar file:

CommandProcessor.java
----------------------------------------------
package plugins;
public interface CommandProcessor {
    public String onCommand(String command);
}

I then have a function for loading the plugins.

http://hastebin.com/jabacopeye.coffee (HasteBin to keep from over-cluttering the question)

Here is an example of one of the user plugins:

public class MyCommand implements plugins.CommandProcessor {
    @Override
    public String onCommand(String command){
            return "this is a test";
    }
}

Whenever the application tries to compile this externally stored .java file, it says that the class "plugins.CommandProcessor" does not exist.

like image 679
Nathan F. Avatar asked Apr 12 '16 21:04

Nathan F.


Video Answer


2 Answers

As you stated that,

Whenever the application tries to compile this externally stored .java file, it says that the class "plugins.CommandProcessor" does not exist.

So Actual problem is

  1. Your jar file structure and java files location is mismatching because you stored .java file externally. In your code, when task.call() is called then URLClassLoader can not load file from "./plugins/WebC/plugins/" location.

So your code can not proceed on following

 if (task.call()) {
     URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./plugins/WebC/plugins/").toURI().toURL()});
     Class<?> loadedClass = classLoader.loadClass(className);
     Object obj = loadedClass.newInstance();
     if (obj instanceof CommandProcessor) {
         CommandProcessor cmd = (CommandProcessor)obj;
         classLoader.close();
         return cmd;
     }else{
        classLoader.close();
     }
 }

So you need to specify jars on the classpath.

JARs on the classpath

The java compiler and run-time can search for classes not only in separate files, but also in JAR' archives. A JAR file can maintain its own directory structure, and Java follows exactly the same rules as for searching in ordinary directories. Specifically,directory name = package name'. Because a JAR is itself a directory, to include a JAR file in the class search path, the path must reference the JAR itself, not merely the directory that contains the JAR. This is a very common error. Suppose I have a JAR myclasses.jar in directory /myclasses. To have the Java compiler look for classes in this jar, we need to specify:

javac -classpath /myclasses/myclasses.jar ...

and not merely the directory myclasses.

Multiple class search directories

In the examples above, we have told javac to search in only one directory at a time. In practice, your class search path will contain numerous directories and JAR archives. The -classpath option to javac and java allows multiple entries to be specified, but notice that the syntax is slightly different for Unix and Windows systems. On Unix, we would do this:

javac -classpath dir1:dir2:dir3 ...

whereas on Windows we have:

javac -classpath dir1;dir2;dir3 ...

The reason for the difference is that Windows uses the colon (:) character as part of a filename, so it can't be used as a filename separator. Naturally the directory separator character is different as well: forward slash (/) for Unix and backslash () for Windows.

Resource Location:

  1. Java ignores classpath
  2. JARs on the classpath and Multiple class search directories
like image 165
SkyWalker Avatar answered Oct 12 '22 23:10

SkyWalker


You should be creating all of your classes using a custom ClassLoader that can handle the resolution you are expecting. You can code in the loading of any external jar file contents as well your required interfaces. The JVM would keep track of any of the classes you define.

The main program would create your, soon to be written, ScriptingClassLoader that would contain resolution scripting source code:

 public class ScriptingClassLoader extends ClassLoader {

   public Class findClass(String name) {
     // you can fixup the desired api interface here
     // as you already know where they are

     byte[] b = loadClassData(name);
     return defineClass(name, b, 0, b.length);
   }

   private byte[] loadClassData(String name) {
     // go find your script source and compile
     // can be local disk, etc. You are only returning
     // the byte code here
   }
}

http://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

like image 44
Ken Brittain Avatar answered Oct 12 '22 23:10

Ken Brittain