@@ -13,21 +13,136 @@ package control
1313import scala .reflect .{ ClassTag , classTag }
1414import scala .language .implicitConversions
1515
16-
1716/** Classes representing the components of exception handling.
18- * Each class is independently composable. Some example usages:
17+ *
18+ * Each class is independently composable.
19+ *
20+ * This class differs from [[scala.util.Try ]] in that it focuses on composing exception handlers rather than
21+ * composing behavior. All behavior should be composed first and fed to a [[Catch ]] object using one of the
22+ * `opt`, `either` or `withTry` methods. Taken together the classes provide a DSL for composing catch and finally
23+ * behaviors.
24+ *
25+ * === Examples ===
26+ *
27+ * Create a `Catch` which handles specified exceptions.
1928 * {{{
2029 * import scala.util.control.Exception._
2130 * import java.net._
2231 *
2332 * val s = "http://www.scala-lang.org/"
24- * val x1 = catching(classOf[MalformedURLException]) opt new URL(s)
25- * val x2 = catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s)
33+ *
34+ * // Some(http://www.scala-lang.org/)
35+ * val x1: Option[URL] = catching(classOf[MalformedURLException]) opt new URL(s)
36+ *
37+ * // Right(http://www.scala-lang.org/)
38+ * val x2: Either[Throwable,URL] =
39+ * catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s)
40+ *
41+ * // Success(http://www.scala-lang.org/)
42+ * val x3: Try[URL] = catching(classOf[MalformedURLException], classOf[NullPointerException]) withTry new URL(s)
43+ *
44+ * val defaultUrl = new URL("http://example.com")
45+ * // URL(http://example.com) because htt/xx throws MalformedURLException
46+ * val x4: URL = failAsValue(classOf[MalformedURLException])(defaultUrl)(new URL("htt/xx"))
47+ * }}}
48+ *
49+ * Create a `Catch` which logs exceptions using `handling` and `by`.
50+ * {{{
51+ * def log(t: Throwable): Unit = t.printStackTrace
52+ *
53+ * val withThrowableLogging: Catch[Unit] = handling(classOf[MalformedURLException]) by (log)
54+ *
55+ * def printUrl(url: String) : Unit = {
56+ * val con = new URL(url) openConnection()
57+ * val source = scala.io.Source.fromInputStream(con.getInputStream())
58+ * source.getLines.foreach(println)
59+ * }
60+ *
61+ * val badUrl = "htt/xx"
62+ * // Prints stacktrace,
63+ * // java.net.MalformedURLException: no protocol: htt/xx
64+ * // at java.net.URL.<init>(URL.java:586)
65+ * withThrowableLogging { printUrl(badUrl) }
66+ *
67+ * val goodUrl = "http://www.scala-lang.org/"
68+ * // Prints page content,
69+ * // <!DOCTYPE html>
70+ * // <html>
71+ * withThrowableLogging { printUrl(goodUrl) }
72+ * }}}
73+ *
74+ * Use `unwrapping` to create a `Catch` that unwraps exceptions before rethrowing.
75+ * {{{
76+ * class AppException(cause: Throwable) extends RuntimeException(cause)
77+ *
78+ * val unwrappingCatch: Catch[Nothing] = unwrapping(classOf[AppException])
79+ *
80+ * def calcResult: Int = throw new AppException(new NullPointerException)
81+ *
82+ * // Throws NPE not AppException,
83+ * // java.lang.NullPointerException
84+ * // at .calcResult(<console>:17)
85+ * val result = unwrappingCatch(calcResult)
2686 * }}}
2787 *
28- * This class differs from `scala.util.Try` in that it focuses on composing exception handlers rather than
29- * composing behavior. All behavior should be composed first and fed to a `Catch` object using one of the
30- * `opt` or `either` methods.
88+ * Use `failAsValue` to provide a default when a specified exception is caught.
89+ *
90+ * {{{
91+ * val inputDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0)
92+ * val candidatePick = "seven" // scala.io.StdIn.readLine()
93+ *
94+ * // Int = 0
95+ * val pick = inputDefaulting(candidatePick.toInt)
96+ * }}}
97+ *
98+ * Compose multiple `Catch`s with `or` to build a `Catch` that provides default values varied by exception.
99+ * {{{
100+ * val formatDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0)
101+ * val nullDefaulting: Catch[Int] = failAsValue(classOf[NullPointerException])(-1)
102+ * val otherDefaulting: Catch[Int] = nonFatalCatch withApply(_ => -100)
103+ *
104+ * val combinedDefaulting: Catch[Int] = formatDefaulting or nullDefaulting or otherDefaulting
105+ *
106+ * def p(s: String): Int = s.length * s.toInt
107+ *
108+ * // Int = 0
109+ * combinedDefaulting(p("tenty-nine"))
110+ *
111+ * // Int = -1
112+ * combinedDefaulting(p(null: String))
113+ *
114+ * // Int = -100
115+ * combinedDefaulting(throw new IllegalStateException)
116+ *
117+ * // Int = 22
118+ * combinedDefaulting(p("11"))
119+ * }}}
120+ *
121+ * @groupname composition-catch Catch behavior composition
122+ * @groupprio composition-catch 10
123+ * @groupdesc composition-catch Build Catch objects from exception lists and catch logic
124+ *
125+ * @groupname composition-finally Finally behavior composition
126+ * @groupprio composition-finally 20
127+ * @groupdesc composition-finally Build Catch objects from finally logic
128+ *
129+ * @groupname canned-behavior General purpose catch objects
130+ * @groupprio canned-behavior 30
131+ * @groupdesc canned-behavior Catch objects with predefined behavior. Use combinator methods to compose additional behavior.
132+ *
133+ * @groupname dsl DSL behavior composition
134+ * @groupprio dsl 40
135+ * @groupdesc dsl Expressive Catch behavior composition
136+ *
137+ * @groupname composition-catch-promiscuously Promiscuous Catch behaviors
138+ * @groupprio composition-catch-promiscuously 50
139+ * @groupdesc composition-catch-promiscuously Useful if catching `ControlThrowable` or `InterruptedException` is required.
140+ *
141+ * @groupname logic-container Logic Containers
142+ * @groupprio logic-container 60
143+ * @groupdesc logic-container Containers for catch and finally behavior.
144+ *
145+ * @define protectedExceptions `ControlThrowable` or `InterruptedException`
31146 *
32147 * @author Paul Phillips
33148 */
@@ -51,6 +166,7 @@ object Exception {
51166
52167 /** !!! Not at all sure of every factor which goes into this,
53168 * and/or whether we need multiple standard variations.
169+ * @return true if `x` is $protectedExceptions otherwise false.
54170 */
55171 def shouldRethrow (x : Throwable ): Boolean = x match {
56172 case _ : ControlThrowable => true
@@ -70,7 +186,9 @@ object Exception {
70186 override def toString () = name + " (" + desc + " )"
71187 }
72188
73- /** A container class for finally code. */
189+ /** A container class for finally code.
190+ * @group logic-container
191+ */
74192 class Finally private [Exception ](body : => Unit ) extends Described {
75193 protected val name = " Finally"
76194
@@ -87,6 +205,7 @@ object Exception {
87205 * @param pf Partial function used when applying catch logic to determine result value
88206 * @param fin Finally logic which if defined will be invoked after catch logic
89207 * @param rethrow Predicate on throwables determining when to rethrow a caught [[Throwable ]]
208+ * @group logic-container
90209 */
91210 class Catch [+ T ](
92211 val pf : Catcher [T ],
@@ -153,23 +272,30 @@ object Exception {
153272 final def nonFatalCatcher [T ]: Catcher [T ] = mkThrowableCatcher({ case NonFatal (_) => true ; case _ => false }, throw _)
154273 final def allCatcher [T ]: Catcher [T ] = mkThrowableCatcher(_ => true , throw _)
155274
156- /** The empty `Catch` object. */
275+ /** The empty `Catch` object.
276+ * @group canned-behavior
277+ **/
157278 final val noCatch : Catch [Nothing ] = new Catch (nothingCatcher) withDesc " <nothing>"
158279
159- /** A `Catch` object which catches everything. */
280+ /** A `Catch` object which catches everything.
281+ * @group canned-behavior
282+ **/
160283 final def allCatch [T ]: Catch [T ] = new Catch (allCatcher[T ]) withDesc " <everything>"
161284
162- /** A `Catch` object which catches non-fatal exceptions. */
285+ /** A `Catch` object which catches non-fatal exceptions.
286+ * @group canned-behavior
287+ **/
163288 final def nonFatalCatch [T ]: Catch [T ] = new Catch (nonFatalCatcher[T ]) withDesc " <non-fatal>"
164289
165290 /** Creates a `Catch` object which will catch any of the supplied exceptions.
166291 * Since the returned `Catch` object has no specific logic defined and will simply
167- * rethrow the exceptions it catches, you will typically want to call `opt` or
168- * `either` on the return value, or assign custom logic by calling "withApply".
292+ * rethrow the exceptions it catches, you will typically want to call `opt`,
293+ * `either` or `withTry` on the return value, or assign custom logic by calling "withApply".
169294 *
170295 * Note that `Catch` objects automatically rethrow `ControlExceptions` and others
171296 * which should only be caught in exceptional circumstances. If you really want
172297 * to catch exactly what you specify, use `catchingPromiscuously` instead.
298+ * @group composition-catch
173299 */
174300 def catching [T ](exceptions : Class [_]* ): Catch [T ] =
175301 new Catch (pfFromExceptions(exceptions : _* )) withDesc (exceptions map (_.getName) mkString " , " )
@@ -178,42 +304,56 @@ object Exception {
178304
179305 /** Creates a `Catch` object which will catch any of the supplied exceptions.
180306 * Unlike "catching" which filters out those in shouldRethrow, this one will
181- * catch whatever you ask of it: `ControlThrowable`, `InterruptedException`,
182- * `OutOfMemoryError`, you name it.
307+ * catch whatever you ask of it including $protectedExceptions.
308+ * @group composition-catch-promiscuously
183309 */
184310 def catchingPromiscuously [T ](exceptions : Class [_]* ): Catch [T ] = catchingPromiscuously(pfFromExceptions(exceptions : _* ))
185311 def catchingPromiscuously [T ](c : Catcher [T ]): Catch [T ] = new Catch (c, None , _ => false )
186312
187- /** Creates a `Catch` object which catches and ignores any of the supplied exceptions. */
313+ /** Creates a `Catch` object which catches and ignores any of the supplied exceptions.
314+ * @group composition-catch
315+ */
188316 def ignoring (exceptions : Class [_]* ): Catch [Unit ] =
189317 catching(exceptions : _* ) withApply (_ => ())
190318
191- /** Creates a `Catch` object which maps all the supplied exceptions to `None`. */
319+ /** Creates a `Catch` object which maps all the supplied exceptions to `None`.
320+ * @group composition-catch
321+ */
192322 def failing [T ](exceptions : Class [_]* ): Catch [Option [T ]] =
193323 catching(exceptions : _* ) withApply (_ => None )
194324
195- /** Creates a `Catch` object which maps all the supplied exceptions to the given value. */
325+ /** Creates a `Catch` object which maps all the supplied exceptions to the given value.
326+ * @group composition-catch
327+ */
196328 def failAsValue [T ](exceptions : Class [_]* )(value : => T ): Catch [T ] =
197329 catching(exceptions : _* ) withApply (_ => value)
198330
331+ class By [T ,R ](f : T => R ) {
332+ def by (x : T ): R = f(x)
333+ }
334+
199335 /** Returns a partially constructed `Catch` object, which you must give
200- * an exception handler function as an argument to `by`. Example:
336+ * an exception handler function as an argument to `by`.
337+ * @example
201338 * {{{
202- * handling(ex1, ex2 ) by (_.printStackTrace)
339+ * handling(classOf[MalformedURLException], classOf[NullPointerException] ) by (_.printStackTrace)
203340 * }}}
341+ * @group dsl
204342 */
205- class By [T ,R ](f : T => R ) {
206- def by (x : T ): R = f(x)
207- }
343+ // TODO: Add return type
208344 def handling [T ](exceptions : Class [_]* ) = {
209345 def fun (f : Throwable => T ) = catching(exceptions : _* ) withApply f
210346 new By [Throwable => T , Catch [T ]](fun _)
211347 }
212348
213- /** Returns a `Catch` object with no catch logic and the argument as `Finally`. */
349+ /** Returns a `Catch` object with no catch logic and the argument as the finally logic.
350+ * @group composition-finally
351+ */
214352 def ultimately [T ](body : => Unit ): Catch [T ] = noCatch andFinally body
215353
216- /** Creates a `Catch` object which unwraps any of the supplied exceptions. */
354+ /** Creates a `Catch` object which unwraps any of the supplied exceptions.
355+ * @group composition-catch
356+ */
217357 def unwrapping [T ](exceptions : Class [_]* ): Catch [T ] = {
218358 def unwrap (x : Throwable ): Throwable =
219359 if (wouldMatch(x, exceptions) && x.getCause != null ) unwrap(x.getCause)
0 commit comments