Search code examples
javadynamicjava-compiler-apidynamic-class-loaders

Compiling within code, not getting behavior I'd like.


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

}

Solution

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