Compiling on the Fly with the Eclipse Compiler

There is this question about how to compile code on the fly using the eclipse compiler. A useful link was posted, but it didn’t deal with cascading compilation, which is something I found out the eclipse compiler can support in the comments here.

So, I thought it would be useful to get a sample of how to do that up.

Download ECJ by starting from this page, clicking on the latest release, then find and download the file ecj-[version].jar. For this, I’m using 4.2.1. Reference this jar in your classpath.

You use the org.eclipse.jdt.internal.compiler.Compiler. Most things for the constructor have defaults available. You just give it a callback for the results in the form of an ICompilerRequestor. The below example uses a simple byte class loader to test the results. To do cascading compilation, you create a subclass of FileSystem, overriding the methods from INameEnvironment.

(There are some \ in there because wordpress has some bug with the code as-is)


package test.eclipse.compiler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.batch.FileSystem;
import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.util.Util;

public class TestCompile {
  static class ByteClassLoader extends ClassLoader {
    private Map classMap;
    public ByteClassLoader(Map classMap) {
      super();
      this.classMap = classMap;
    }
    protected Class findClass(String name) throws ClassNotFoundException {
      byte[] bytes = classMap.get(name);
      if (bytes == null) {
        return super.findClass(name); } else { return defineClass(name, bytes, 0, bytes.length); 
      }
    }
  }

  public static void compile(String code, String filename) {
    ArrayList cp = new ArrayList();
    Util.collectRunningVMBootclasspath(cp);
    INameEnvironment env = new NameEnv(cp.toArray(new FileSystem.Classpath[cp.size()]), null);
    ICompilerRequestor requestor = new ICompilerRequestor() \{
        ClassFile[] cf = result.getClassFiles();
        HashMap classMap = new HashMap();
        classMap.put("Test", cf[0].getBytes());
        ByteClassLoader cl = new ByteClassLoader(classMap);
        try {
          Class c = cl.loadClass("Test");
          Method m = c.getMethod("test");
          m.invoke(null);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
    Compiler compiler = new Compiler(env, DefaultErrorHandlingPolicies.exitAfterAllProblems(), new CompilerOptions(), requestor, new DefaultProblemFactory());
    ICompilationUnit[] units = new ICompilationUnit[] {
      new CompilationUnit(code.toCharArray(), filename, null)
    };
    compiler.compile(units);
  }

  public static void main(String[] args) {
    compile("public class Test \{ public static void test() \{ System.out.println("Hello, world."); }}", "Test.java");
  }
}

Comment on this post

mentics