先週、ScalaのCompiler APIを使ってclassの定義を実行時にコンパイルしたり、スクリプトを走らせたりしました。
今回は、Scalaのobject構文で宣言されたオブジェクトを扱ってみたいと思います。
ちょっと癖がありましたので。
まずは、今回のスケープゴート。
HelloWorldObject.scala
object HelloWorldObject { println("Hello World Object") def foo(): Unit = { println("Hello Object foo") } }
なんの変哲もない、ただのScala Objectです。メソッドはfooのみ宣言してあります。まずは、これを普通にコンパイルしてjavapしてみます。
$ fsc HelloWorldObject.scala $ javap HelloWorldObject\$ Compiled from "HelloWorldObject.scala" public final class HelloWorldObject$ extends java.lang.Object implements scala.ScalaObject{ public static final HelloWorldObject$ MODULE$; public static {}; public void foo(); }
結果を見ればわかるように、メソッドfooはstaticメソッドではありません。さらに、クラス名は「HelloWorldObject」ではなく、「HelloWorldObject$」であることがポイントです。
これを踏まえると、このクラス…というかオブジェクトを動的にコンパイルして操作するためのコードは、先週のサンプルを引用して考えると、以下のようになります。
object CompileObjectSourceRunner { def main(args: Array[String]): Unit = { args.headOption match { case None => println("""Required One or more Arguments | Usage: [SourcePath]:[ClassName]*""".stripMargin) sys.exit(1) case _ => val compiler = new DynamicCompiler args foreach { pair => val (path, className) = (pair.split(":")(0), pair.split(":")(1)) compiler.compileClassFromFile(path, className + "$") match { case Some(c) => println("Compile Success[%s]".format(c.getName)) try { val module = c.getDeclaredField("MODULE$") c.getDeclaredMethod("foo").invoke(module.get(null)) } catch { case e => e.printStackTrace() } case None => println("Compile Fail") } } } } }
ポイントは、ここと
compiler.compileClassFromFile(path, className + "$") match {
ここ。
val module = c.getDeclaredField("MODULE$") c.getDeclaredMethod("foo").invoke(module.get(null))
クラス名を指定するのに、「$」を入れさせるのはあんまりな感じがしたので、解釈側で補うようにしています。そして、フィールドMODULE$を取得して、これ経由でメソッドを呼び出すことになります。
実行結果は、以下の通り。
> run-main CompileObjectSourceRunner ../HelloWorldObject.scala:HelloWorldObject [info] Compiling 1 Scala source to /xxxxx/dynamic-compiler/target/scala-2.9.0.final/classes... [info] Running CompileObjectSourceRunner ../HelloWorldObject.scala:HelloWorldObject Compile Success[HelloWorldObject$] Hello World Object Hello Object foo [success] Total time: 34 s, completed Aug 14, 2011 5:13:55 PM
汎用クラスのようで、今回の起動プログラムは引数無しのfooメソッドを持ったクラスにしか適用できないのですが、まあサンプルということで…。