Skip to content

Commit 9f86f4d

Browse files
committed
add scala.util.Using from stdlib for 2.12- builds
1 parent d87b17d commit 9f86f4d

File tree

1 file changed

+395
-0
lines changed

1 file changed

+395
-0
lines changed

os/src-2.12-/Using.scala

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.util
14+
15+
import scala.util.control.{ControlThrowable, NonFatal}
16+
17+
/** A utility for performing automatic resource management. It can be used to perform an
18+
* operation using resources, after which it releases the resources in reverse order
19+
* of their creation.
20+
*
21+
* ==Usage==
22+
*
23+
* There are multiple ways to automatically manage resources with `Using`. If you only need
24+
* to manage a single resource, the [[Using.apply `apply`]] method is easiest; it wraps the
25+
* resource opening, operation, and resource releasing in a `Try`.
26+
*
27+
* Example:
28+
* {{{
29+
* import java.io.{BufferedReader, FileReader}
30+
* import scala.util.{Try, Using}
31+
*
32+
* val lines: Try[Seq[String]] =
33+
* Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
34+
* Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq
35+
* }
36+
* }}}
37+
*
38+
* If you need to manage multiple resources, [[Using.Manager$.apply `Using.Manager`]] should
39+
* be used. It allows the managing of arbitrarily many resources, whose creation, use, and
40+
* release are all wrapped in a `Try`.
41+
*
42+
* Example:
43+
* {{{
44+
* import java.io.{BufferedReader, FileReader}
45+
* import scala.util.{Try, Using}
46+
*
47+
* val lines: Try[Seq[String]] = Using.Manager { use =>
48+
* val r1 = use(new BufferedReader(new FileReader("file1.txt")))
49+
* val r2 = use(new BufferedReader(new FileReader("file2.txt")))
50+
* val r3 = use(new BufferedReader(new FileReader("file3.txt")))
51+
* val r4 = use(new BufferedReader(new FileReader("file4.txt")))
52+
*
53+
* // use your resources here
54+
* def lines(reader: BufferedReader): Iterator[String] =
55+
* Iterator.continually(reader.readLine()).takeWhile(_ != null)
56+
*
57+
* (lines(r1) ++ lines(r2) ++ lines(r3) ++ lines(r4)).toList
58+
* }
59+
* }}}
60+
*
61+
* If you wish to avoid wrapping management and operations in a `Try`, you can use
62+
* [[Using.resource `Using.resource`]], which throws any exceptions that occur.
63+
*
64+
* Example:
65+
* {{{
66+
* import java.io.{BufferedReader, FileReader}
67+
* import scala.util.Using
68+
*
69+
* val lines: Seq[String] =
70+
* Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
71+
* Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq
72+
* }
73+
* }}}
74+
*
75+
* ==Suppression Behavior==
76+
*
77+
* If two exceptions are thrown (e.g., by an operation and closing a resource),
78+
* one of them is re-thrown, and the other is
79+
* [[java.lang.Throwable#addSuppressed added to it as a suppressed exception]].
80+
* If the two exceptions are of different 'severities' (see below), the one of a higher
81+
* severity is re-thrown, and the one of a lower severity is added to it as a suppressed
82+
* exception. If the two exceptions are of the same severity, the one thrown first is
83+
* re-thrown, and the one thrown second is added to it as a suppressed exception.
84+
* If an exception is a [[scala.util.control.ControlThrowable `ControlThrowable`]], or
85+
* if it does not support suppression (see
86+
* [[java.lang.Throwable `Throwable`'s constructor with an `enableSuppression` parameter]]),
87+
* an exception that would have been suppressed is instead discarded.
88+
*
89+
* Exceptions are ranked from highest to lowest severity as follows:
90+
* - `java.lang.VirtualMachineError`
91+
* - `java.lang.LinkageError`
92+
* - `java.lang.InterruptedException` and `java.lang.ThreadDeath`
93+
* - [[scala.util.control.NonFatal fatal exceptions]], excluding `scala.util.control.ControlThrowable`
94+
* - `scala.util.control.ControlThrowable`
95+
* - all other exceptions
96+
*
97+
* When more than two exceptions are thrown, the first two are combined and
98+
* re-thrown as described above, and each successive exception thrown is combined
99+
* as it is thrown.
100+
*
101+
* @define suppressionBehavior See the main doc for [[Using `Using`]] for full details of
102+
* suppression behavior.
103+
*/
104+
object Using {
105+
/** Performs an operation using a resource, and then releases the resource,
106+
* even if the operation throws an exception.
107+
*
108+
* $suppressionBehavior
109+
*
110+
* @return a [[Try]] containing an exception if one or more were thrown,
111+
* or the result of the operation if no exceptions were thrown
112+
*/
113+
def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A] = Try { Using.resource(resource)(f) }
114+
115+
/** A resource manager.
116+
*
117+
* Resources can be registered with the manager by calling [[acquire `acquire`]];
118+
* such resources will be released in reverse order of their acquisition
119+
* when the manager is closed, regardless of any exceptions thrown
120+
* during use.
121+
*
122+
* $suppressionBehavior
123+
*
124+
* @note It is recommended for API designers to require an implicit `Manager`
125+
* for the creation of custom resources, and to call `acquire` during those
126+
* resources' construction. Doing so guarantees that the resource ''must'' be
127+
* automatically managed, and makes it impossible to forget to do so.
128+
*
129+
*
130+
* Example:
131+
* {{{
132+
* class SafeFileReader(file: File)(implicit manager: Using.Manager)
133+
* extends BufferedReader(new FileReader(file)) {
134+
*
135+
* def this(fileName: String)(implicit manager: Using.Manager) = this(new File(fileName))
136+
*
137+
* manager.acquire(this)
138+
* }
139+
* }}}
140+
*/
141+
final class Manager private {
142+
import Manager._
143+
144+
private var closed = false
145+
private[this] var resources: List[Resource[_]] = Nil
146+
147+
/** Registers the specified resource with this manager, so that
148+
* the resource is released when the manager is closed, and then
149+
* returns the (unmodified) resource.
150+
*/
151+
def apply[R: Releasable](resource: R): R = {
152+
acquire(resource)
153+
resource
154+
}
155+
156+
/** Registers the specified resource with this manager, so that
157+
* the resource is released when the manager is closed.
158+
*/
159+
def acquire[R: Releasable](resource: R): Unit = {
160+
if (resource == null) throw new NullPointerException("null resource")
161+
if (closed) throw new IllegalStateException("Manager has already been closed")
162+
resources = new Resource(resource) :: resources
163+
}
164+
165+
private def manage[A](op: Manager => A): A = {
166+
var toThrow: Throwable = null
167+
try {
168+
op(this)
169+
} catch {
170+
case t: Throwable =>
171+
toThrow = t
172+
null.asInstanceOf[A] // compiler doesn't know `finally` will throw
173+
} finally {
174+
closed = true
175+
var rs = resources
176+
resources = null // allow GC, in case something is holding a reference to `this`
177+
while (rs.nonEmpty) {
178+
val resource = rs.head
179+
rs = rs.tail
180+
try resource.release()
181+
catch {
182+
case t: Throwable =>
183+
if (toThrow == null) toThrow = t
184+
else toThrow = preferentiallySuppress(toThrow, t)
185+
}
186+
}
187+
if (toThrow != null) throw toThrow
188+
}
189+
}
190+
}
191+
192+
object Manager {
193+
/** Performs an operation using a `Manager`, then closes the `Manager`,
194+
* releasing its resources (in reverse order of acquisition).
195+
*
196+
* Example:
197+
* {{{
198+
* val lines = Using.Manager { use =>
199+
* use(new BufferedReader(new FileReader("file.txt"))).lines()
200+
* }
201+
* }}}
202+
*
203+
* If using resources which require an implicit `Manager` as a parameter,
204+
* this method should be invoked with an `implicit` modifier before the function
205+
* parameter:
206+
*
207+
* Example:
208+
* {{{
209+
* val lines = Using.Manager { implicit use =>
210+
* new SafeFileReader("file.txt").lines()
211+
* }
212+
* }}}
213+
*
214+
* See the main doc for [[Using `Using`]] for full details of suppression behavior.
215+
*
216+
* @param op the operation to perform using the manager
217+
* @tparam A the return type of the operation
218+
* @return a [[Try]] containing an exception if one or more were thrown,
219+
* or the result of the operation if no exceptions were thrown
220+
*/
221+
def apply[A](op: Manager => A): Try[A] = Try { (new Manager).manage(op) }
222+
223+
private final class Resource[R](resource: R)(implicit releasable: Releasable[R]) {
224+
def release(): Unit = releasable.release(resource)
225+
}
226+
}
227+
228+
private def preferentiallySuppress(primary: Throwable, secondary: Throwable): Throwable = {
229+
def score(t: Throwable): Int = t match {
230+
case _: VirtualMachineError => 4
231+
case _: LinkageError => 3
232+
case _: InterruptedException | _: ThreadDeath => 2
233+
case _: ControlThrowable => 0
234+
case e if !NonFatal(e) => 1 // in case this method gets out of sync with NonFatal
235+
case _ => -1
236+
}
237+
@inline def suppress(t: Throwable, suppressed: Throwable): Throwable = { t.addSuppressed(suppressed); t }
238+
239+
if (score(secondary) > score(primary)) suppress(secondary, primary)
240+
else suppress(primary, secondary)
241+
}
242+
243+
/** Performs an operation using a resource, and then releases the resource,
244+
* even if the operation throws an exception. This method behaves similarly
245+
* to Java's try-with-resources.
246+
*
247+
* $suppressionBehavior
248+
*
249+
* @param resource the resource
250+
* @param body the operation to perform with the resource
251+
* @tparam R the type of the resource
252+
* @tparam A the return type of the operation
253+
* @return the result of the operation, if neither the operation nor
254+
* releasing the resource throws
255+
*/
256+
def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A = {
257+
if (resource == null) throw new NullPointerException("null resource")
258+
259+
var toThrow: Throwable = null
260+
try {
261+
body(resource)
262+
} catch {
263+
case t: Throwable =>
264+
toThrow = t
265+
null.asInstanceOf[A] // compiler doesn't know `finally` will throw
266+
} finally {
267+
if (toThrow eq null) releasable.release(resource)
268+
else {
269+
try releasable.release(resource)
270+
catch { case other: Throwable => toThrow = preferentiallySuppress(toThrow, other) }
271+
finally throw toThrow
272+
}
273+
}
274+
}
275+
276+
/** Performs an operation using two resources, and then releases the resources
277+
* in reverse order, even if the operation throws an exception. This method
278+
* behaves similarly to Java's try-with-resources.
279+
*
280+
* $suppressionBehavior
281+
*
282+
* @param resource1 the first resource
283+
* @param resource2 the second resource
284+
* @param body the operation to perform using the resources
285+
* @tparam R1 the type of the first resource
286+
* @tparam R2 the type of the second resource
287+
* @tparam A the return type of the operation
288+
* @return the result of the operation, if neither the operation nor
289+
* releasing the resources throws
290+
*/
291+
def resources[R1: Releasable, R2: Releasable, A](
292+
resource1: R1,
293+
resource2: => R2
294+
)(body: (R1, R2) => A
295+
): A =
296+
resource(resource1) { r1 =>
297+
resource(resource2) { r2 =>
298+
body(r1, r2)
299+
}
300+
}
301+
302+
/** Performs an operation using three resources, and then releases the resources
303+
* in reverse order, even if the operation throws an exception. This method
304+
* behaves similarly to Java's try-with-resources.
305+
*
306+
* $suppressionBehavior
307+
*
308+
* @param resource1 the first resource
309+
* @param resource2 the second resource
310+
* @param resource3 the third resource
311+
* @param body the operation to perform using the resources
312+
* @tparam R1 the type of the first resource
313+
* @tparam R2 the type of the second resource
314+
* @tparam R3 the type of the third resource
315+
* @tparam A the return type of the operation
316+
* @return the result of the operation, if neither the operation nor
317+
* releasing the resources throws
318+
*/
319+
def resources[R1: Releasable, R2: Releasable, R3: Releasable, A](
320+
resource1: R1,
321+
resource2: => R2,
322+
resource3: => R3
323+
)(body: (R1, R2, R3) => A
324+
): A =
325+
resource(resource1) { r1 =>
326+
resource(resource2) { r2 =>
327+
resource(resource3) { r3 =>
328+
body(r1, r2, r3)
329+
}
330+
}
331+
}
332+
333+
/** Performs an operation using four resources, and then releases the resources
334+
* in reverse order, even if the operation throws an exception. This method
335+
* behaves similarly to Java's try-with-resources.
336+
*
337+
* $suppressionBehavior
338+
*
339+
* @param resource1 the first resource
340+
* @param resource2 the second resource
341+
* @param resource3 the third resource
342+
* @param resource4 the fourth resource
343+
* @param body the operation to perform using the resources
344+
* @tparam R1 the type of the first resource
345+
* @tparam R2 the type of the second resource
346+
* @tparam R3 the type of the third resource
347+
* @tparam R4 the type of the fourth resource
348+
* @tparam A the return type of the operation
349+
* @return the result of the operation, if neither the operation nor
350+
* releasing the resources throws
351+
*/
352+
def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A](
353+
resource1: R1,
354+
resource2: => R2,
355+
resource3: => R3,
356+
resource4: => R4
357+
)(body: (R1, R2, R3, R4) => A
358+
): A =
359+
resource(resource1) { r1 =>
360+
resource(resource2) { r2 =>
361+
resource(resource3) { r3 =>
362+
resource(resource4) { r4 =>
363+
body(r1, r2, r3, r4)
364+
}
365+
}
366+
}
367+
}
368+
369+
/** A type class describing how to release a particular type of resource.
370+
*
371+
* A resource is anything which needs to be released, closed, or otherwise cleaned up
372+
* in some way after it is finished being used, and for which waiting for the object's
373+
* garbage collection to be cleaned up would be unacceptable. For example, an instance of
374+
* [[java.io.OutputStream]] would be considered a resource, because it is important to close
375+
* the stream after it is finished being used.
376+
*
377+
* An instance of `Releasable` is needed in order to automatically manage a resource
378+
* with [[Using `Using`]]. An implicit instance is provided for all types extending
379+
* [[java.lang.AutoCloseable]].
380+
*
381+
* @tparam R the type of the resource
382+
*/
383+
trait Releasable[-R] {
384+
/** Releases the specified resource. */
385+
def release(resource: R): Unit
386+
}
387+
388+
object Releasable {
389+
/** An implicit `Releasable` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */
390+
implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
391+
def release(resource: AutoCloseable): Unit = resource.close()
392+
}
393+
}
394+
395+
}

0 commit comments

Comments
 (0)