読者です 読者をやめる 読者になる 読者になる

Java Compiler API を使って何かしてみよう

Java

Java のバイナリから Java ソースコードコンパイルするための API を使って、String 型の文字列をコンパイルし、クラスから static なメソッドを呼んで値を返す方法を模索していました。


以下のプログラムと解説を参考にしています

  1. Compiling from Memory : Java Compiler tools « JDK 6 « Java
  2. Javaリフレクションメモ(Hishidama's Java Reflection Memo)
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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;

public class CompileSourceInMemory {

  public static void main(String args[]) throws IOException {

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

    String src =
      "public class HelloWorld {" +
      "public static int main(String args[]) {" +
      "System.out.println(\"This is in another java file\");" +
      "return(1);" +
      "}" +
      "}";

    JavaFileObject file = new JavaSourceFromString("HelloWorld", src);

    String[] compileOptions = new String[] {"-d", "bin"} ;
    Iterable<String> compilationOptionss = Arrays.asList(compileOptions);

    Iterable <? extends JavaFileObject > compilationUnits = Arrays.asList(file);
    CompilationTask task = compiler.getTask(
                             null,
                             null,
                             diagnostics,
                             compilationOptionss,
                             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) {
      Object ret;
      try {
        Class<?> clazz = Class.forName("HelloWorld");
        Method method = clazz.getMethod("main", new Class[] { String[].class });
        ret = method.invoke(null, new Object[] { null });
        System.out.println(ret);
      } 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;
  }

}

ポイント

私の環境では Eclipse 上で実行しているので、

String[] compileOptions = new String[] {"-d", "bin"} ;

を入れています。


もう一つは、

Object ret;
try {
  Class<?> clazz = Class.forName("HelloWorld");
  Method method = clazz.getMethod("main", new Class[] { String[].class });
  ret = method.invoke(null, new Object[] { null });
  System.out.println(ret);
}

という感じにリフレクションを使って、返り値がある場合に拡張してみました。
とりあえず Eclipse 上では実行できた。ので、jar ファイルなどにしても動けばいいのですが・・・

2012/06/25 追記

実行可能 jar ファイル化して動かなかった原因は、jdk ではないと動かない (tools.jar 必須) のに、jre から動いていたため・・・
最新 jdk 以外を全てアンインストール / jar ファイルの関連づけを変更したら普通に動きました。
とりあえず、

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
  System.out.println("null dayo!");
}

で、compiler を取ってこれているかどうかはチェックはしておいた方が良いでしょう。
null の場合は tools.jar が読み込めていないので、jdk から実行できていそうかなどをちゃんとチェックしておくとよいみたいです。

2012/06/26 リフレクションメモ

複数引数のリフレクションサンプルを作ってみました。
可変長引数がないので、以下の様に列挙するだけで簡単に使えますね。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class a {
  public static void main(String[] args) {
    Class<?> clazz;
    try {
      clazz = Class.forName("test");
      Object obj = clazz.newInstance();
      Method method =
        obj.getClass().getMethod("fx", double.class, double.class, double[].class);
      System.out.println((Double) method.invoke(null, 2.3, 1.0, new double[] {1.0, 2.0, 1.0}));
      // 7.3 が出力される
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (SecurityException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
  }
}

class test {
  public static double fx(double a, double b, double[] c) {
    double cc = 0;
    for (int i = 0; i < c.length; i++) {
      cc += c[i];
    }
    return a + b + cc;
  }
}