How to get the class of Option[_] on a None with Reflection - scala

I have this setup where I want to populate an object fields from an XML NodeSeq. A cutdown version of my setup is shown. It works well, but when I put None in testObj, I get a java.util.NoSuchElementException: None.get.
The problem I believe is that I cannot find a way to get the class of the Option[_] when it is None. I've been stuck on this for ages and I cannot see a way out. I feel there must be a way to branch and populate the object field when the original value is None, but how?
import xml.NodeSeq
object test {
case class TestObject(name: Option[String] = None, value: Option[Double] = None)
def main(args: Array[String]): Unit = {
val testObj = TestObject(Some("xxx"), Some(12.0))
// val testObj = TestObject(None, Some(12.0))
val nodeSeq = <test> <name> yyy </name> <value> 34.0 </value> </test>
val result = getObject[TestObject](nodeSeq, Option(testObj))
println("result = " + result) // result = Some(TestObject(Some(yyy),Some(34.0)))
}
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if ((nodeSeq.isEmpty) || (!obj.isDefined)) None else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val fieldValue = field.get(obj.get)
if (fieldValue == null || !fieldValue.isInstanceOf[Option[_]]) None
var newValue: Option[_] = None
fieldValue.asInstanceOf[Option[_]].get match { // <---- None.get is the error here
case x: Double => newValue = Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => newValue = Some((nodeSeq \ field.getName).text.trim)
}
val decField = obj.get.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(obj.get, newValue)
}
obj
}
}
}

I'm ignoring some other parts of this code that I don't like, however, you can't call .get on a None. You can call map and it will return what I assume you want. Here's the modified code:
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if (nodeSeq.isEmpty || obj.isEmpty) {
None
}
else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val fieldValue = field.get(obj.get)
if (fieldValue == null || !fieldValue.isInstanceOf[Option[_]]) None
val newValue = fieldValue.asInstanceOf[Option[_]].map(a => {
a match {
case x: Double => Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => Some((nodeSeq \ field.getName).text.trim)
}
})
val decField = obj.get.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(obj.get, newValue)
}
obj
}
}
In practice, I usually avoid calling .get on options as it results in bugs quite often.
UPDATE:
This version is a little more functional, though it still alters the obj that you send in:
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
obj.map(o => {
for {
field <- o.getClass.getDeclaredFields
_ = field.setAccessible(true)
fieldValue <- Option(field.get(o))
} yield {
val newValue = fieldValue match {
case Some(a) => {
a match {
case x: Double => Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => Some((nodeSeq \ field.getName).text.trim)
}
}
case _ => None
}
val decField = o.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(o, newValue)
}
o
})
}

Actually, this might be closer to what you want: check the type of the field instead.
import xml.NodeSeq
object test {
case class TestObject(name: Option[String] = None, value: Option[Double] = None)
def main(args: Array[String]): Unit = {
val testObj = TestObject(Some("xxx"), Some(12.0))
// val testObj = TestObject(None, Some(12.0))
val nodeSeq = <test> <name> yyy </name> <value> 34.0 </value> </test>
val result = getObject[TestObject](nodeSeq, Option(testObj))
println("result = " + result) // result = Some(TestObject(Some(yyy),Some(34.0)))
}
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if ((nodeSeq.isEmpty) || (!obj.isDefined)) None else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val newValue = field.getGenericType match {
case t: ParametrizedType if (t.getActualType == classOf[Option[_]]) =>
val paramType = t.getActualTypeArguments()(0)
if (paramType == classOf[java.lang.Double]) // since Double can't be a generic type parameter on JVM
// from your original code, but it probably needs to be modified
// to handle cases where field isn't in nodeSeq or not a number
Some((nodeSeq \ field.getName).text.trim.toDouble)
else if (paramType == classOf[String])
Some((nodeSeq \ field.getName).text.trim)
else None
case _ => None
}
if (newValue.isDefined)
field.set(obj.get, newValue)
}
obj
}
}
}
Version creating a new object instead (might need modification for your requirements):
def getObject[A](nodeSeq: NodeSeq, objClass: Class[A]): Option[A] = {
if (nodeSeq.isEmpty) None else {
try {
val fieldValues = objClass.getDeclaredFields.flatMap { field =>
field.setAccessible(true)
field.getGenericType match {
case t: ParametrizedType if (t.getActualType == classOf[Option[_]]) =>
val paramType = t.getActualTypeArguments()(0)
if (paramType == classOf[java.lang.Double])
Some(Some((nodeSeq \ field.getName).text.trim.toDouble))
else if (paramType == classOf[String])
Some(Some((nodeSeq \ field.getName).text.trim))
else None
case _ => None
}
}
// assumes only one constructor, otherwise get the desired one
val ctor = objClass.getConstructors()(0)
Some(ctor.newInstance(fieldValues))
} catch {
case _ => None
}
}
}

Related

Why I get a () when I running my Scala code?

object test {
def multBylarge(input: Array[Int]): Int = {
var result=0
var lst: List[Int]=List()
if(input.length == 0) result+=1
else
for(elements <- input) {
lst = lst :+ elements
}
var newlst = lst.sortWith(_ > _)
result += newlst(0) * newlst(1)
result
}
def check2(input: Array[_]) = input.foreach {
case _:Int => multBylarge(_)
case _:Double => "this is a double array"
case _:Float => "this is a float array"
case _=> "this is not a value"
}
def main(args: Array[String]): Unit = {
println(check2(Array(1,3,5,1,3,4)))
}
}
this is my code, I just have no idea. It will be appreciated if anyone can help me with it. And I am not familiar with scala code, so if there has any other problem, please let me know.
Scaladoc is your friend. foreach has return type of scala.Unit (see also) which has only one value - ():
def foreach[U](f: (T) => U): Unit
Apply f to each element for its side effects.
So actual signature of your method is def check2(input: Array[_]): Unit
You can do the following:
Update check2 match on input rather then foreach (since multBylarge requires a array input anyway, foreach wouldn't work here since it iterates over every item in the array then tries to apply it to multBylarge)
Wrap result in a Either (You can think if it as Left = Failure and Right = Successful result)
Then match on the result in the main function to pretty print the result/error
object test {
def multBylarge(input: Array[Int]): Int = {
var result=0
var lst: List[Int]=List()
if(input.length == 0) result+=1
else
for(elements <- input) {
lst = lst :+ elements
}
var newlst = lst.sortWith(_ > _)
result += newlst(0) * newlst(1)
result
}
def check2(input: Array[_]): Either[String, Int] = input match {
case ints: Array[Int] => Right(multBylarge(ints))
case _: Array[Double] => Left("this is a double array")
case _: Array[Float] => Left("this is a float array")
case _. => Left("this is not a value")
}
def main(args: Array[String]): Unit = {
check2(Array(1,3,5,1,3,4)) match {
case Right(result) => println(s"Result is $result")
case Left(error) => println(s"Failed with $error")
}
}
}

Convert Map[String, Any] to nested case class with controlled Type casting

I'd like to convert a Map[String, Any] to a given case class, and the map can be a nested map.
The case class can have certain fields as Optional, and if the map doesn't have the field in question , it'll have a default value.
eg.
case class CC(a: Int, b: Option[Int], c: String)
Map(a -> 1, c -> "abc")
result case class : CC(1, None, "abc")
The only caveat is I need to control the typeSignatures of these fields.
ex:
Map(a ->, b -> "10", c ->"abc"). \\Note b is string here
result case class : CC(1, Some(10), "abc")
I got some code that can do this exact behavior(code below), but I cant seem to make it work for nested case class
import scala.reflect.runtime.universe._
object DynamicClassFactory {
val classLoaderMirror = runtimeMirror(getClass.getClassLoader)
}
class DynamicClassFactory[T: TypeTag] {
import DynamicClassFactory.classLoaderMirror
val tpe = typeOf[T]
val classSymbol = tpe.typeSymbol.asClass
val companion = tpe.typeSymbol.companion
val classMirror = classLoaderMirror reflectClass classSymbol
val instanceMirror = classLoaderMirror.reflectModule(companion.asModule)
val objMirror = classLoaderMirror.reflect(instanceMirror.instance)
val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR)
val constructorArgs = constructorSymbol.asMethod.paramLists.flatten.map{
p => (p.name.decodedName.toString, p.typeSignature)
}
val defaultConstructor =
if (constructorSymbol.isMethod) constructorSymbol.asMethod
else {
val ctors = constructorSymbol.asTerm.alternatives
ctors.map { _.asMethod }.find { _.isPrimaryConstructor }.get
}
val constructorMethod = classMirror reflectConstructor defaultConstructor
def buildParameters: List[String] = constructorArgs.map ( x => x._1 )
def getCompanionParam(name: String): Any =
if(classSymbol.isCaseClass) {
objMirror.reflectMethod(instanceMirror.symbol.info.decl(TermName(name)).asMethod)("test")
}
def getDefaults = {
val defaults = companion.typeSignature.members.filter { m => m.isMethod && m.name.toString.contains("apply$default")}.toList.map { x => (x.asMethod.name.toTermName.toString.replaceAll("[^0-9]", "").toInt-1, getCompanionParam(x.asMethod.name.toTermName.toString)) }.toMap
val defaultValues = scala.collection.mutable.Map[String, Any]()
for( (x, i) <- buildParameters.view.zipWithIndex ) {
if(defaults.contains(i)) {
defaultValues(x) = defaults(i)
}
}
defaultValues.toMap
}
def buildWith(args: Seq[_]): T = {
constructorMethod(args: _*).asInstanceOf[T]
}
def buildSafe(configuration: Map[String, Any]): T = {
val config = getDefaults ++ configuration
val safeConfig = safeTypeConvert(config, constructorArgs.toMap)
buildWith(constructorArgs.map { x => safeConfig(x._1) })
}
def safeTypeConvert(v: Map[String, Any], t: Map[String, Type]): Map[String, Any] = {
val o = scala.collection.mutable.Map[String, Any]()
val z = v.filter{
case (k, v) =>
t.contains(k)
}
z.foreach {
case (k, v) =>
val typeS: Type = t(k)
try {
if (typeS <:< typeOf[Option[String]]) {
v match {
case x: Int => o(k) = Some(x.toString)
case y: Option[Nothing] => o(k) = None
case _ => o(k) = Some(v.asInstanceOf[String])
}
}
else if (typeS <:< typeOf[Option[Int]]) {
v match {
case x: String => o(k) = Some(x.toFloat.toInt)
case y: Option[Nothing] => o(k) = None
case _ => o(k) = Some(v.asInstanceOf[Int])
}
}
else if (typeS <:< typeOf[Option[Boolean]]) {
v match {
case x: String => o(k) = Some(x.toLowerCase.toBoolean)
case y: Option[Nothing] => o(k) = None
case _ => o(k) = v
}
}
else if (typeS <:< typeOf[Int]) {
v match {
case x: String => o(k) = x.toFloat.toInt // this is to handle decimals returned from json
case _ => o(k) = v.asInstanceOf[Int]
}
} else if (typeS =:= typeOf[String]) {
v match {
case x: Int => o(k) = x.toString
case _ => o(k) = v.asInstanceOf[String]
}
} else if (typeS =:= typeOf[Boolean]) {
v match {
case x: String => o(k) = x.toLowerCase.toBoolean
case _ => o(k) = v
}
}
else if (typeS <:< typeOf[List[_]]) {
if (v == Nil) {
o(k) = List[String]()
} else {
o(k) = v.asInstanceOf[List[_]]
}
} else if (typeS <:< typeOf[Map[_, _]]) {
if (v == None) {
o(k) = Map()
} else o(k) = v
}
else {
throw new Exception(s"sdf $k must be of type ${typeS.typeSymbol.name.decoded}, got ${v.getClass.getName}: ${v.toString}")
}
} catch {
case e#(_: ClassCastException | _: NumberFormatException | _: IllegalArgumentException) =>
throw new Exception(s"$k must be of type ${typeS.typeSymbol.name.decoded}, got ${v.getClass.getName}: ${v.toString}")
}
}
return o.toMap
}
}
The above works great for a map which is not a nested map, but fails for a nested map
case class Address(addLine1: String, addLine2: Optional[String], zip: Long)
case class Inner(id: Option[String], addr: Option[Address], isPath: Option[Boolean])
case class Outer(name: String, addtlnInfo: Inner)
val map: Map[String, Any] = Map("name" -> "john", "addtlnInfo" -> Map("id" -> 123, "addr" -> Map("addLine1" -> "901 Lincoln St", "zip" -> "80101")))
val ins = new DynamicClassFactory[Outer]
ins.buildSafe(map)

slick 3.0 select then insert

I want to insert a new row with different status value if that row exists and return the new instance as Some to the caller. If it does not exist, nothing happens and return None. What I have is
def revoke(name: String, issueFor: String): Future[MyLicense] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicense = for {
licenseOption <- retrieveLicense
} yield {
licenseOption match {
case Some(l) => Some(slickLicenses += l.copy(status = RevokedLicense))
case None => None
}
}
db.run(insertLicense).map {r =>
r.map { dbLicense => MyLicense(dbLicense.name, dbLicense.status)
}
}
How do I fix this?
Edit 1
I changed the code to
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
newLicense <- slickLicenses += l.get.copy(status = RevokedLicense)
} yield newLicense
db.run(insertLicenseAction).map(_ => name)
}
Hence, the question changes to how do I get the instance I insert into the database? Thanks
This is what I did finally,
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
_ <- l.map(x => slickLicenses += x.copy(status = RevokedLicense,
modifiedOn = new Timestamp(System.currentTimeMillis())))
.getOrElse(DBIO.successful(l))
} yield ()
db.run(insertLicenseAction).map(x => name)}

Equality of a list of classes

I am trying to compare two Lists containing case classes. Can't figure out which method to override to get this working. Please refer to the code below.
trait Filter {
def toQueryString: String
}
trait SimpleFilter extends Filter {
val key: String
val value: Any
override def toQueryString = value match {
case v: String => s"$key='$v'"
case _ => s"$key=$value"
}
override def toString = "$key:$value"
override def equals(that: Any) = {
that match {
case s: SimpleFilter => {
key == key && value == value
}
case _ => false
}
}
override def hashCode() = key.hashCode + value.hashCode
}
class DestinationFilter(override val value: String) extends SimpleFilter {
override val key = "destination"
}
object DestinationFilter {
def apply(value: String) = new DestinationFilter(value)
}
object Tester {
def main(args: Array[String]): Unit = {
val d1 = DestinationFilter("Google")
val d2 = DestinationFilter("Google")
val l1 = List(d1)
val l2 = List(d2)
println(11 == 12)
println(d1 == d2)
}
}
which returns
false
true
Can't understand why the list comparison returns false. Ultimately what I want is this to be equal
List(DestinationFilter("Amazon"), LocationFilter("Yahoo"), DestinationFilter("Google") == List(DestinationFilter("Yahoo"), LocationFilter("Amazon"), DestinationFilter("Google")
*they should be equal irrespective of order.
You have a typo in your comparison, 11 and 12 should be l1 and l2:
println(l1 == l2) // true
So 11 == 12 is always false, you're just comparing Ints.
You also need to change your equals override, your just comparing key and value to themselves, which is always true:
override def equals(that: Any) = {
that match {
case s: SimpleFilter =>
key == s.key && value == s.value //edited
case _ => false
}
}

How to get a name of a class member?

I want to be able to do something like this:
prepare form:
val formDescription = formBuilder(_.textField[User](_.firstName)
.textField[User](_.lastName)
).build
showForm(formDescription)
extract data from user filled form, using User:
//contains data of a form submitted by a user:
val formData: Map[String, String] = getFormData
val newUser = User(id = randomUuid, firstName = formData.extract[User](_.firstName))
One solution I see is to use a dynamic proxy that extends provided class and remembers what was invoked on him:
def getFieldName[T:Manifest](foo: T => Any) = {
val clazz = implicitly[Manifest[T]].erasure
val proxy = createDynamicProxy(clazz)
foo(proxy)
proxy.lastInvokedMethodName
}
Is there a better way to do it? Is there any lib that implements it already?
This reflective approach takes a case class and invokes its companion apply, calling getField and fetching default args if the field is not in the data.
import scala.reflect.runtime.{currentMirror => cm, universe => uni}
import uni._
def fromXML(xml: Node): Option[PluginDescription] = {
def extract[A]()(implicit tt: TypeTag[A]): Option[A] = {
// extract one field
def getField(field: String): Option[String] = {
val text = (xml \\ field).text.trim
if (text == "") None else Some(text)
}
val apply = uni.newTermName("apply")
val module = uni.typeOf[A].typeSymbol.companionSymbol.asModule
val ts = module.moduleClass.typeSignature
val m = (ts member apply).asMethod
val im = cm reflect (cm reflectModule module).instance
val mm = im reflectMethod m
def getDefault(i: Int): Option[Any] = {
val n = uni.newTermName("apply$default$" + (i+1))
val m = ts member n
if (m == NoSymbol) None
else Some((im reflectMethod m.asMethod)())
}
def extractArgs(pss: List[List[Symbol]]): List[Option[Any]] =
pss.flatten.zipWithIndex map (p => getField(p._1.name.encoded) orElse getDefault(p._2))
val args = extractArgs(m.paramss)
if (args exists (!_.isDefined)) None
else Some(mm(args.flatten: _*).asInstanceOf[A])
}
// check the top-level tag
xml match {
case <plugin>{_*}</plugin> => extract[PluginDescription]()
case _ => None
}
}
The idea was to do something like:
case class User(id: Int = randomUuid, firstName: String, lastName: String)
val user = extract[User]()
That's my own solution:
package utils
import javassist.util.proxy.{MethodHandler, MethodFilter, ProxyFactory}
import org.specs2.mutable._
import javassist.util.proxy.Proxy
import java.lang.reflect.{Constructor, Method}
class DynamicProxyTest extends Specification with MemberNameGetter {
"Dynamic proxy" should {
"extract field name" in {
memberName[TestClass](_.a) must ===("a")
memberName[TestClass](_.i) must ===("i")
memberName[TestClass](_.b) must ===("b")
memberName[TestClass](_.variable) must ===("variable")
memberName[TestClass](_.value) must ===("value")
memberName[TestClass](_.method) must ===("method")
}
}
}
trait MemberNameGetter {
def memberName[T: Manifest](foo: T => Any) = {
val mf = manifest[T]
val clazz = mf.erasure
val proxyFactory = new ProxyFactory
proxyFactory.setSuperclass(clazz)
proxyFactory.setFilter(new MethodFilter {
def isHandled(p1: Method) = true
})
val newClass = proxyFactory.createClass()
var lastInvokedMethod: String = null
val mh = new MethodHandler {
def invoke(p1: Any, p2: Method, p3: Method, p4: Array[AnyRef]) = {
lastInvokedMethod = p2.getName
p3.invoke(p1, p4: _*)
}
}
val constructor = defaultConstructor(newClass)
val parameters = defaultConstructorParameters(constructor)
// val proxy = constructor.newInstance("dsf", new Integer(0))
val proxy2 = constructor.newInstance(parameters: _*)
proxy2.asInstanceOf[Proxy].setHandler(mh)
foo(proxy2.asInstanceOf[T])
lastInvokedMethod
}
private def defaultConstructor(c: Class[_]) = c.getConstructors.head
private def defaultConstructorParameters(constructor: Constructor[_]) = {
val parameterTypes = constructor.getParameterTypes
parameterTypes.map{
case Integer.TYPE => Integer.valueOf(0)
case _ => null
}
}
}
case class TestClass(a: String, i: Int, b: Boolean) {
var variable = "asdf"
val value = "asdfasdfasd"
def method = "method"
}
val mh = new MethodHandler {
def invoke(p1: Any, p2: Method, p3: Method, p4: Array[AnyRef]) = {
lastInvokedMethod = p2.getName
p3.invoke(p1, p4: _*)
}
}
val constructor = defaultConstructor(newClass)
val parameters = defaultConstructorParameters(constructor)
// val proxy = constructor.newInstance("dsf", new Integer(0))
val proxy2 = constructor.newInstance(parameters: _*)
proxy2.asInstanceOf[Proxy].setHandler(mh)
foo(proxy2.asInstanceOf[T])
lastInvokedMethod
}
private def defaultConstructor(c: Class[_]) = c.getConstructors.head
private def defaultConstructorParameters(constructor: Constructor[_]) = {
val parameterTypes = constructor.getParameterTypes
parameterTypes.map{
case Integer.TYPE => Integer.valueOf(0)
case java.lang.Double.TYPE => java.lang.Double.valueOf(0)
case java.lang.Long.TYPE => java.lang.Long.valueOf(0)
case java.lang.Boolean.TYPE => java.lang.Boolean.FALSE
case _ => null
}
}
}
case class TestClass(a: String, i: Int, b: Boolean) {
var variable = "asdf"
val value = "asdfasdfasd"
def method = "method"
}