Question

So I've got some code - I'd like to compile the string 'temp' then execute it, then change the string, recompile and execute. Problem is that currently it's just executing the first bit of code first.

I expect:

This is in another java file
How about now?

And I get:

This is in another java file
This is in another java file

Full code follows, any help appreciated.

import java.io.IOException;
import java.util.Arrays;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;

public class Another2 {

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

        //First run
        String temp = "public class HelloWorld {\n" + "  public static void main(String args[]) {\n"
                + "    System.out.println(\"First Compiled Class\");\n" + "  }\n" + "}";

        JavaFileObject file = new JavaSourceFromString("HelloWorld", temp);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
        task.call();
        try {
            Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class })
                    .invoke(null, new Object[] { null });
        } catch (Exception e) {
            // handled it
        }

        //second run
        temp = "public class HelloWorld {\n" + "  public static void main(String args[]) {\n"
                + "    System.out.println(\"How About Now?\");\n" + "  }\n" + "}";
        file = new JavaSourceFromString("HelloWorld", temp);
        Iterable<? extends JavaFileObject> compilationUnits2 = Arrays.asList(file);
        task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits2);
        task.call();
        try {
            Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class })
                    .invoke(null, new Object[] { null });
        } catch (Exception e) {
            // handled it
        }
    }

}
Was it helpful?

Solution 2

It turns out that the code I was looking for was:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;

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;

public class AnotherClassLoaderRunner {

    private static final String nameOfDemoClass = "ByeWorld";

    public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException,
            IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        // Start compilation and loading of first class.
        String sourceCode = "public class " + nameOfDemoClass + " {\n" + "  public static void main(String args[]) {\n"
                + "    System.out.println(\"First of our Compiled Class\");\n" + "  }\n" + "}";
        AnotherClassLoader classLoader = new AnotherClassLoader(AnotherClassLoaderRunner.class.getClassLoader(), nameOfDemoClass);
        Class<?> myclass = classLoader.loadClass(nameOfDemoClass,sourceCode);
        Method mymethod = myclass.getDeclaredMethod("main", new Class[] { String[].class });
        mymethod.invoke(null, new Object[] { null });

        // Start compilation and loading of second class.
        sourceCode = sourceCode.replace("First of our Compiled Cla", "Second of our Compiled Cla");
        classLoader = new AnotherClassLoader(AnotherClassLoaderRunner.class.getClassLoader(), nameOfDemoClass);
        myclass = classLoader.loadClass(nameOfDemoClass,sourceCode);
        mymethod = myclass.getDeclaredMethod("main", new Class[] { String[].class });
        mymethod.invoke(null, new Object[] { null });
    }
}

class AnotherClassLoader extends ClassLoader {

    private static final String pathToClassDirectory = "file:/Users/*********/Documents/workspace/Dynamic/src/";
    private String targetName;

    public AnotherClassLoader(ClassLoader parent, String target) {
        super(parent);
        targetName = target;
    }

    public Class<?> loadClass(String name, String compileme) throws ClassNotFoundException {
        if (!targetName.equals(name)) {
            return super.loadClass(name);
        }
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        JavaFileObject file = new JavaSourceFromString(name, compileme);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
        CompilationTask task = compiler.getTask(null, null, new DiagnosticCollector<JavaFileObject>(), null, null,
                compilationUnits);
        task.call();
        // THE CLASSFILE FOR THE SYSTEM EXSITS AT THIS POINT, thought how it
        // chooses the directory escapes me.
        try {
            String url = pathToClassDirectory + targetName + ".class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();
            while (data != -1) {
                buffer.write(data);
                data = input.read();
            }
            input.close();
            byte[] classData = buffer.toByteArray();

            return defineClass(targetName, classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

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

OTHER TIPS

I suspect that the default classloader is caching the class file and your second Class.forName() isn't actually doing anything. You'll need to create your own classloader and use that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top