package scala.util.control
import collection.immutable.List
import java.lang.reflect.InvocationTargetException
object Exception {
type Catcher[+T] = PartialFunction[Throwable, T]
def mkCatcher[Ex <: Throwable: ClassManifest, T](isDef: Ex => Boolean, f: Ex => T) = new Catcher[T] {
private def downcast(x: Throwable): Option[Ex] =
if (classManifest[Ex].erasure.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[Ex])
else None
def isDefinedAt(x: Throwable) = downcast(x) exists isDef
def apply(x: Throwable): T = f(downcast(x).get)
}
def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f)
implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassManifest, T](pf: PartialFunction[Ex, T]) =
mkCatcher(pf.isDefinedAt _, pf.apply _)
def shouldRethrow(x: Throwable): Boolean = x match {
case _: ControlThrowable => true
case _: InterruptedException => true
case _ => false
}
trait Described {
protected val name: String
private var _desc: String = ""
def desc = _desc
def withDesc(s: String): this.type = {
_desc = s
this
}
override def toString() = name + "(" + desc + ")"
}
class Finally private[Exception](body: => Unit) extends Described {
protected val name = "Finally"
def and(other: => Unit): Finally = new Finally({ body ; other })
def invoke(): Unit = { body }
}
class Catch[+T](
val pf: Catcher[T],
val fin: Option[Finally] = None,
val rethrow: Throwable => Boolean = shouldRethrow)
extends Described {
protected val name = "Catch"
def or[U >: T](pf2: Catcher[U]): Catch[U] = new Catch(pf orElse pf2, fin, rethrow)
def or[U >: T](other: Catch[U]): Catch[U] = or(other.pf)
def apply[U >: T](body: => U): U =
try body
catch {
case x if rethrow(x) => throw x
case x if pf isDefinedAt x => pf(x)
}
finally fin map (_.invoke())
def andFinally(body: => Unit): Catch[T] = fin match {
case None => new Catch(pf, Some(new Finally(body)), rethrow)
case Some(f) => new Catch(pf, Some(f and body), rethrow)
}
def opt[U >: T](body: => U): Option[U] = toOption(Some(body))
def either[U >: T](body: => U): Either[Throwable, U] = toEither(Right(body))
def withApply[U](f: Throwable => U): Catch[U] = {
val pf2 = new Catcher[U] {
def isDefinedAt(x: Throwable) = pf isDefinedAt x
def apply(x: Throwable) = f(x)
}
new Catch(pf2, fin, rethrow)
}
def toOption: Catch[Option[T]] = withApply(_ => None)
def toEither: Catch[Either[Throwable, T]] = withApply(Left(_))
}
class Try[+T] private[Exception](body: => T, val catcher: Catch[T]) {
def apply(): T = catcher(body)
def apply[U >: T](other: => U): U = catcher(other)
def opt(): Option[T] = catcher opt body
def opt[U >: T](other: => U): Option[U] = catcher opt other
def either(): Either[Throwable, T] = catcher either body
def either[U >: T](other: => U): Either[Throwable, U] = catcher either other
def tryInstead[U >: T](other: => U) = new Try(other, catcher)
def or[U >: T](pf: Catcher[U]) = new Try(body, catcher or pf)
def andFinally(fin: => Unit) = new Try(body, catcher andFinally fin)
override def toString() = List("Try(<body>)", catcher.toString) mkString " "
}
final val nothingCatcher: Catcher[Nothing] = mkThrowableCatcher(_ => false, throw _)
final def allCatcher[T]: Catcher[T] = mkThrowableCatcher(_ => true, throw _)
final val noCatch: Catch[Nothing] = new Catch(nothingCatcher) withDesc "<nothing>"
final def allCatch[T]: Catch[T] = new Catch(allCatcher[T]) withDesc "<everything>"
def catching[T](exceptions: Class[_]*): Catch[T] =
new Catch(pfFromExceptions(exceptions : _*)) withDesc (exceptions map (_.getName) mkString ", ")
def catching[T](c: Catcher[T]): Catch[T] = new Catch(c)
def catchingPromiscuously[T](exceptions: Class[_]*): Catch[T] = catchingPromiscuously(pfFromExceptions(exceptions : _*))
def catchingPromiscuously[T](c: Catcher[T]): Catch[T] = new Catch(c, None, _ => false)
def ignoring(exceptions: Class[_]*): Catch[Unit] =
catching(exceptions: _*) withApply (_ => ())
def failing[T](exceptions: Class[_]*): Catch[Option[T]] =
catching(exceptions: _*) withApply (_ => None)
def failAsValue[T](exceptions: Class[_]*)(value: => T): Catch[T] =
catching(exceptions: _*) withApply (_ => value)
class By[T,R](f: T => R) {
def by(x: T): R = f(x)
}
def handling[T](exceptions: Class[_]*) = {
def fun(f: Throwable => T) = catching(exceptions: _*) withApply f
new By[Throwable => T, Catch[T]](fun _)
}
def ultimately[T](body: => Unit): Catch[T] = noCatch andFinally body
def unwrapping[T](exceptions: Class[_]*): Catch[T] = {
def unwrap(x: Throwable): Throwable =
if (wouldMatch(x, exceptions) && x.getCause != null) unwrap(x.getCause)
else x
catching(exceptions: _*) withApply (x => throw unwrap(x))
}
private def wouldMatch(x: Throwable, classes: collection.Seq[Class[_]]): Boolean =
classes exists (_ isAssignableFrom x.getClass)
private def pfFromExceptions(exceptions: Class[_]*) =
new PartialFunction[Throwable, Nothing] {
def apply(x: Throwable) = throw x
def isDefinedAt(x: Throwable) = wouldMatch(x, exceptions)
}
}