2013年3月3日日曜日

Scala2.10のReflectionで、class constructorを取得する

まだ書きかけですが、とりあえずさらしておきます。
後日、もう少しバージョンアップします。

やりたい事は、ScalaのReflectionを使ってクラスのコンストラクタを取得して新しいインスタンスを作るです。
case classも、これでパラメータの取得が可能です。

build.sbt
scalaVersion := "2.10.0"

libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.0"
App.scala


import scala.reflect.runtime.universe._
import scala.reflect._

case class Hoge(val name : String)


object App{

  def main(args : Array[String] ) {
    println(newFromParamsMap[Hoge](Map("name" -> "hoge")))
  }
  
  
  def newFromParamsMap[T](params : Map[String,Any])(implicit tt : TypeTag[T],ct : ClassTag[T]) : T = {
    
    val t = typeOf[T]
    
    // Primary constructorの取得
    val primaryConstructor = t.declarations.collectFirst({
      case m : MethodSymbol if m.isPrimaryConstructor => m
    })
    
    if(!primaryConstructor.isDefined){
      throw new Exception(s"Class ${t} doesn't have primary constructor.")
    }
    
    //コンストラクタのパラメーターを取得してくる
    //後日型のチェック機能も入れます。
    val constructorValues = primaryConstructor.get.paramss.map( _params => {
      _params.map( {
        case t : TermSymbol => {
          val p = params(t.name.encoded.trim)
          /*if( ! t.typeSignatur =:= typeOf(p.getClass){
            throw new Exception("Parameter :${t.name.encoded} doesn't match type!)
          }*/
          p
        }
      })
    })
    // 実行のためのMirror取得
    val mirror = tt.mirror
    
    val classMirror = mirror.reflectClass(t.typeSymbol.asClass)
    val constructorMirror = classMirror.reflectConstructor(primaryConstructor.get)
    
    constructorMirror(constructorValues.flatten :_*).asInstanceOf[T]
  }
  
  
}