By Shadowtrot


2014-02-04 05:14:34 8 Comments

(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.

2 comments

@Peter Lawrey 2016-02-11 23:18:34

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();

@MadProgrammer 2014-02-04 05:46:43

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...

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") + ";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!

@Shadowtrot 2014-02-04 08:00:05

Wow I didn't even know about the javax.tools package. Works great and easy to understand code. Now to go find some way to completely screw it up. Thanks for the help and a quick reply!

@Nitin Bansal 2015-11-02 08:42:23

This is brilliant. Worked flawlessly. Thanks a lot...

@MadProgrammer 2015-11-02 10:43:18

@NitinBansal Yeah, that one was a lot fun ;)

@c4k 2017-01-02 15:12:57

I was wondering what can be the security issues if we compile user-input code and how to protect our system against that ? Thanks

@MadProgrammer 2017-01-02 20:03:33

@c4k Since the user has access to the entire API, they could pretty much do what they want, without going to the extent of having your own library implementation, you could sandbox the user and/or execute the code as an unprivileged user

@c4k 2017-01-03 11:15:39

@MadProgrammer thanks for the explanation. To go further I found this paragraph on Janino's website janino-compiler.github.io/janino/#security. It might be worth reading before going deeper on this ;)

@user840718 2017-02-08 14:45:20

I tried to run this code, but I got the following error message: Error on line 2 in file:/C:/Users/User/workspace/MyProject/testcompile/HelloWor‌​ld.java

@MadProgrammer 2017-02-08 18:53:07

What does the error say? What does line 2 say?

@Shineed Basheer 2017-03-21 07:49:24

@user840718 Please check your package name. I also had same but solved by changing my package name inlinecompiler

@user1870400 2018-10-18 08:02:46

How do I just get the bytecode at runtime after successful compilation at runtime?

@MadProgrammer 2018-10-18 08:04:06

@user1870400 You should be able to just either load the class through a class loader or read as a file off the file system depending on what you want to achieve

@user1870400 2018-10-18 08:12:18

@MadProgrammer I am not sure if my question was clear enough. Let me try one more time. I understand ToolProvider.getSystemJavaCompiler() is used to compile classes at runtime so my question is how to compile at runtime and get the bytecode at runtime?

@MadProgrammer 2018-10-18 08:18:00

@user1870400 It depends. The example above both compiles and loads the class via a custom class loader, so it can then instantiated. It is unclear what you mean by "byte code" - the example above also saves the compilation to disk, so you could just read like any other file

@user1870400 2018-10-18 08:27:49

@MadProgrammer Can I capture the compilation into byte[] or string instead of a disk so I can say print the Java bytecode to stdout (although I understand I can just read from the file)?

@MadProgrammer 2018-10-18 08:54:56

@user1870400 Based on my understanding, the JavaFileManager is the one managing that side of things. If it's really important, you could take a closer look and see if you could implement your own to maintain a memory copy of the compilation instead of writing it to disk

@ShiraOzeri 2018-12-13 13:51:23

@MadProgrammer I get this error: Exception in thread "main" java.lang.NullPointerException at InlineCompiler.main(InlineCompiler.java:47). and the line 47 is: StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);

@MadProgrammer 2018-12-14 02:37:10

@ShiraOzeri I don't have any issues with the example presented

Related Questions

Sponsored Content

41 Answered Questions

[SOLVED] How do I efficiently iterate over each entry in a Java Map?

43 Answered Questions

[SOLVED] How do I convert a String to an int in Java?

28 Answered Questions

[SOLVED] How do I determine whether an array contains a particular value in Java?

  • 2009-07-15 00:03:21
  • Mike Sickler
  • 1884689 View
  • 2184 Score
  • 28 Answer
  • Tags:   java arrays

65 Answered Questions

[SOLVED] How do I generate random integers within a specific range in Java?

  • 2008-12-12 18:20:57
  • user42155
  • 3903299 View
  • 3361 Score
  • 65 Answer
  • Tags:   java random integer

15 Answered Questions

[SOLVED] Efficiency of Java "Double Brace Initialization"?

24 Answered Questions

[SOLVED] How do I declare and initialize an array in Java?

  • 2009-07-29 14:22:27
  • bestattendance
  • 4323821 View
  • 1940 Score
  • 24 Answer
  • Tags:   java arrays declare

57 Answered Questions

[SOLVED] How do I read / convert an InputStream into a String in Java?

21 Answered Questions

[SOLVED] How do I call one constructor from another in Java?

  • 2008-11-12 20:10:19
  • ashokgelal
  • 806493 View
  • 2134 Score
  • 21 Answer
  • Tags:   java constructor

25 Answered Questions

[SOLVED] Java inner class and static nested class

19 Answered Questions

[SOLVED] How do I "decompile" Java class files?

  • 2008-11-07 16:00:03
  • Kip
  • 736532 View
  • 556 Score
  • 19 Answer
  • Tags:   java decompiler

Sponsored Content