Created
September 18, 2013 07:24
-
-
Save takezoux2/6605678 to your computer and use it in GitHub Desktop.
Scala の case classのコンストラクターを取得して、デフォルト値とかをごにょごにょするサンプルです。
CaseClassExtractorが、汎用クラスで
SimpleMapExtractorが、Map[String,Any]をcase classに変換しているサンプルです。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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