Class Resource<T>

java.lang.Object
dmx.fun.Resource<T>
Type Parameters:
T - the type of the managed resource value

@NullMarked public final class Resource<T> extends Object
A functional managed resource: a value that must be acquired before use and released afterward. Resource<T> is the composable alternative to try-with-resources.

Acquisition and release are declared together at construction time. The resource is only live during the execution of use(fn) — it is acquired just before the body runs, and the release function is always called when the body completes, whether it succeeds or throws.

Behaviour contract

  • If the body succeeds and release succeeds → Try.success(result).
  • If the body succeeds but release throws → Try.failure(releaseException).
  • If the body throws and release succeeds → Try.failure(bodyException).
  • If both the body and release throw → the release exception is suppressed onto the body exception (mirroring try-with-resources semantics) and Try.failure(bodyException) is returned.

Composition

  • map transforms the resource value without changing acquire/release.
  • flatMap sequences two resources; both are released in reverse acquisition order (inner first, then outer).
  • Method Details

    • of

      public static <T> Resource<T> of(CheckedSupplier<? extends T> acquire, CheckedConsumer<? super T> release)
      Creates a Resource<T> from explicit acquire and release functions.

      Example:

      Resource<Connection> conn = Resource.of(
          () -> dataSource.getConnection(),
          Connection::close
      );
      
      Type Parameters:
      T - the resource type
      Parameters:
      acquire - supplier that obtains the resource; may throw
      release - consumer that frees the resource; always called after the body
      Returns:
      a new Resource<T>
      Throws:
      NullPointerException - if acquire or release is null
    • fromAutoCloseable

      public static <T extends AutoCloseable> Resource<T> fromAutoCloseable(CheckedSupplier<? extends T> acquire)
      Creates a Resource<T> from an AutoCloseable supplier. The AutoCloseable.close() method is used as the release function.

      Example:

      Resource<BufferedReader> reader = Resource.fromAutoCloseable(
          () -> new BufferedReader(new FileReader(path))
      );
      Try<String> content = reader.use(r -> r.lines().collect(joining("\n")));
      
      Type Parameters:
      T - the resource type, must extend AutoCloseable
      Parameters:
      acquire - supplier that obtains the AutoCloseable resource; may throw
      Returns:
      a new Resource<T>
      Throws:
      NullPointerException - if acquire is null
    • eval

      public static <T> Resource<T> eval(Try<? extends T> acquired, CheckedConsumer<? super T> release)
      Creates a Resource<T> from a pre-computed Try<T> and a release function.

      If acquired is already a failure, use returns that failure immediately and the release function is never called — there is nothing to release.

      One-shot contract: because the resource value is pre-computed rather than freshly acquired on each call, invoking use() more than once on the returned Resource will call release on the same underlying value each time. If the resource is not idempotent with respect to release (e.g., a JDBC Connection or an I/O stream), calling use() more than once produces undefined behaviour. Prefer of() when reuse is required, as it acquires a fresh resource on every call.

      Example:

      Try<Connection> tryConn = Try.of(() -> dataSource.getConnection());
      Resource<Connection> conn = Resource.eval(tryConn, Connection::close);
      Try<List<User>> users = conn.use(c -> fetchUsers(c)); // call use() exactly once
      
      Type Parameters:
      T - the resource type
      Parameters:
      acquired - the pre-computed result of an acquire attempt; if failure, release is skipped
      release - consumer that frees the resource when acquired successfully
      Returns:
      a new Resource<T> backed by the pre-computed acquired value
      Throws:
      NullPointerException - if acquired or release is null
    • use

      public <R> Try<R> use(CheckedFunction<? super T, ? extends R> body)
      Acquires the resource, applies body to it, releases the resource, and returns the body's result wrapped in a Try.

      The release function is always called, even when body throws. See the class-level contract for the exact exception-merging rules.

      Type Parameters:
      R - the result type
      Parameters:
      body - function applied to the live resource; may throw
      Returns:
      Try.success(result) on success, or Try.failure(cause) on any error
      Throws:
      NullPointerException - if body is null
    • useAsResult

      public <R,E> Result<R,E> useAsResult(Function<? super T, ? extends Result<? extends R, ? extends E>> body, Function<? super Throwable, ? extends E> onError)
      Acquires the resource, applies body to produce a Result, releases the resource, and returns a Result<R, E>.

      This is the Result-integrated variant of use(). It is useful when the domain layer models failures as typed Result values rather than Throwable.

      • If acquire or release throws a Throwable, it is mapped to E via onError and returned as Result.err(e).
      • If the body returns Result.err(e), that error is returned as-is.
      • If both body and release fail, the release exception is suppressed onto the body exception and the combined throwable is passed to onError.

      Example:

      Result<List<User>, DbError> users = connResource.useAsResult(
          conn -> fetchUsers(conn),
          ex   -> new DbError.QueryFailed(ex.getMessage())
      );
      
      Type Parameters:
      R - the success type
      E - the error type
      Parameters:
      body - function applied to the live resource; returns a Result
      onError - maps any Throwable from acquire/release/body to E
      Returns:
      Result.ok(value) on success, or Result.err(error) on any failure
      Throws:
      NullPointerException - if body or onError is null
    • map

      public <R> Resource<R> map(Function<? super T, ? extends R> fn)
      Returns a new Resource<R> whose body receives the result of applying fn to the acquired value. The acquire/release lifecycle of the underlying resource is unchanged.

      If fn throws, the underlying resource is still released and the exception is captured as a Try.failure.

      Type Parameters:
      R - the mapped resource type
      Parameters:
      fn - function transforming the acquired value; must not be null
      Returns:
      a new Resource<R>
      Throws:
      NullPointerException - if fn is null
    • flatMap

      public <R> Resource<R> flatMap(Function<? super T, ? extends Resource<R>> fn)
      Sequences this resource with an inner resource derived from its value. Both resources are released in reverse acquisition order: the inner resource is released first, then this (outer) resource.

      Example — connection then prepared statement:

      Resource<PreparedStatement> stmt = connResource.flatMap(c ->
          Resource.of(
              () -> c.prepareStatement("SELECT * FROM users"),
              PreparedStatement::close
          )
      );
      Try<List<User>> result = stmt.use(ps -> mapRows(ps.executeQuery()));
      
      Type Parameters:
      R - the inner resource type
      Parameters:
      fn - function that produces the inner resource from this resource's value; must not be null and must not return null
      Returns:
      a composed Resource<R> whose lifecycle manages both resources
      Throws:
      NullPointerException - if fn is null or returns null
    • mapTry

      public <R> Resource<R> mapTry(Function<? super T, ? extends Try<? extends R>> fn)
      Returns a new Resource<R> whose value is obtained by applying a Try-returning function to the acquired value.

      This is the Try-integrated counterpart of map(). It is useful when the transformation itself is a fallible operation already wrapped in a Try (e.g., parsing, validation, or a call to a Try.of(...)-wrapped API). If fn returns a failure, the underlying resource is still released and the failure is propagated.

      Example:

      Resource<Config> config = rawTextResource.mapTry(text ->
          Try.of(() -> Config.parse(text))
      );
      Try<Integer> port = config.use(c -> c.port());
      
      Type Parameters:
      R - the mapped resource type
      Parameters:
      fn - function returning a Try<R>; must not be null or return null
      Returns:
      a new Resource<R>
      Throws:
      NullPointerException - if fn is null