4
votes

I'm trying to figure out a way to call a Java API from Scala. Basically, there's a ContentValues object that has a couple of methods like getAsString, getAsLong, each having their own different return type.

Can I wrap ContentValues in another object so that I can add a get[T] method that calls the correct getAsXXX method depending on T?

What I tried (didn't work, complains about ambiguous implicit resolution) :

object SContentValuesConversions {

  case class SContentGetter[ScalaType](val check: String => Boolean, val getter: String => ScalaType) {
    def getTyped(key: String): Option[ScalaType] = {
      if (!check(key)) None
      else Some(getter(key))
    }
  }

  implicit def SIntContentValues(cv: ContentValues) =
    SContentGetter((cv containsKey _), (cv getAsInteger _))

  implicit def SLongContentValues(cv: ContentValues) =
    SContentGetter((cv containsKey _), (cv getAsLong _))

  implicit def SStringContentValues(cv: ContentValues) =
    SContentGetter((cv containsKey _), (cv getAsString _))
}
1

1 Answers

6
votes

You can use the same technique as the CanBuildFrom trait of collections.

We first create a Getter case class

case class Getter[T](getter: (ContentValues, String) => T) {

  def getOpt(contentValues: ContentValues, key: String): Option[T] =
    if (contentValues containsKey key) Some(getter(contentValues, key))
    else None
}

This allows us to create a ContentValues wrapper that has the desired method.

implicit class ContentValuesWrapper(val c: ContentValues) extends AnyVal {
  def getAsOpt[T](key: String)(implicit getter: Getter[T]) =
    getter.getOpt(c, key)
}

Now, in order to call the getAsOpt method on ContentValues we need to provide implicit instances of Getter for the correct types.

object Getter {
  implicit val intGetter = Getter(_ getAsInteger _)
  implicit val longGetter = Getter(_ getAsLong _)
  implicit val stringGetter = Getter(_ getAsString _)
}

Now you can use the getAsOpt method on a ContentValues instance.

// fake version of ContentValues
val c = 
  new ContentValues {
    val m = Map("a" -> "1", "b" -> "2", "c" -> "3")

    def getAsInteger(k: String): Int = getAsString(k).toInt
    def getAsLong(k: String): Long = getAsString(k).toLong
    def getAsString(k: String): String = m(k)

    def containsKey(k: String): Boolean = m contains k
  }

c.getAsOpt[Int]("a")      //Option[Int] = Some(1)
c.getAsOpt[Long]("b")     //Option[Long] = Some(2)
c.getAsOpt[String]("c")   //Option[String] = Some(3)
c.getAsOpt[Int]("d")      //Option[Int] = None
c.getAsOpt[Long]("e")     //Option[Long] = None
c.getAsOpt[String]("f")   //Option[String] = None