Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you dynamically compile and load external java classes? [duplicate]

(This question is similar to many questions I have seen but most are not specific enough for what I am doing)

Background:

The purpose of my program is to make it easy for people who use my program to make custom "plugins" so to speak, then compile and load them into the program for use (vs having an incomplete, slow parser implemented in my program). My program allows users to input code into a predefined class extending a compiled class packaged with my program. They input the code into text panes then my program copies the code into the methods being overridden. It then saves this as a .java file (nearly) ready for the compiler. The program runs javac (java compiler) with the saved .java file as its input.

My question is, how do I get it so that the client can (using my compiled program) save this java file (which extends my InterfaceExample) anywhere on their computer, have my program compile it (without saying "cannot find symbol: InterfaceExample") then load it and call the doSomething() method?

I keep seeing Q&A's using reflection or ClassLoader and one that almost described how to compile it, but none are detailed enough for me/I do not understand them completely.

like image 661
Shadowtrot Avatar asked Feb 04 '14 05:02

Shadowtrot


People also ask

What is the extension of compiled Java classes * 2 points?

1 Answer. The correct answer to the question “What is the extension of compiled Java classes” is, option (B). (. class).

What is dynamic compilation in Java?

Dynamic compilation is a process used by some programming language implementations to gain performance during program execution. Although the technique originated in Smalltalk, the best-known language that uses this technique is Java.


2 Answers

Take a look at JavaCompiler

The following is based on the example given in the JavaDocs

This will save a File in the testcompile directory (based on the package name requirements) and the compile the File to a Java class...

package inlinecompiler;  import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider;  public class InlineCompiler {      public static void main(String[] args) {         StringBuilder sb = new StringBuilder(64);         sb.append("package testcompile;\n");         sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");         sb.append("    public void doStuff() {\n");         sb.append("        System.out.println(\"Hello world\");\n");         sb.append("    }\n");         sb.append("}\n");          File helloWorldJava = new File("testcompile/HelloWorld.java");         if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {              try {                 Writer writer = null;                 try {                     writer = new FileWriter(helloWorldJava);                     writer.write(sb.toString());                     writer.flush();                 } finally {                     try {                         writer.close();                     } catch (Exception e) {                     }                 }                  /** Compilation Requirements *********************************************************************************************/                 DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();                 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();                 StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);                  // This sets up the class path that the compiler will use.                 // I've added the .jar file that contains the DoStuff interface within in it...                 List<String> optionList = new ArrayList<String>();                 optionList.add("-classpath");                 optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");                  Iterable<? extends JavaFileObject> compilationUnit                         = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));                 JavaCompiler.CompilationTask task = compiler.getTask(                     null,                      fileManager,                      diagnostics,                      optionList,                      null,                      compilationUnit);                 /********************************************************************************************* Compilation Requirements **/                 if (task.call()) {                     /** Load and execute *************************************************************************************************/                     System.out.println("Yipe");                     // Create a new custom class loader, pointing to the directory that contains the compiled                     // classes, this should point to the top of the package structure!                     URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});                     // Load the class from the classloader by name....                     Class<?> loadedClass = classLoader.loadClass("testcompile.HelloWorld");                     // Create a new instance...                     Object obj = loadedClass.newInstance();                     // Santity check                     if (obj instanceof DoStuff) {                         // Cast to the DoStuff interface                         DoStuff stuffToDo = (DoStuff)obj;                         // Run it baby                         stuffToDo.doStuff();                     }                     /************************************************************************************************* Load and execute **/                 } else {                     for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {                         System.out.format("Error on line %d in %s%n",                                 diagnostic.getLineNumber(),                                 diagnostic.getSource().toUri());                     }                 }                 fileManager.close();             } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {                 exp.printStackTrace();             }         }     }      public static interface DoStuff {          public void doStuff();     }  } 

Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!

like image 151
MadProgrammer Avatar answered Oct 06 '22 01:10

MadProgrammer


I suggest using the Java Runtime Compiler library. You can give it a String in memory and it will compile and load the class into the current class loader (or one of your choice) and return the Class loaded. Nested classes are also loaded. Note: this works entirely in memory by default.

e.g.

 // dynamically you can call  String className = "mypackage.MyClass";  String javaCode = "package mypackage;\n" +                   "public class MyClass implements Runnable {\n" +                   "    public void run() {\n" +                   "        System.out.println(\"Hello World\");\n" +                   "    }\n" +                   "}\n";  Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode);  Runnable runner = (Runnable) aClass.newInstance();  runner.run(); 
like image 36
Peter Lawrey Avatar answered Oct 06 '22 01:10

Peter Lawrey