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を返しますが、型の判定を実装するため今回はMap[String,String]を返すようにしています。)

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では扱えなかった情報も扱うことができるようになるため、夢がひろがりんぐです。

参考にした資料


Tags

Scala