33

I have found many references explaining how to programmatically compile a Java class using the JavaCompiler class:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, "a_file_name");

However, I would like to know if there is an open source library that let me compile source code generated programmatically (therefore without a src file being involved) and generate some byte code in an output stream (without generating a class file in the file system).

For example, I am looking for being able to write something like this:

InputStream input = generateSourceCode();
OutputStream output = getByteCode(input);
doCoolStuffWithByteCode(output);

Thanks for any help.

1
  • See the SSCCE Text Based Compiler for a demo. of what James & Brian are referring to. The STBC uses JavaCompiler/SimpleJavaFileObject. Commented Nov 3, 2011 at 5:55

3 Answers 3

45

To start, look at the JavaCompiler API. Basically:

  1. Create the Java class in a string.
  2. Put the string into class that extends SimpleJavaFileObject.
  3. Compile using a JavaCompiler instance.

Finally, call the methods the new class.


Here is an example that works with JDK6+:

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Arrays;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;

public class CompileSourceInMemory {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

    StringWriter writer = new StringWriter();
    PrintWriter out = new PrintWriter(writer);
    out.println("public class HelloWorld {");
    out.println("  public static void main(String args[]) {");
    out.println("    System.out.println(\"This is in another java file\");");    
    out.println("  }");
    out.println("}");
    out.close();
    JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());

    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);

    boolean success = task.call();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
      System.out.println(diagnostic.getCode());
      System.out.println(diagnostic.getKind());
      System.out.println(diagnostic.getPosition());
      System.out.println(diagnostic.getStartPosition());
      System.out.println(diagnostic.getEndPosition());
      System.out.println(diagnostic.getSource());
      System.out.println(diagnostic.getMessage(null));

    }
    System.out.println("Success: " + success);

    if (success) {
      try {
        Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class })
            .invoke(null, new Object[] { null });
      } catch (ClassNotFoundException e) {
        System.err.println("Class not found: " + e);
      } catch (NoSuchMethodException e) {
        System.err.println("No such method: " + e);
      } catch (IllegalAccessException e) {
        System.err.println("Illegal access: " + e);
      } catch (InvocationTargetException e) {
        System.err.println("Invocation target: " + e);
      }
    }
  }
}

class JavaSourceFromString extends SimpleJavaFileObject {
  final String code;

  JavaSourceFromString(String name, String code) {
    super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
    this.code = code;
  }

  @Override
  public CharSequence getCharContent(boolean ignoreEncodingErrors) {
    return code;
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

+1 Thanks a lot for the pointers. The last link shows almost what I am looking for. The only difference is that apparently it requires to know the name of the class to be compiled in advance, and the only thing I have is its full source code.
Can you just search the class for the word 'public class' and the next word will be the class name perhaps.
I know I can manually scan the source code, just wondering if something more elegant could be done. Thanks !
Assuming you are generating the classes yourself, just do something like: String name = "example.MyClass"; InputStream input = generateSourceCode(name); OutputStream output = getByteCode(input, name); doCoolStuffWithByteCode(output);
When I run this example code on java 8, I get Class not found: java.lang.ClassNotFoundException: HelloWorld.
|
7

JavaDocs are your friend:

http://download.oracle.com/javase/6/docs/api/javax/tools/JavaCompiler.html

Look at the last section that refers to the SimpleJavaFileObject; it shows you how to use it in conjunction with code that is stored in a String

Comments

0

We gave a talk about this use case in JavaOne 2016 (the question is kind of old, but there seems to be some interest still).

There is a repository with examples of practical code generation using javac in-memory.

Specifically look at SimpleJavaCompiler for an example on how to do this in memory that deals with thread safety (we use it in the context of a server) for a single class. It could easily be adapted for a multi-class scenario.

There are also classes to deal with class loading and code generation (scoping of variables, generating unique names, name shadowing, etc.).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.