001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.util;
007
008import org.eclipse.jdt.annotation.Owning;
009
010import java.io.FilterOutputStream;
011import java.io.IOException;
012import java.io.OutputStream;
013
014import edu.umd.cs.findbugs.annotations.NonNull;
015import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
016
017/**
018 * Wraps a resource to make it {@link AutoCloseable}.
019 *
020 * @param <T>
021 *          the resource type
022 * @param <E>
023 *          the exception type that may be thrown if an error occurs when
024 *          closing the resource
025 */
026public final class AutoCloser<T, E extends Exception> implements AutoCloseable {
027  @NonNull
028  private final T resource;
029  @NonNull
030  private final Closer<T, E> closeLambda;
031
032  /**
033   * Adapt the the provided {@code resource} to be {@link AutoCloseable}, using a
034   * provided closer {@code lambda}.
035   * <p>
036   * The caller owns the returned wrapper and is responsible for closing it.
037   *
038   * @param <T>
039   *          the resource's type
040   * @param <E>
041   *          the exception type that can be thrown when closing
042   * @param resource
043   *          the object to adapt
044   * @param lambda
045   *          the lambda to use as a callback on close
046   * @return the resource wrapped in an {@link AutoCloseable}
047   */
048  @Owning
049  @NonNull
050  public static <T, E extends Exception> AutoCloser<T, E> autoClose(
051      @NonNull T resource,
052      @NonNull Closer<T, E> lambda) {
053    return new AutoCloser<>(resource, lambda);
054  }
055
056  /**
057   * Adapt the provided {@code resource} to be {@link AutoCloseable}, using a
058   * provided closer {@code lambda}.
059   *
060   * @param resource
061   *          the object to adapt
062   * @param lambda
063   *          the lambda to use as a callback on close
064   */
065  private AutoCloser(@NonNull T resource, @NonNull Closer<T, E> lambda) {
066    this.resource = resource;
067    this.closeLambda = lambda;
068  }
069
070  /**
071   * Get the wrapped resource.
072   *
073   * @return the resource object
074   */
075  @NonNull
076  public T getResource() {
077    return resource;
078  }
079
080  @Override
081  @SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION",
082      justification = "Generic exception is required for flexible resource closing")
083  public void close() throws E {
084    closeLambda.close(getResource());
085  }
086
087  /**
088   * A callback interface representing a close operation.
089   *
090   * @param <T>
091   *          the Java type of the object being closed
092   * @param <E>
093   *          the Java type of the exception that can be thrown when closing
094   */
095  @FunctionalInterface
096  public interface Closer<T, E extends Exception> {
097    /**
098     * This method is called to auto-close the resource.
099     *
100     * @param object
101     *          the resource to auto-close
102     * @throws E
103     *           the exception type that can be thrown when closing
104     */
105    @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION")
106    void close(@NonNull T object) throws E;
107  }
108
109  /**
110   * Wraps the provided output stream to prevent the wrapped stream from being
111   * closed.
112   * <p>
113   * This is useful for protecting standard streams. i.e. {@link System#out},
114   * {@link System#err}.
115   * <p>
116   * The caller owns the returned stream and is responsible for closing it.
117   *
118   * @param out
119   *          the stream to wrap
120   * @return the new wrapped stream
121   */
122  @Owning
123  @NonNull
124  public static OutputStream preventClose(OutputStream out) {
125    return new ClosePreventingOutputStream(out);
126  }
127
128  private static class ClosePreventingOutputStream
129      extends FilterOutputStream {
130
131    public ClosePreventingOutputStream(OutputStream out) {
132      super(out);
133    }
134
135    @Override
136    public void close() throws IOException {
137      // do nothing
138    }
139  }
140}