Anthony Accioly
Anthony Accioly

Reputation: 22481

How can I check if Any value isEmpty?

I have the following trait and class (actually this is a simplification, the real code is written in Java and is outside of my control):

trait BusinessTermValue {
  def getValue: Any
}

class BusinessTermValueImpl(override val getValue: Any) extends BusinessTermValue

Now I'm trying to improve my API without touching the original code (Pimp My Library pattern):

package object businessterms {

  implicit final class BusinessTermValueSupport(val btv: BusinessTermValue) extends AnyVal {

    def isDefined(): Boolean = btv != null && btv.value != null

    def isEmpty(): Boolean = isDefined() && (btv.value match {
      case s: String => s.isEmpty
      case l: Traversable[_] => l.isEmpty
      case c: java.util.Collection[_] => c.isEmpty
      case _ => false
    })

    def getAs[T](): T = btv.value.asInstanceOf[T]

  }

  object BusinessTerm {
    def apply(value: Any): BusinessTermValue = new BusinessTermValueImpl(value)
  }

}

It works quite well:

println(BusinessTerm("A String").isEmpty) // false
println(BusinessTerm(1).isEmpty) // false, Int can't be empty
println(BusinessTerm(new Integer(1)).isEmpty) // false, Integer can't be empty
println(BusinessTerm(List(1, 2, 3)).isEmpty) // false
println(BusinessTerm(List(1, 2, 3).asJava).isEmpty) // false
println(BusinessTerm("").isEmpty) // true
println(BusinessTerm(List()).isEmpty) // true
println(BusinessTerm(Seq()).isEmpty) // true
println(BusinessTerm(Map()).isEmpty) // true
println(BusinessTerm(List().asJava).isEmpty) // true

Still the pattern matching in isEmpty is cumbersome. Ideally I would like to use structural types and make sure that any type that implements isEmpty works with my API.

Unfortunately the code below doesn't work. The variable e matches any type, even when value does not define isEmpty:

def isEmpty(): Boolean = isDefined() && (btv.value match {
  case e: { def isEmpty(): Boolean } => e.isEmpty
  case _ => false
})

Is there a way to delegate isEmpty only when the underlying value implements it?

Upvotes: 2

Views: 1582

Answers (2)

Miquel
Miquel

Reputation: 4839

I was the one suggesting the Try version

def isEmpty(): Boolean = isDefined && (btv.value match {
  case e: { def isEmpty(): Boolean } => Try(e.isEmpty).getOrElse(false)
  case _ => false
})

I also noted that catching exceptions is expensive but I believe that this case would be the least common as in principle you would be using this construct when you expect to have an isEmpty method. So I think the trade-off may pay and would consider this a case of defensive coding.

Upvotes: 3

Andrey Tyukin
Andrey Tyukin

Reputation: 44967

Since the Java class that is not under your control returns Any/Object, you don't have any compile-time type safety anyway, so you don't lose anything by using reflection directly.

Here is an isEmpty implementation that works with Java-reflection. It's implemented as a function, but it should be trivial to change it into a method of your wrapper class:

def isEmpty(a: Any): Boolean = {
  try {
    a.getClass.getMethod("isEmpty").invoke(a).asInstanceOf[Boolean]
  } catch {
    case e: java.lang.NoSuchMethodException => false
  }
}

Here are a few examples:

println(isEmpty("hello"))
println(isEmpty(""))
println(isEmpty(List(1,2,3)))
println(isEmpty(Nil))
println(isEmpty(0 to 10))
println(isEmpty(42 until 42))
println(isEmpty(Some(42)))
println(isEmpty(None))
println(isEmpty((x: Int) => x * x))

It prints false/true in an alternating manner. It does not throw any nasty exceptions if it gets an object that has no isEmpty method, as the last example demonstrates.


EDIT

A more robust version would be this:

def isEmpty(a: Any): Boolean = {
  try {
    val m = a.getClass.getMethod("isEmpty")
    if (m.getParameterCount == 0) {
      m.invoke(a).asInstanceOf[Boolean]
    } else {
      false
    }
  } catch {
    case e: java.lang.NoSuchMethodException => false
    case e: java.lang.ClassCastException => false
  }
}

It guards against the cases that isEmpty is not nullary, and that it doesn't return a Boolean. However, it's still not "perfect", because it could happen that the method isEmpty is overloaded. Maybe org.apache.commons.lang3.reflect could come in handy.

Upvotes: 1

Related Questions