vendredi 25 janvier 2019

Need to Reference Trait on Companion Object From Trait on Case Class

I need to access a companion class with a specified trait -- from a trait intended for case classes. I am certain that the Scala reflection library can accomplish this but I haven't quite been able to piece it together.

I created test code below that requires one section of ??? be filled in with some reflection magic. The code compiles and runs as is -- with a notification due to the missing functionality.

Some related answers that I have seen on StackOverflow were from 2.10. Scala 2.12 compatible please.

import scala.reflect.{ClassTag, classTag}

//for companion object
//accesses Fields of the associated case class to ensure the correctness
//note: abstract class -- not a trait due to issues using ClassTag on a trait
abstract class SupportsField1Companion[T: ClassTag] {
  //gets the names of all Fields on the associated case class
  val fieldNamesOfInstancedClass: Array[String] =
    classTag[T].runtimeClass.getDeclaredFields.map(_.getName)

  //prints the name and fields of the associated case class -- plus extra on success
  def printFieldNames(extra: String = ""): Unit = {
    val name = classTag[T].runtimeClass.getCanonicalName
    val fields = fieldNamesOfInstancedClass.reduceLeft(_ + ", " + _)
    println(s"Fields of $name: $fields" + extra)
  }
}

//for case classes
//IMPORTANT -- please do not parameterize this if possible
trait SupportsField1 {
  //some data for printing
  val field1: String = this.getClass.getCanonicalName + ": field1"

  //should get a reference to the associated companion object as instance of SupportsFieldsCompanion
  def getSupportsFieldsCompanion: SupportsField1Companion[this.type] = //this.type may be wrong
    ??? //TODO reflection magic required -- need functionality to retrieve companion object cast as type

  //calls a function on the associated Companion class
  def callPrintFuncOnCompanion(): Unit =
    getSupportsFieldsCompanion.printFieldNames(s" -- from ${this.getClass.getCanonicalName}")
}

//two case classes with the SupportsFieldsCompanion trait to ensure data is accessed correctly
object ExampleA extends SupportsField1Companion[ExampleA] {}
case class ExampleA() extends SupportsField1 {
  val fieldA: String = "ExampleA: fieldA"
}
object ExampleB extends SupportsField1Companion[ExampleB] {}
case class ExampleB() extends SupportsField1 {
  val fieldB: String = "ExampleB: fieldB"
}

object Run extends App {
  //create instanced classes and print some test data
  val exampleA = ExampleA()
  println(exampleA.field1) //prints "ExampleA: field1" due to trait SupportsFields
  println(exampleA.fieldA) //prints "ExampleA: fieldA" due to being of class ExampleA
  val exampleB = ExampleB()
  println(exampleB.field1) //prints "ExampleB: field1" due to trait SupportsFields
  println(exampleB.fieldB) //prints "ExampleB: fieldB" due to being of class ExampleB

  //via the SupportsFieldsCompanion trait on the companion objects,
  //call a function on each companion object to show that each companion is associated with the correct case class
  ExampleA.printFieldNames() //prints "Fields of ExampleA: fieldA, field1"
  ExampleB.printFieldNames() //prints "Fields of ExampleB: fieldB, field1"

  //test access of printFieldNames on companion object from instanced class
  try {
    exampleA.callPrintFuncOnCompanion() //on success, prints "Fields of ExampleA: fieldA, field1 -- from ExampleA"
    exampleB.callPrintFuncOnCompanion() //on success, prints "Fields of ExampleB: fieldB, field1 -- from ExampleB"
  } catch {
    case _: NotImplementedError => println("!!! Calling function on companion(s) failed.")
  }
}





Aucun commentaire:

Enregistrer un commentaire