@@ -13,21 +13,136 @@ package control
13
13
import scala .reflect .{ ClassTag , classTag }
14
14
import scala .language .implicitConversions
15
15
16
-
17
16
/** 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.
19
28
* {{{
20
29
* import scala.util.control.Exception._
21
30
* import java.net._
22
31
*
23
32
* 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)
26
86
* }}}
27
87
*
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`
31
146
*
32
147
* @author Paul Phillips
33
148
*/
@@ -51,6 +166,7 @@ object Exception {
51
166
52
167
/** !!! Not at all sure of every factor which goes into this,
53
168
* and/or whether we need multiple standard variations.
169
+ * @return true if `x` is $protectedExceptions otherwise false.
54
170
*/
55
171
def shouldRethrow (x : Throwable ): Boolean = x match {
56
172
case _ : ControlThrowable => true
@@ -70,7 +186,9 @@ object Exception {
70
186
override def toString () = name + " (" + desc + " )"
71
187
}
72
188
73
- /** A container class for finally code. */
189
+ /** A container class for finally code.
190
+ * @group logic-container
191
+ */
74
192
class Finally private [Exception ](body : => Unit ) extends Described {
75
193
protected val name = " Finally"
76
194
@@ -87,6 +205,7 @@ object Exception {
87
205
* @param pf Partial function used when applying catch logic to determine result value
88
206
* @param fin Finally logic which if defined will be invoked after catch logic
89
207
* @param rethrow Predicate on throwables determining when to rethrow a caught [[Throwable ]]
208
+ * @group logic-container
90
209
*/
91
210
class Catch [+ T ](
92
211
val pf : Catcher [T ],
@@ -153,23 +272,30 @@ object Exception {
153
272
final def nonFatalCatcher [T ]: Catcher [T ] = mkThrowableCatcher({ case NonFatal (_) => true ; case _ => false }, throw _)
154
273
final def allCatcher [T ]: Catcher [T ] = mkThrowableCatcher(_ => true , throw _)
155
274
156
- /** The empty `Catch` object. */
275
+ /** The empty `Catch` object.
276
+ * @group canned-behavior
277
+ **/
157
278
final val noCatch : Catch [Nothing ] = new Catch (nothingCatcher) withDesc " <nothing>"
158
279
159
- /** A `Catch` object which catches everything. */
280
+ /** A `Catch` object which catches everything.
281
+ * @group canned-behavior
282
+ **/
160
283
final def allCatch [T ]: Catch [T ] = new Catch (allCatcher[T ]) withDesc " <everything>"
161
284
162
- /** A `Catch` object which catches non-fatal exceptions. */
285
+ /** A `Catch` object which catches non-fatal exceptions.
286
+ * @group canned-behavior
287
+ **/
163
288
final def nonFatalCatch [T ]: Catch [T ] = new Catch (nonFatalCatcher[T ]) withDesc " <non-fatal>"
164
289
165
290
/** Creates a `Catch` object which will catch any of the supplied exceptions.
166
291
* 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".
169
294
*
170
295
* Note that `Catch` objects automatically rethrow `ControlExceptions` and others
171
296
* which should only be caught in exceptional circumstances. If you really want
172
297
* to catch exactly what you specify, use `catchingPromiscuously` instead.
298
+ * @group composition-catch
173
299
*/
174
300
def catching [T ](exceptions : Class [_]* ): Catch [T ] =
175
301
new Catch (pfFromExceptions(exceptions : _* )) withDesc (exceptions map (_.getName) mkString " , " )
@@ -178,42 +304,56 @@ object Exception {
178
304
179
305
/** Creates a `Catch` object which will catch any of the supplied exceptions.
180
306
* 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
183
309
*/
184
310
def catchingPromiscuously [T ](exceptions : Class [_]* ): Catch [T ] = catchingPromiscuously(pfFromExceptions(exceptions : _* ))
185
311
def catchingPromiscuously [T ](c : Catcher [T ]): Catch [T ] = new Catch (c, None , _ => false )
186
312
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
+ */
188
316
def ignoring (exceptions : Class [_]* ): Catch [Unit ] =
189
317
catching(exceptions : _* ) withApply (_ => ())
190
318
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
+ */
192
322
def failing [T ](exceptions : Class [_]* ): Catch [Option [T ]] =
193
323
catching(exceptions : _* ) withApply (_ => None )
194
324
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
+ */
196
328
def failAsValue [T ](exceptions : Class [_]* )(value : => T ): Catch [T ] =
197
329
catching(exceptions : _* ) withApply (_ => value)
198
330
331
+ class By [T ,R ](f : T => R ) {
332
+ def by (x : T ): R = f(x)
333
+ }
334
+
199
335
/** 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
201
338
* {{{
202
- * handling(ex1, ex2 ) by (_.printStackTrace)
339
+ * handling(classOf[MalformedURLException], classOf[NullPointerException] ) by (_.printStackTrace)
203
340
* }}}
341
+ * @group dsl
204
342
*/
205
- class By [T ,R ](f : T => R ) {
206
- def by (x : T ): R = f(x)
207
- }
343
+ // TODO: Add return type
208
344
def handling [T ](exceptions : Class [_]* ) = {
209
345
def fun (f : Throwable => T ) = catching(exceptions : _* ) withApply f
210
346
new By [Throwable => T , Catch [T ]](fun _)
211
347
}
212
348
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
+ */
214
352
def ultimately [T ](body : => Unit ): Catch [T ] = noCatch andFinally body
215
353
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
+ */
217
357
def unwrapping [T ](exceptions : Class [_]* ): Catch [T ] = {
218
358
def unwrap (x : Throwable ): Throwable =
219
359
if (wouldMatch(x, exceptions) && x.getCause != null ) unwrap(x.getCause)
0 commit comments