java-compiler-api

JavaCompiler API: access functions/variables outside the compiled program while it's running?


Adapting this the following code takes a class and function name, a string of Java code, compiles the code and runs the function.

public class Compile {

    static void compileAndRun(final String className, final String methodName, final String code) {

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

        JavaFileObject file = new JavaSourceFromString(className, code);

        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        JavaCompiler.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 {

                URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() });
                Class.forName("HelloWorld", true, classLoader).getDeclaredMethod(methodName, 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);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
    }
}

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;
    }
}

You might call this using:

    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(\"Hello World !\");");
    out.println("  }");
    out.println("}");
    out.close();

    Compile.compileAndRun("HelloWorld", "main", writer.toString());

Is it possible from inside the compiled program to call external functions and variables? e.g.

class SomeClass {

    int x = 123;

    void myFunc() {

        StringWriter writer = new StringWriter();
        PrintWriter out = new PrintWriter(writer);
        out.println("public class HelloWorld {");
        out.println("  public static void main(String args[]) {");
        out.println("    y = foo(x);");
        out.println("  }");
        out.println("}");
        out.close();

        Compile.compileAndRun("HelloWorld", "main", writer.toString());
    }

    void foo(int x) {
    } 
}

or maybe with the foo and x in another class? Obviously I tried this, but it fails to compile. Is there a way to achieve this?


Solution

  • In the end I was able to use Groovy, which is a superset of the Java language, to achieve my goal, so you can write a script in Java or Groovy. Passing in variables and classes, which can of course contain variables and functions, was straight-forward, e.g.

    class MyClass {
    
        int x = 100;
    
        void myFunc(int r) {
    
            System.out.println("value from inside script = " + r + " !");
        }
    }
    
    void groovy() {
    
        MyClass q = new MyClass();
    
        StringWriter writer = new StringWriter();
        PrintWriter out = new PrintWriter(writer);
    
        out.println("    System.out.println(\"Hello world\" + q.x);");
        out.println("    q.x += 100;");
        out.println("    q.myFunc(123)");
    
        out.close();
    
        // call groovy expressions from Java code
        Binding binding = new Binding();
        binding.setVariable("q", q);
        GroovyShell shell = new GroovyShell(binding);
    
        Object value = shell.evaluate(writer.toString());
    
        System.out.println("new value = " + q.x);
    }