From 0db65ea0c4805cfa501c8300547aef318256dbf9 Mon Sep 17 00:00:00 2001 From: Janek Bogucki Date: Mon, 2 May 2016 09:09:09 +0100 Subject: Add examples to Exception object and group members (#5111) - Extend main comment with additional examples - Group methods from a user perspective - List exceptions special cased by shouldRethrow - Include overlooked withTry in opt, either notes. --- src/library/scala/util/control/Exception.scala | 190 +++++++++++++++++++++---- 1 file changed, 165 insertions(+), 25 deletions(-) diff --git a/src/library/scala/util/control/Exception.scala b/src/library/scala/util/control/Exception.scala index 8aa4073a51..64f491d7f0 100644 --- a/src/library/scala/util/control/Exception.scala +++ b/src/library/scala/util/control/Exception.scala @@ -13,21 +13,136 @@ package control import scala.reflect.{ ClassTag, classTag } import scala.language.implicitConversions - /** Classes representing the components of exception handling. - * Each class is independently composable. Some example usages: + * + * Each class is independently composable. + * + * This class differs from [[scala.util.Try]] in that it focuses on composing exception handlers rather than + * composing behavior. All behavior should be composed first and fed to a [[Catch]] object using one of the + * `opt`, `either` or `withTry` methods. Taken together the classes provide a DSL for composing catch and finally + * behaviors. + * + * === Examples === + * + * Create a `Catch` which handles specified exceptions. * {{{ * import scala.util.control.Exception._ * import java.net._ * * val s = "http://www.scala-lang.org/" - * val x1 = catching(classOf[MalformedURLException]) opt new URL(s) - * val x2 = catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s) + * + * // Some(http://www.scala-lang.org/) + * val x1: Option[URL] = catching(classOf[MalformedURLException]) opt new URL(s) + * + * // Right(http://www.scala-lang.org/) + * val x2: Either[Throwable,URL] = + * catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s) + * + * // Success(http://www.scala-lang.org/) + * val x3: Try[URL] = catching(classOf[MalformedURLException], classOf[NullPointerException]) withTry new URL(s) + * + * val defaultUrl = new URL("http://example.com") + * // URL(http://example.com) because htt/xx throws MalformedURLException + * val x4: URL = failAsValue(classOf[MalformedURLException])(defaultUrl)(new URL("htt/xx")) + * }}} + * + * Create a `Catch` which logs exceptions using `handling` and `by`. + * {{{ + * def log(t: Throwable): Unit = t.printStackTrace + * + * val withThrowableLogging: Catch[Unit] = handling(classOf[MalformedURLException]) by (log) + * + * def printUrl(url: String) : Unit = { + * val con = new URL(url) openConnection() + * val source = scala.io.Source.fromInputStream(con.getInputStream()) + * source.getLines.foreach(println) + * } + * + * val badUrl = "htt/xx" + * // Prints stacktrace, + * // java.net.MalformedURLException: no protocol: htt/xx + * // at java.net.URL.(URL.java:586) + * withThrowableLogging { printUrl(badUrl) } + * + * val goodUrl = "http://www.scala-lang.org/" + * // Prints page content, + * // <!DOCTYPE html> + * // <html> + * withThrowableLogging { printUrl(goodUrl) } + * }}} + * + * Use `unwrapping` to create a `Catch` that unwraps exceptions before rethrowing. + * {{{ + * class AppException(cause: Throwable) extends RuntimeException(cause) + * + * val unwrappingCatch: Catch[Nothing] = unwrapping(classOf[AppException]) + * + * def calcResult: Int = throw new AppException(new NullPointerException) + * + * // Throws NPE not AppException, + * // java.lang.NullPointerException + * // at .calcResult(<console>:17) + * val result = unwrappingCatch(calcResult) * }}} * - * This class differs from `scala.util.Try` in that it focuses on composing exception handlers rather than - * composing behavior. All behavior should be composed first and fed to a `Catch` object using one of the - * `opt` or `either` methods. + * Use `failAsValue` to provide a default when a specified exception is caught. + * + * {{{ + * val inputDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0) + * val candidatePick = "seven" // scala.io.StdIn.readLine() + * + * // Int = 0 + * val pick = inputDefaulting(candidatePick.toInt) + * }}} + * + * Compose multiple `Catch`s with `or` to build a `Catch` that provides default values varied by exception. + * {{{ + * val formatDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0) + * val nullDefaulting: Catch[Int] = failAsValue(classOf[NullPointerException])(-1) + * val otherDefaulting: Catch[Int] = nonFatalCatch withApply(_ => -100) + * + * val combinedDefaulting: Catch[Int] = formatDefaulting or nullDefaulting or otherDefaulting + * + * def p(s: String): Int = s.length * s.toInt + * + * // Int = 0 + * combinedDefaulting(p("tenty-nine")) + * + * // Int = -1 + * combinedDefaulting(p(null: String)) + * + * // Int = -100 + * combinedDefaulting(throw new IllegalStateException) + * + * // Int = 22 + * combinedDefaulting(p("11")) + * }}} + * + * @groupname composition-catch Catch behavior composition + * @groupprio composition-catch 10 + * @groupdesc composition-catch Build Catch objects from exception lists and catch logic + * + * @groupname composition-finally Finally behavior composition + * @groupprio composition-finally 20 + * @groupdesc composition-finally Build Catch objects from finally logic + * + * @groupname canned-behavior General purpose catch objects + * @groupprio canned-behavior 30 + * @groupdesc canned-behavior Catch objects with predefined behavior. Use combinator methods to compose additional behavior. + * + * @groupname dsl DSL behavior composition + * @groupprio dsl 40 + * @groupdesc dsl Expressive Catch behavior composition + * + * @groupname composition-catch-promiscuously Promiscuous Catch behaviors + * @groupprio composition-catch-promiscuously 50 + * @groupdesc composition-catch-promiscuously Useful if catching `ControlThrowable` or `InterruptedException` is required. + * + * @groupname logic-container Logic Containers + * @groupprio logic-container 60 + * @groupdesc logic-container Containers for catch and finally behavior. + * + * @define protectedExceptions `ControlThrowable` or `InterruptedException` * * @author Paul Phillips */ @@ -51,6 +166,7 @@ object Exception { /** !!! Not at all sure of every factor which goes into this, * and/or whether we need multiple standard variations. + * @return true if `x` is $protectedExceptions otherwise false. */ def shouldRethrow(x: Throwable): Boolean = x match { case _: ControlThrowable => true @@ -70,7 +186,9 @@ object Exception { override def toString() = name + "(" + desc + ")" } - /** A container class for finally code. */ + /** A container class for finally code. + * @group logic-container + */ class Finally private[Exception](body: => Unit) extends Described { protected val name = "Finally" @@ -87,6 +205,7 @@ object Exception { * @param pf Partial function used when applying catch logic to determine result value * @param fin Finally logic which if defined will be invoked after catch logic * @param rethrow Predicate on throwables determining when to rethrow a caught [[Throwable]] + * @group logic-container */ class Catch[+T]( val pf: Catcher[T], @@ -153,23 +272,30 @@ object Exception { final def nonFatalCatcher[T]: Catcher[T] = mkThrowableCatcher({ case NonFatal(_) => true; case _ => false }, throw _) final def allCatcher[T]: Catcher[T] = mkThrowableCatcher(_ => true, throw _) - /** The empty `Catch` object. */ + /** The empty `Catch` object. + * @group canned-behavior + **/ final val noCatch: Catch[Nothing] = new Catch(nothingCatcher) withDesc "" - /** A `Catch` object which catches everything. */ + /** A `Catch` object which catches everything. + * @group canned-behavior + **/ final def allCatch[T]: Catch[T] = new Catch(allCatcher[T]) withDesc "" - /** A `Catch` object which catches non-fatal exceptions. */ + /** A `Catch` object which catches non-fatal exceptions. + * @group canned-behavior + **/ final def nonFatalCatch[T]: Catch[T] = new Catch(nonFatalCatcher[T]) withDesc "" /** Creates a `Catch` object which will catch any of the supplied exceptions. * Since the returned `Catch` object has no specific logic defined and will simply - * rethrow the exceptions it catches, you will typically want to call `opt` or - * `either` on the return value, or assign custom logic by calling "withApply". + * rethrow the exceptions it catches, you will typically want to call `opt`, + * `either` or `withTry` on the return value, or assign custom logic by calling "withApply". * * Note that `Catch` objects automatically rethrow `ControlExceptions` and others * which should only be caught in exceptional circumstances. If you really want * to catch exactly what you specify, use `catchingPromiscuously` instead. + * @group composition-catch */ def catching[T](exceptions: Class[_]*): Catch[T] = new Catch(pfFromExceptions(exceptions : _*)) withDesc (exceptions map (_.getName) mkString ", ") @@ -178,42 +304,56 @@ object Exception { /** Creates a `Catch` object which will catch any of the supplied exceptions. * Unlike "catching" which filters out those in shouldRethrow, this one will - * catch whatever you ask of it: `ControlThrowable`, `InterruptedException`, - * `OutOfMemoryError`, you name it. + * catch whatever you ask of it including $protectedExceptions. + * @group composition-catch-promiscuously */ def catchingPromiscuously[T](exceptions: Class[_]*): Catch[T] = catchingPromiscuously(pfFromExceptions(exceptions : _*)) def catchingPromiscuously[T](c: Catcher[T]): Catch[T] = new Catch(c, None, _ => false) - /** Creates a `Catch` object which catches and ignores any of the supplied exceptions. */ + /** Creates a `Catch` object which catches and ignores any of the supplied exceptions. + * @group composition-catch + */ def ignoring(exceptions: Class[_]*): Catch[Unit] = catching(exceptions: _*) withApply (_ => ()) - /** Creates a `Catch` object which maps all the supplied exceptions to `None`. */ + /** Creates a `Catch` object which maps all the supplied exceptions to `None`. + * @group composition-catch + */ def failing[T](exceptions: Class[_]*): Catch[Option[T]] = catching(exceptions: _*) withApply (_ => None) - /** Creates a `Catch` object which maps all the supplied exceptions to the given value. */ + /** Creates a `Catch` object which maps all the supplied exceptions to the given value. + * @group composition-catch + */ 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) + } + /** Returns a partially constructed `Catch` object, which you must give - * an exception handler function as an argument to `by`. Example: + * an exception handler function as an argument to `by`. + * @example * {{{ - * handling(ex1, ex2) by (_.printStackTrace) + * handling(classOf[MalformedURLException], classOf[NullPointerException]) by (_.printStackTrace) * }}} + * @group dsl */ - class By[T,R](f: T => R) { - def by(x: T): R = f(x) - } + // TODO: Add return type def handling[T](exceptions: Class[_]*) = { def fun(f: Throwable => T) = catching(exceptions: _*) withApply f new By[Throwable => T, Catch[T]](fun _) } - /** Returns a `Catch` object with no catch logic and the argument as `Finally`. */ + /** Returns a `Catch` object with no catch logic and the argument as the finally logic. + * @group composition-finally + */ def ultimately[T](body: => Unit): Catch[T] = noCatch andFinally body - /** Creates a `Catch` object which unwraps any of the supplied exceptions. */ + /** Creates a `Catch` object which unwraps any of the supplied exceptions. + * @group composition-catch + */ def unwrapping[T](exceptions: Class[_]*): Catch[T] = { def unwrap(x: Throwable): Throwable = if (wouldMatch(x, exceptions) && x.getCause != null) unwrap(x.getCause) -- cgit v1.2.3