Closer.java

package info.sixcorners.closer;

import java.io.Closeable;
import java.lang.ref.Cleaner;
import java.util.stream.Stream;
import javax.imageio.stream.ImageInputStream;
import lombok.Lombok;
import lombok.val;

/**
 * Collects and closes resources in the reverse order
 *
 * <p>Inspired by {@link Stream#close}. Not thread-safe. Also contains static utility methods for
 * closing resources that are thread-safe. If you have Java 9+ and want a thread-safe version try
 * {@link ConcurrentCloser}.
 */
public final class Closer implements Closeable, Runnable {
  private Handle head;

  /** Registers an {@link AutoCloseable} to be closed in reverse order */
  public Handle onClose(AutoCloseable closeable) {
    val newNode = new Handle(closeable);
    newNode.next = head;
    return head = newNode;
  }

  /** Closes registered {@link AutoCloseable}s in reverse order */
  @Override
  public void close() {
    Throwable t = null;
    Handle handle = head;
    head = null;
    while (handle != null) {
      try {
        handle.justClose();
      } catch (Throwable e) {
        if (t == null) {
          t = e;
        } else if (t != e) {
          t.addSuppressed(e);
        }
      }
      val oldHandle = handle;
      handle = handle.next;
      oldHandle.next = null; // things might hold onto the Handle and prevent gc
    }
    if (t != null) throw sneakyThrow(t);
  }

  /** Calls {@link #close} */
  @Override
  public void run() {
    close();
  }

  /** Handle to one {@link AutoCloseable} registered in a {@link Closer} */
  public static final class Handle implements Closeable, Runnable {
    private Handle next;
    private AutoCloseable closeable;

    /**
     * Can be used as a wrapper that only performs a close once
     *
     * <p>Registered {@link Cleaner.Cleanable}s have this same effect.
     *
     * <p>Useful for {@link ImageInputStream}s.
     */
    public Handle(AutoCloseable closeable) {
      this.closeable = closeable;
    }

    private void justClose() {
      val closeable = this.closeable;
      if (closeable != null) {
        this.closeable = null;
        Closer.close(closeable);
      }
    }

    /**
     * Closes the associated {@link AutoCloseable}
     *
     * <p>Repeated calls will do nothing.
     */
    @Override
    public void close() {
      compact(this, 1);
      justClose();
    }

    /** Calls {@link #close} */
    @Override
    public void run() {
      close();
    }
  }

  /**
   * Remove closed {@link Handle}s
   *
   * @param startWith {@link Handle} to start with
   * @param modLimit number of modifications to make
   */
  public static void compact(Handle startWith, int modLimit) {
    Handle prev = startWith;
    for (Handle handle = startWith.next; handle != null; handle = handle.next) {
      if (handle.closeable == null) continue;
      if (prev.next != handle) prev.next = handle;
      if (modLimit-- <= 0) break;
      prev = handle;
    }
  }

  /**
   * Remove closed {@link Handle}s
   *
   * @param startWith {@link Handle} to start with
   */
  public static void compact(Handle startWith) {
    compact(startWith, Integer.MAX_VALUE);
  }

  /** Remove closed {@link Handle}s */
  public void compact() {
    if (head != null) compact(head);
  }

  int size() {
    int i = 0;
    for (Handle handle = head; handle != null; handle = handle.next) i++;
    return i;
  }

  @SuppressWarnings("unchecked")
  private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
    throw (T) t;
  }

  /**
   * Throws as an unchecked exception.
   *
   * <p>Works like how Kotlin throws checked exceptions.
   *
   * @param t the throwable to rethrow
   * @return never returns; always throws
   * @see Lombok#sneakyThrow
   */
  public static RuntimeException sneakyThrow(Throwable t) {
    return sneakyThrow0(t);
  }

  /** Closes with throwing */
  public static void close(AutoCloseable closeable) {
    try {
      closeable.close();
    } catch (Exception e) {
      throw sneakyThrow(e);
    }
  }

  /** Closes without throwing */
  public static void closeQuietly(AutoCloseable closeable) {
    try {
      closeable.close();
    } catch (Throwable ignored) {
    }
  }

  /**
   * Converts an {@link AutoCloseable} to a {@link Runnable}
   *
   * <p>Useful for {@link Cleaner#register} and {@link Stream#onClose}.
   */
  public static Runnable toRunnable(AutoCloseable closeable) {
    return () -> close(closeable);
  }
}