Osthekake
Osthekake

Reputation: 315

Scala copy and reflection

In my project, there are many places where objects are picked out of a collection, copied with some values changed, and pushed back into the collection. I have been trying to create my own 'copy' method, that in addition to making a copy, also gives me a 'Diff' object. In other words, something that contains the arguments you just put into it.

The 'Diff' object should then be sent somewhere to be aggregated, so that someone else can get a report of all the changes since last time, without sending the actual entire object. This is all simple enough if one does it like this:

val user = new User(Some(23), true, "arne", None, None, "position", List(), "email", None, false)
val user0 = user.copy(position = "position2")
list ::= user0
val diff =  new Diff[User](Map("position" -> "position2"))

However, there is some duplicate work there, and I would very much like to just have it in one method, like:

val (user, diff) = user.copyAndDiff(position = "position")

I haven't been able to figure out what form the arguments to 'copy' actually takes, but I would be able to work with other forms as well.

I made a method with a Type argument, that should make a copy and a diff. Something like this:

object DiffCopy[Copyable]{
  def apply(original:Copyable, changes:Map[String, Any]){
    original.copy(??uhm..
    original.getAllTheFieldsAndCopyAndOverWriteSomeAccordingToChanges??

My first problem was that there doesn't seem to be any way to guarantee that the original object has a 'copy' method that I can overload to. The second problem appears when I want to actually assign the changes to their correct fields in the new, copied object. I tried to fiddle about with Reflection, and tried to find a way to set the value of a field with a name given as String. In which case I could keep my Diff as a a simple map, and simply create this diff-map first, and then apply it to my objects and also send them to where they needed to go.

However, I ended up deeper and deeper in the rabbit hole, and further and further away from what I actually wanted. I got to a point where I had an array of fields from an arbitrary object, and could get them by name, but I couldn't get it to work for a generic Type. So now I am here to ask if anyone can give me some advice on this situation?

The best answer I could get, would be if someone could tell me a simple way to apply a Map[String, Any] to something equivalent to the 'copy' method. I'm fairly sure this should be possible to implement, but it is simply currently beyond me...

Upvotes: 0

Views: 778

Answers (1)

vvg
vvg

Reputation: 6385

A little bit overcomplicated but solve your original problem.

The best answer I could get, would be if someone could tell me a simple way to apply a Map[String, Any] to something equivalent to the 'copy' method. I'm fairly sure this should be possible to implement, but it is simply currently beyond me...

  1. Take all fields from case class to map.

  2. Update map with new values.

  3. Create case class from new fields map.

Problems:

  • low performance

I'm pretty sure it can be done simpler...

 case class     Person(name: String, age: Int)

  def getCCParams(cc: Any) =
    (Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
      f.setAccessible(true)
      a + (f.getName -> f.get(cc))
    }

  def enrichCaseClass[T](cc: T, vals : Map[String, Any])(implicit cmf : ClassManifest[T]) = {
    val ctor = cmf.erasure.getConstructors().head
    val params = getCCParams(cc.asInstanceOf[Any]) ++ vals
    val args = cmf.erasure.getDeclaredFields().map( f => params(f.getName).asInstanceOf[Object] )
    ctor.newInstance(args : _*).asInstanceOf[T]
  }

  val j = Person("Jack", 15)
  enrichCaseClass(j, Map("age" -> 18))

Upvotes: 1

Related Questions