All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tonivade.purefun.effect.Managed Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2021, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.effect;

import static com.github.tonivade.purefun.Consumer1.noop;
import static com.github.tonivade.purefun.Function1.identity;
import static com.github.tonivade.purefun.Precondition.checkNonNull;

import java.time.Duration;

import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Nothing;
import com.github.tonivade.purefun.Tuple;
import com.github.tonivade.purefun.Tuple2;
import com.github.tonivade.purefun.type.Either;

@HigherKind
public final class Managed implements ManagedOf {

  private final PureIO>> resource;

  protected Managed(PureIO>> resource) {
    this.resource = checkNonNull(resource);
  }
  
  public  Managed map(Function1 mapper) {
    return flatMap(a -> pure(PureIO.pure(mapper.apply(a))));
  }
  
  public  Managed mapError(Function1 mapper) {
    return new Managed<>(resource.mapError(mapper));
  }
  
  public  Managed flatMap(Function1> mapper) {
    PureIO>> result = resource.flatMap(t -> {
      Managed apply = ManagedOf.narrowK(mapper.apply(t.get1()));
      return apply.resource.map(r -> r.map2(ignore -> releaseAndThen(t, r)));
    });
    return new Managed<>(result);
  }

  public  Managed flatMapError(Function1> mapper) {
    return new Managed<>(resource.flatMapError(e -> ManagedOf.narrowK(mapper.apply(e)).resource));
  }
  
  public  Managed andThen(Managed other) {
    PureIO>> flatMap = resource.flatMap(a -> {
      Either>> next = other.resource.provide(a.get1());
      return PureIO.fromEither(() -> next.map(t -> t.map2(ignore -> releaseAndThen(a, t))));
    });
    return new Managed<>(flatMap);
  }
  
  public  PureIO use(Function1> use) {
    return PureIO.bracket(resource, a -> use.apply(a.get1()), release());
  }
  
  public  Managed fold(
      Function1 mapError, Function1 mapper) {
    return foldM(
        mapError.andThen(Managed::pure),
        mapper.andThen(Managed::pure));
  }

  public Managed recover(Function1 mapError) {
    return fold(mapError, identity());
  }

  public Managed orElse(Managed other) {
    return foldM(Function1.cons(other), Function1.cons(this));
  }
  
  public  Managed foldM(
      Function1> mapError, 
      Function1> mapper) {
    PureIO>> foldM = 
        resource.foldM(
            error -> ManagedOf.narrowK(mapError.apply(error)).resource,
            a -> ManagedOf.narrowK(mapper.apply(a.get1())).resource.map(b -> b.map2(ignore -> releaseAndThen(a, b))));
    return new Managed<>(foldM);
  }
  
  public  Managed> combine(Managed other) {
    return new Managed<>(PureIO.bracket(resource,
        t -> PureIO.bracket(other.resource,
          r -> PureIO.pure(Tuple.of(Tuple.of(t.get1(), r.get1()), noop())), 
          release()), 
        release()));
  }
  
  public  Managed> either(Managed other) {
    PureIO>, Tuple2>>> foldM = 
        this.resource.foldM(
            error -> other.resource.map(Either::right), 
            success -> PureIO.pure(Either.left(success)));
    
    return new Managed<>(foldM.map(
        e -> e.fold(
            a -> a.map(Either::left, x -> either -> Managed.release().accept(a)), 
            b -> b.map(Either::right, y -> either -> Managed.release().accept(b)))
        ));
  }
  
  public Managed retry() {
    return retry(1);
  }
  
  public Managed retry(int maxRetries) {
    return retry(Schedule.recurs(maxRetries));
  }
  
  public Managed retry(Duration delay) {
    return retry(delay, 1);
  }
  
  public Managed retry(Duration delay, int maxRetries) {
    return retry(Schedule.recursSpaced(delay, maxRetries));
  }
  
  public  Managed retry(Schedule schedule) {
    return new Managed<>(resource.retry(schedule));
  }
  
  public Managed> timed() {
    return new Managed<>(resource.timed().map(
        tt -> Tuple.of(Tuple.of(tt.get1(), tt.get2().get1()), t -> tt.get2().get2().accept(t.get2()))));
  }
  
  public static  Managed pure(A resource) {
    return pure(PureIO.pure(resource));
  }
  
  public static  Managed pure(PureIO resource) {
    return from(resource, noop());
  }
  
  public static  Managed from(PureIO resource) {
    return from(resource, AutoCloseable::close);
  }
  
  public static  Managed from(PureIO resource, Consumer1 release) {
    return new Managed<>(resource.map(a -> Tuple.of(a, release)));
  }
  
  public static  Managed from(Function1 mapper) {
    return from(mapper, AutoCloseable::close);
  }
  
  public static  Managed from(Function1 mapper, Consumer1 release) {
    return new Managed<>(PureIO.access(mapper).map(y -> Tuple.of(y, release)));
  }
  
  public static  Managed fromM(Function1> mapper) {
    return fromM(mapper, AutoCloseable::close);
  }
  
  public static  Managed fromM(
      Function1> mapper, Consumer1 release) {
    return new Managed<>(PureIO.accessM(mapper).map(y -> Tuple.of(y, release)));
  }

  private static  Consumer1 releaseAndThen(
      Tuple2> outter, Tuple2> inner) {
    return ignore -> {
      try {
        Managed.release().accept(inner);
      } finally {
        Managed.release().accept(outter);
      }
    };
  }

  private static  Consumer1>> release() {
    return t -> t.get2().accept(t.get1());
  }
}