package jresource | |
import cats.data.Xor | |
import scala.util.control.NonFatal | |
object JResource { | |
type FinalActions = List[() => Unit] | |
final case class FinalActionsExecutionThrew(throwables: Seq[Throwable]) extends Exception | |
sealed abstract class ManagedResource[R] { | |
def map[B](f: (R) => B): ManagedResource[B] = { | |
new MappedManagedResource[R, B](this, f) | |
} | |
def flatMap[B](f: R => ManagedResource[B]): ManagedResource[B] | |
def acquireAndGet[B](f: R => B): B | |
protected def openAndReturnWithFinalActions(previousFinalActions: FinalActions): (R, FinalActions) | |
} | |
class DefaultManagedResource[R: Resource](resource: R) extends ManagedResource[R] { | |
override def map[B](f: (R) => B): ManagedResource[B] = { | |
new MappedManagedResource[R, B](this, f) | |
} | |
override def flatMap[B](f: (R) => ManagedResource[B]): ManagedResource[B] = { | |
new SequencedManagedResource[R, B](this, f) | |
} | |
override def acquireAndGet[B](f: (R) => B): B = { | |
try { | |
implicitly[Resource[R]].open(resource) | |
f(resource) | |
} finally { | |
implicitly[Resource[R]].close(resource) | |
} | |
} | |
override protected def openAndReturnWithFinalActions(previousFinalActions: FinalActions): (R, FinalActions) = { | |
val newFinalActions = (() => implicitly[Resource[R]].close(resource)) :: previousFinalActions | |
try { | |
implicitly[Resource[R]].open(resource) | |
(resource, newFinalActions) | |
} catch { | |
case NonFatal(nonFatal) => | |
executeFinalActions(newFinalActions) | |
throw nonFatal | |
} | |
} | |
} | |
class MappedManagedResource[R: Resource, B](managedResource: ManagedResource[R], f: R => B) extends ManagedResource[B] { | |
override def map[C](g: (B) => C): ManagedResource[C] = { | |
new MappedManagedResource[R, C](managedResource, f andThen g) | |
} | |
override def flatMap[C](g: (B) => ManagedResource[C]): ManagedResource[C] = { | |
new SequencedManagedResource[B, C](this, r => g(f(r))) | |
} | |
override def acquireAndGet[C](g: (B) => C): C = { | |
managedResource.acquireAndGet(f andThen g) | |
} | |
override protected def openAndReturnWithFinalActions(previousFinalActions: FinalActions): (B, FinalActions) = { | |
val (r, finalActions) = managedResource.openAndReturnWithFinalActions(previousFinalActions) | |
try { | |
(f(r), finalActions) | |
} catch { | |
case NonFatal(nonFatal) => | |
executeFinalActions(finalActions) | |
throw nonFatal | |
} | |
} | |
} | |
class SequencedManagedResource[R, C](managedResource: ManagedResource[R], f: R => ManagedResource[C]) extends ManagedResource[C] { | |
override def flatMap[D](g: (C) => ManagedResource[D]): ManagedResource[D] = { | |
new SequencedManagedResource[C, D](this, g) | |
} | |
override def acquireAndGet[D](g: (C) => D): D = { | |
val (b, bClosingActions) = managedResource.openAndReturnWithFinalActions(List.empty) | |
val (c, cClosingActions) = try { | |
f(b).openAndReturnWithFinalActions(bClosingActions) | |
} catch { | |
case NonFatal(nonFatal) => | |
executeFinalActions(bClosingActions) | |
throw nonFatal | |
} | |
try { | |
g(f(c)) | |
} finally { | |
executeFinalActions(cClosingActions) | |
} | |
} | |
override protected def openAndReturnWithFinalActions(previousFinalActions: FinalActions): (C, FinalActions) = { | |
val (b, bClosingActions) = managedResource.openAndReturnWithFinalActions(List.empty) | |
try { | |
f(b).openAndReturnWithFinalActions(bClosingActions) | |
} catch { | |
case NonFatal(nonFatal) => | |
bClosingActions.foreach(action => action()) | |
throw nonFatal | |
} | |
} | |
} | |
private def executeFinalActions(finalActions: FinalActions): Unit = { | |
val thrownThrowables = finalActions.map(action => Xor.catchNonFatal(action())).collect { case Xor.Left(throwable) => throwable } | |
if (thrownThrowables.nonEmpty) { | |
throw FinalActionsExecutionThrew(thrownThrowables) | |
} | |
} | |
trait Resource[R] { | |
def open(r: R): Unit = { | |
() | |
} | |
def close(r: R): Unit | |
} | |
type HasCloseMethod = {def close(): Unit} | |
implicit def hasCloseMethodToResource[HCM <: HasCloseMethod]: Resource[HCM] = { | |
new Resource[HCM] { | |
override def close(r: HCM): Unit = { | |
r.close() | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment