Skip to content

Instantly share code, notes, and snippets.

@takezoux2
Created September 18, 2013 07:24
Show Gist options
  • Save takezoux2/6605678 to your computer and use it in GitHub Desktop.
Save takezoux2/6605678 to your computer and use it in GitHub Desktop.
Scala の case classのコンストラクターを取得して、デフォルト値とかをごにょごにょするサンプルです。 CaseClassExtractorが、汎用クラスで SimpleMapExtractorが、Map[String,Any]をcase classに変換しているサンプルです。
import scala.reflect.runtime.universe._
import scala.reflect._
/**
*
* User: takeshita
* DateTime: 13/09/18 11:39
*/
trait CaseClassExtractor[ExtractTarget] {
lazy val fieldNamesForName = Set("name","id")
def extract[T](from : ExtractTarget,nameCandidate : Option[String])(implicit m : Manifest[T]) : T = {
extract(m,from,nameCandidate).asInstanceOf[T]
}
def extract(m : Manifest[_],from : ExtractTarget,nameCandidate : Option[String]) : Any = {
val mirror = runtimeMirror(getClass.getClassLoader)
val typeTag = manifestToTypeTag(mirror,m).asInstanceOf[TypeTag[_]]
val t = typeOf(typeTag)
extract(t,from,nameCandidate)
}
def extract(t : Type, from : ExtractTarget,nameCandidate : Option[String]) : Any = {
val mirror = runtimeMirror(getClass.getClassLoader)
val template = templates.getOrElse(t,{
val tem = createTemplate(t)
templates = templates + (t -> tem)
tem
})
val consParams = template.constructorParams.map( f => {
getValue(from,f.name,f.paramType) match{
case Some(v) => {
v
}
case None => {
if (nameCandidate.isDefined &&
fieldNamesForName.contains(f.name) &&
f.paramType =:= typeOf[String]){
nameCandidate.get
}else if (f.defaultValue.isDefined){
val v = template.companionObject.get.reflectMethod(f.defaultValue.get)
v()
}else{
throw new Exception(s"${template.classType.name.decoded}@${f.name} has no default value")
}
}
}
})
val classMirror = mirror.reflectClass(template.classType)
val instance = classMirror.reflectConstructor(template.constructor)(consParams:_*)
if(template.varFields.size > 0){
val instanceMirror = mirror.reflect(instance)
template.varFields.foreach( varField => {
getValue(from,varField.name,varField.paramType) match{
case Some(v) => {
instanceMirror.reflectMethod(varField.setter)(v)
}
case None => // nothing
}
})
}
instance
}
def createTemplate(t : Type) = LockObj.synchronized{
val cm = runtimeMirror(getClass.getClassLoader)
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 pc = primaryConstructor.get
var indexOfDefaultValue = 0
val companionObj = pc.owner.companionSymbol.typeSignature
lazy val companionObjIns = {
val moduleMirror = (cm reflectModule (pc.owner.companionSymbol.asModule)).instance
cm reflect moduleMirror
}
val params = pc.paramss.flatten.map({
case p : TermSymbol => {
indexOfDefaultValue += 1
val name = p.name.encoded
val paramType = p.typeSignature
if (p.isParamWithDefault){
val m = companionObj member newTermName(
s"${pc.name.encoded}$$default$$${indexOfDefaultValue}")
FieldInfo(name,paramType,Some(m.asMethod))
}else{
FieldInfo(name,paramType,None)
}
}
})
val vars = t.members.collect({
case t : TermSymbol if t.isVar => {
VarInfo(t.name.decoded.trim,t.typeSignature,t.setter.asMethod)
}
}).toList
Template(
pc.owner.asClass,
primaryConstructor.get,
params,
Some(companionObjIns),
vars
)
}
case class Template(
classType : ClassSymbol,
constructor : MethodSymbol,
constructorParams : List[FieldInfo],
companionObject : Option[InstanceMirror],
varFields : List[VarInfo]
)
case class FieldInfo(
name : String,
paramType : Type,
defaultValue : Option[MethodSymbol]
)
case class VarInfo(
name : String,
paramType : Type,
setter : MethodSymbol
)
private var templates : Map[Type,Template] = Map.empty
def getValue( from : ExtractTarget, key : String, t : Type) : Option[Any]
}
object LockObj
import scala.reflect.runtime.universe._
import scala.reflect._
/**
*
* <pre>
* case class User(id : Long, name : String = "no name",age : Int)
*
* val extractor = new SimpleMapExtractor
* val o = extractor.extract[User](Map("id" -> 23,"name" -> "Taro",age -> 17),None)
*
* print(o) // User(23,"name",17)
*
* val o = extractor.extract[User](Map("id" -> 1,age -> 20),None)
*
* print(o) // User(1,"no name",20)
*
* </pre>
* User: takeshita
* DateTime: 13/09/18 12:30
*/
class SimpleMapExtractor extends CaseClassExtractor[Map[String,Any]] {
def getValue(from: Map[String, Any], key: String, t: Type) = {
from.get(key) match{
case Some(v) =>{
if ( t =:= definitions.IntTpe){
Some(v.toString.toInt)
}else if (t =:= definitions.LongTpe){
Some(v.toString.toLong)
}else if (t =:= definitions.DoubleTpe){
Some(v.toString.toDouble)
}else if (t =:= definitions.BooleanTpe){
Some(v.toString.toBoolean)
}else if (t =:= typeOf[String]){
Some(v.toString)
}else{
None
}
}
case None => None
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment