opensas
opensas

Reputation: 63405

define your own exceptions with overloaded constructors in scala

In java exceptions have at least these four constructors:

Exception() 
Exception(String message) 
Exception(String message, Throwable cause) 
Exception(Throwable cause) 

If you want to define your own extensions, you just have to declare a descendent exceptions and implement each desired constructor calling the corresponden super constructor

How can you achieve the same thing in scala?

so far now I saw this article and this SO answer, but I suspect there must be an easier way to achieve such a common thing

Upvotes: 37

Views: 21317

Answers (6)

matfax
matfax

Reputation: 685

Here is a similar approach to the one of @roman-borisov but more typesafe.

case class ShortException(message: String = "", cause: Option[Throwable] = None)
    extends Exception(message) {
  cause.foreach(initCause)
}

Then, you can create Exceptions in the Java manner:

throw ShortException() 
throw ShortException(message) 
throw ShortException(message, Some(cause)) 
throw ShortException(cause = Some(cause)) 

Upvotes: 0

Jose Fernandez
Jose Fernandez

Reputation: 74

Scala pattern matching in try/catch blocks works on interfaces. My solution is to use an interface for the exception name then use separate class instances.

trait MyException extends RuntimeException

class MyExceptionEmpty() extends RuntimeException with MyException

class MyExceptionStr(msg: String) extends RuntimeException(msg) with MyException

class MyExceptionEx(t: Throwable) extends RuntimeException(t) with MyException

object MyException {
  def apply(): MyException = new MyExceptionEmpty()
  def apply(msg: String): MyException = new MyExceptionStr(msg)
  def apply(t: Throwable): MyException = new MyExceptionEx(t)
}

class MyClass {
  try {
    throw MyException("oops")
  } catch {
    case e: MyException => println(e.getMessage)
    case _: Throwable => println("nope")
  }
}

Instantiating MyClass will output "oops".

Upvotes: 0

senia
senia

Reputation: 38045

Default value for cause is null. And for message it is either cause.toString() or null:

val e1 = new RuntimeException()

e.getCause
// res1: java.lang.Throwable = null

e.getMessage
//res2: java.lang.String = null

val cause = new RuntimeException("cause msg")
val e2 = new RuntimeException(cause)

e.getMessage()
//res3: String = java.lang.RuntimeException: cause msg

So you can just use default values:

class MyException(message: String = null, cause: Throwable = null) extends
  RuntimeException(MyException.defaultMessage(message, cause), cause)

object MyException {
  def defaultMessage(message: String, cause: Throwable) =
    if (message != null) message
    else if (cause != null) cause.toString()
    else null
}

// usage:
new MyException(cause = myCause)
// res0: MyException = MyException: java.lang.RuntimeException: myCause msg

Upvotes: 59

chaotic3quilibrium
chaotic3quilibrium

Reputation: 5924

To me, it appears there are three different needs which have a dynamic tension with each other:

  1. The convenience of the extender of RuntimeException; i.e. minimal code to be written to create a descendant of RuntimeException
  2. Client's perceived ease of use; i.e. minimal code to be written at the call-site
  3. Client's preference to avoiding leaking the dreaded Java null into their code

If one doesn't care about number 3, then this answer (a peer to this one) seems pretty succinct.

However, if one values number 3 while trying to get as close to number 1 and 2 as possible, the solution below effectively encapsulates the Java null leak into your Scala API.

class MyRuntimeException (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
) {
  def this() =
    this(None, None, false, false)
  def this(message: String) =
    this(Some(message), None, false, false)
  def this(cause: Throwable) =
    this(None, Some(cause), false, false)
  def this(message: String, cause: Throwable) =
    this(Some(message), Some(cause), false, false)
}

And if you would like to eliminate having to use new where MyRuntimeException is actually used, add this companion object (which just forwards all of the apply calls to the existing "master" class constructor):

object MyRuntimeException {
  def apply: MyRuntimeException =
    MyRuntimeException()
  def apply(message: String): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message))
  def apply(cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionCause = Some(cause))
  def apply(message: String, cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
  def apply(
    optionMessage: Option[String] = None,
    optionCause: Option[Throwable] = None,
    isEnableSuppression: Boolean = false,
    isWritableStackTrace: Boolean = false
  ): MyRuntimeException =
    new MyRuntimeException(
      optionMessage,
      optionCause,
      isEnableSuppression,
      isWritableStackTrace
    )
}

Personally, I prefer to actually suppress the use of the new operator in as much code as possible so as to ease possible future refactorings. It is especially helpful if said refactoring happens to strongly to the Factory pattern. My final result, while more verbose, should be quite nice for clients to use.

object MyRuntimeException {
  def apply: MyRuntimeException =
    MyRuntimeException()
  def apply(message: String): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message))
  def apply(cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionCause = Some(cause))
  def apply(message: String, cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
  def apply(
    optionMessage: Option[String] = None,
    optionCause: Option[Throwable] = None,
    isEnableSuppression: Boolean = false,
    isWritableStackTrace: Boolean = false
  ): MyRuntimeException =
    new MyRuntimeException(
      optionMessage,
      optionCause,
      isEnableSuppression,
      isWritableStackTrace
    )
}

class MyRuntimeException private[MyRuntimeException] (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
)


Exploring a more sophisticated RuntimeException pattern:

It's only a small leap from the original question to desire to create an ecosystem of specialized RuntimeExceptions for a package or API. The idea is to define a "root" RuntimeException from which a new ecosystem of specific descendant exceptions can be created. For me, it was important to make using catch and match much easier to exploit for specific types of errors.

For example, I have a validate method defined which verifies a set of conditions prior to allowing a case class to be created. Each condition that fails generates a RuntimeException instance. And then the List of RuntimeInstances are returned by the method. This gives the client the ability to decide how they would like to handle the response; throw the list holding exception, scan the list for something specific and throw that or just push the entire thing up the call chain without engaging the very expensive JVM throw command.

This particular problem space has three different descendants of RuntimeException, one abstract (FailedPrecondition) and two concrete (FailedPreconditionMustBeNonEmptyList and FailedPreconditionsException).

The first, FailedPrecondition, is a direct descendant to RuntimeException, very similar to MyRuntimeException, and is abstract (to prevent direct instantiations). FailedPrecondition has a "companion object trait", FailedPreconditionObject which acts as the instantiation factory (suppressing the new operator).

trait FailedPreconditionObject[F <: FailedPrecondition] {
  def apply: F =
    apply()

  def apply(message: String): F =
    apply(optionMessage = Some(message))

  def apply(cause: Throwable): F =
    apply(optionCause = Some(cause))

  def apply(message: String, cause: Throwable): F =
    apply(optionMessage = Some(message), optionCause = Some(cause))

  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): F
}
abstract class FailedPrecondition (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
)

The second, FailedPreconditionMustBeNonEmptyList, is an indirect RuntimeException descendant and a direct concrete implementation of FailedPrecondition. It defines both a companion object and a class. The companion object extends the trait FailedPreconditionObject. And the class simply extends the abstract class FailedPrecondition and marks it final to prevent any further extensions.

object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): FailedPreconditionMustBeNonEmptyList =
    new FailedPreconditionMustBeNonEmptyList(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
    optionMessage: Option[String]
  , optionCause: Option[Throwable]
  , isEnableSuppression: Boolean
  , isWritableStackTrace: Boolean
) extends
  FailedPrecondition(
      optionMessage
    , optionCause
    , isEnableSuppression
    , isWritableStackTrace
  )

The third, FailedPreconditionsException, is a direct descendant to RuntimeException which wraps a List of FailedPreconditions and then dynamically manages the emitting of the exception message.

object FailedPreconditionsException {
  def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
    FailedPreconditionsException(List(failedPrecondition))
  def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
    tryApply(failedPreconditions).get
  def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
    tryApply(List(failedPrecondition))
  def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
    if (failedPreconditions.nonEmpty)
      Success(new FailedPreconditionsException(failedPreconditions))
    else
      Failure(FailedPreconditionMustBeNonEmptyList())
  private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
    if (failedPreconditions.size > 1)
      s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
    else
      s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
  val failedPreconditions: List[FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))

And then bringing all of that together as a whole and tidy things up, I place both FailedPrecondition and FailedPreconditionMustBeNonEmptyList within object FailedPreconditionsException. And this is what the final result looks like:

object FailedPreconditionsException {
  trait FailedPreconditionObject[F <: FailedPrecondition] {
    def apply: F =
      apply()

    def apply(message: String): F =
      apply(optionMessage = Some(message))

    def apply(cause: Throwable): F =
      apply(optionCause = Some(cause))

    def apply(message: String, cause: Throwable): F =
      apply(optionMessage = Some(message), optionCause = Some(cause))

    def apply(
        optionMessage: Option[String] = None
      , optionCause: Option[Throwable] = None
      , isEnableSuppression: Boolean = false
      , isWritableStackTrace: Boolean = false
    ): F
  }
  abstract class FailedPrecondition (
      val optionMessage: Option[String]
    , val optionCause: Option[Throwable]
    , val isEnableSuppression: Boolean
    , val isWritableStackTrace: Boolean
  ) extends RuntimeException(
    optionMessage match {
      case Some(string) => string
      case None => null
    },
    optionCause match {
      case Some(throwable) => throwable
      case None => null
    },
    isEnableSuppression,
    isWritableStackTrace
  )

  object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
    def apply(
        optionMessage: Option[String] = None
      , optionCause: Option[Throwable] = None
      , isEnableSuppression: Boolean = false
      , isWritableStackTrace: Boolean = false
    ): FailedPreconditionMustBeNonEmptyList =
      new FailedPreconditionMustBeNonEmptyList(
          optionMessage
        , optionCause
        , isEnableSuppression
        , isWritableStackTrace
      )
  }
  final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
      optionMessage: Option[String]
    , optionCause: Option[Throwable]
    , isEnableSuppression: Boolean
    , isWritableStackTrace: Boolean
  ) extends
    FailedPrecondition(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )

  def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
    FailedPreconditionsException(List(failedPrecondition))

  def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
    tryApply(failedPreconditions).get

  def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
    tryApply(List(failedPrecondition))

  def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
    if (failedPreconditions.nonEmpty)
      Success(new FailedPreconditionsException(failedPreconditions))
    else
      Failure(FailedPreconditionMustBeNonEmptyList())
  private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
    if (failedPreconditions.size > 1)
      s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
    else
      s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
  val failedPreconditions: List[FailedPreconditionsException.FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))

And this is what it would look like for a client to use the above code to create their own exception derivation called FailedPreconditionMustBeNonEmptyString:

object FailedPreconditionMustBeNonEmptyString extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyString] {
  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): FailedPreconditionMustBeNonEmptyString =
    new FailedPreconditionMustBeNonEmptyString(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )
}
final class FailedPreconditionMustBeNonEmptyString private[FailedPreconditionMustBeNonEmptyString] (
    optionMessage: Option[String]
  , optionCause: Option[Throwable]
  , isEnableSuppression: Boolean
  , isWritableStackTrace: Boolean
) extends
  FailedPrecondition(
      optionMessage
    , optionCause
    , isEnableSuppression
    , isWritableStackTrace
  )

And then the use of this exception looks like this:

throw FailedPreconditionMustBeNonEmptyString()

I went well beyond answering the original question because I found it so difficult to locate anything close to being both specific and comprehensive in Scala-ifying RuntimeException in specific or extending into the more general "exception ecosystem" with which I grew so comfortable when in Java.

I'd love to hear feedback (other than variations on, "Wow! That's way too verbose for me.") on my solution set. And I would love any additional optimizations or ways to reduce the verbosity WITHOUT LOSING any of the value or terseness I have generated for the clients of this pattern.

Upvotes: 5

Roman Borisov
Roman Borisov

Reputation: 61

You can use Throwable.initCause.

class MyException (message: String, cause: Throwable) 
  extends RuntimeException(message) {
    if (cause != null)
      initCause(cause)

    def this(message: String) = this(message, null)  
}

Upvotes: 6

opensas
opensas

Reputation: 63405

well, this is the best I've found so far

class MissingConfigurationException private(ex: RuntimeException) extends RuntimeException(ex) {
  def this(message:String) = this(new RuntimeException(message))
  def this(message:String, throwable: Throwable) = this(new RuntimeException(message, throwable))
}

object MissingConfigurationException {
  def apply(message:String) = new MissingConfigurationException(message)
  def apply(message:String, throwable: Throwable) = new MissingConfigurationException(message, throwable)
}

this way you may use the "new MissingConfigurationException" or the apply method from the companion object

Anyway, I'm still surprised that there isn't a simpler way to achieve it

Upvotes: 12

Related Questions