scala 2.10のreflectionでorg.apache.commons.beanutils.PropertyUtilsっぽいものを作ってみた
Posted on 2012-12-19 by takezoux2自己紹介
こんにちは、takezoux2こと竹下です。
最近はscala勉強会 in 東京で会場係をさせていただいています。
今回はscala advent calendar2012に参加することになったので、
scala2.10から導入されるReflectionの機能を使ってapache commonsのPropertyUtilsのdescribeメソッドっぽいものとその逆の機能を実装してみました。
環境構築
scala reflectionはscala2.10からの機能なので、それ以前のバージョンのscalaしか入ってい無い場合は、サンプルコードはコンパイル/実行できません。また、2012/12/19時点では安定版は出ておらず、検証にはscala2.10.0-RC4を使用しています。
最新のバイナリはscalaのダウンロードページからダウンロードできます。
※sbt0.12.1はscala2.9.2でビルドされているので、scalaVersion := 2.10を設定しても2.10からの新機能を含んだソースはコンパイルエラーになります。
吉田さんからの指摘により修正
sbtでのコンパイル方法
build.sbt
scalaVersion := "2.10.0-RC4"
libraryDependencies <+= scalaVersion{ scalaVersion => "org.scala-lang" % "scala-reflect" % scalaVersion}
のようにlibraryDependenciesにscala-reflectを設定してあげるとコンパイルできます。
今回実装した機能
PropertyUtils#describeは、JavaBeansを渡すとgetter/setterの名前と値を列挙したMapを返してくれるメソッドです。
PropertyUtils api
今回はこのメソッドと同じように、ScalaObjectのvarを列挙したMap[String,String]を返すメソッドと、
その逆のMap[String,String]の値をScalaObjectに代入するメソッドを実装してみたいと思います。(PropertyUtilsはMap
Reflectionの概要
Reflectionを使用するには、下の簡易図の流れでMethodMirrorを取得し、最終的にMethodMirrorのapplyメソッドを呼ぶことで、メソッドを実行できます。
TypeTag#mirror typeOf[ClassName]
| |
Mirror#reflect Instance Type#members
|-----------┘ |
InstanceMirror#reflectMethod MethodSymbol
|------------------------------┘
MethodMirror@apply
コード
説明するより、コード読んだほうが早いと思うのでさくっとコードを書いておきます。
import scala.reflect.runtime.universe._
import scala.reflect._
case class User(var name : String,var gender : Int,var admin_? : Boolean){
def this() = this("",1,false)
}
object App{
def main(args : Array[String]){
val m = toMap(User("Kudo",2,false))
println(m)
//Map(admin_? -> false, gender -> 2, name -> Kudo)
val u = new User()
fromMap(u,Map("name" -> "Riki","gender" -> "1","admin_?" -> "true"))
println(u)
//User(Riki,1,true)
}
def toMap[T](v : T)(implicit tt : TypeTag[T],ct : ClassTag[T]) : Map[String,String]= {
val t = typeOf[T]
// var だけを抽出
val vars = t.members.filter( _ match{
case t : TermSymbol => t.isVar
case _ => false
})
// Reflection実行のためのMirrorを取得
val mirror = tt.mirror // runtimeMirror(getClass.getClassLoader)でもOK
// インスタンス操作のためのReflectionを取得
val rf = mirror.reflect(v)
var fieldValues : Map[String,String] = Map.empty
vars.foreach( v => v match{
case v : TermSymbol => {
// GetterMethodを取得
val getterMethod = rf.reflectMethod(v.getter.asMethod)
fieldValues += ( v.name.decoded.trim -> getterMethod().toString) //nameを取得すると末尾にスペースが入るのでtrimしておく
}
})
fieldValues
}
def fromMap[T]( v : T , map : Map[String,String] )(implicit tt : TypeTag[T],ct : ClassTag[T]) = {
val t = typeOf[T]
// var だけを抽出
val vars = t.members.filter( _ match{
case t : TermSymbol => t.isVar
case _ => false
})
// Reflection実行のためのMirrorを取得
val mirror = tt.mirror // runtimeMirror(getClass.getClassLoader)でもOK
// インスタンス操作のためのReflectionを取得
val rf = mirror.reflect(v)
vars.foreach( v => v match{
case t : TermSymbol => {
map.get(t.name.decoded.trim) match{
case Some(v) =>{
val setterMethod = rf.reflectMethod(t.setter.asMethod)
// 型に合わせて文字列から変換する
t.getter.asMethod.returnType match{
case t if t =:= typeOf[String] => {
setterMethod(v)
}
// Primitive型に当たるものは、JavaUniverse.definitionsに定義されている
case definitions.IntTpe => {
setterMethod(v.toInt)
}
case definitions.BooleanTpe => {
setterMethod(v.toBoolean)
}
// 他の型は今回は割愛
case t => println("Unknown type " + t)
}
}
case None => println("Value not found:" + t.name.decoded)
}
}
})
v
}
}
ソースコードはGistにも公開しています。
説明は、コード中に書いているので特段解説は不要かと思います。
感想
javaのreflectionを使っていたときは、scalaのpropertyの列挙に一苦労していましたが、scala reflectionでは簡単に列挙できます。他にも、implicitやscalaのEnumerationなどjavaのreflectionでは扱えなかった情報も扱うことができるようになるため、夢がひろがりんぐです。