Rakshith
Rakshith

Reputation: 664

Determine non-empty additional fields in a subclass

Assume I have a trait which looks something like this

trait MyTrait {
  val x: Option[String] = None
  val y: Option[String] = None
}

Post defining the trait I extend this trait to a class MyClass which looks something like this

case class MyClass(
  override val x: Option[String] = None, 
  override val y: Option[String] = None, 
  z: Option[String] = None
) extends MyTrait

Now I need to find if any other property other than the properties extended by MyTrait is not None. In the sense if I need to write a method which is called getClassInfo which returns true/false based upon the values present in the case class. In this case it should return true if z is Non optional. My getClassInfo goes something like this

def getClassInfo(myClass: MyClass): Boolean = {
  myClass
    .productIterator
    .filterNot(x => x.isInstanceOf[MyTrait])
    .exists(_.isInstanceOf[Some[_]])
}

Ideally this should filter out all the fields which are not a part of Mytrait and return me z in this case. I tried using variance, However It seems like isInstanceOf doesn't take the same

filterNot(x => x.isInstanceOf[+MyTrait])

However this cannot be possible

val a = getClassInfo(MyClass()) //Needs to return false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //returns true
val c = getClassInfo(MyClass(z = Some("z"))) //needs to return true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y"))) // needs to return false

Upvotes: 0

Views: 139

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

If you really need reflection you can try

import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._

def getClassInfo(myClass: MyClass): Boolean = {
  def fields[A: TypeTag] = typeOf[A].members.collect {
    case m: MethodSymbol if m.isGetter && m.isPublic => m
  }
  val mtFields = fields[MyTrait]
  val mcFields = fields[MyClass]
  val mtFieldNames = mtFields.map(_.name).toSet
  val mcNotMtFields = mcFields.filterNot(f => mtFieldNames.contains(f.name))
  val instanceMirror = currentMirror.reflect(myClass)
  val mcNotMtFieldValues = mcNotMtFields.map(f => instanceMirror.reflectField(f).get)
  mcNotMtFieldValues.exists(_.isInstanceOf[Some[_]])
}

val a = getClassInfo(MyClass()) //false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //true
val c = getClassInfo(MyClass(z = Some("z"))) //true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y")))//false

Upvotes: 2

Tim
Tim

Reputation: 27356

The simple answer is to declare an abstract method that gives the result you want and override it in the subclass:

trait MyTrait {
  def x: Option[String]
  def y: Option[String]
  def anyNonEmpty: Boolean = false
}

case class MyClass(x: Option[String] = None, y: Option[String] = None, z: Option[String] = None) extends MyTrait {
  override def anyNonEmpty = z.nonEmpty
}

You can then call anyNonEmpty on your object to get the getClassInfo result.

Also note that I've used def here in the trait because val in a trait is generally a bad idea because of initialisation issues.

Upvotes: 6

Related Questions