PageRenderTime 22ms CodeModel.GetById 13ms app.highlight 7ms RepoModel.GetById 0ms app.codeStats 0ms

/worldedit-core/src/main/java/com/sk89q/worldedit/util/io/Closer.java

https://gitlab.com/Skull3x/WorldEdit
Java | 258 lines | 123 code | 29 blank | 106 comment | 15 complexity | c0655f334981f30cf8a83ef19622edca MD5 | raw file
  1/*
  2 * WorldEdit, a Minecraft world manipulation toolkit
  3 * Copyright (C) sk89q <http://www.sk89q.com>
  4 * Copyright (C) WorldEdit team and contributors
  5 *
  6 * This program is free software: you can redistribute it and/or modify it
  7 * under the terms of the GNU Lesser General Public License as published by the
  8 * Free Software Foundation, either version 3 of the License, or
  9 * (at your option) any later version.
 10 *
 11 * This program is distributed in the hope that it will be useful, but WITHOUT
 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 14 * for more details.
 15 *
 16 * You should have received a copy of the GNU Lesser General Public License
 17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20package com.sk89q.worldedit.util.io;
 21
 22import com.google.common.annotations.VisibleForTesting;
 23import com.google.common.base.Throwables;
 24
 25import java.io.Closeable;
 26import java.io.IOException;
 27import java.lang.reflect.Method;
 28import java.util.ArrayDeque;
 29import java.util.Deque;
 30import java.util.logging.Level;
 31import java.util.logging.Logger;
 32import java.util.zip.ZipFile;
 33
 34import static com.google.common.base.Preconditions.checkNotNull;
 35
 36public final class Closer implements Closeable {
 37
 38    private static final Logger logger = Logger.getLogger(Closer.class.getCanonicalName());
 39
 40    /**
 41     * The suppressor implementation to use for the current Java version.
 42     */
 43    private static final Suppressor SUPPRESSOR = SuppressingSuppressor.isAvailable()
 44            ? SuppressingSuppressor.INSTANCE
 45            : LoggingSuppressor.INSTANCE;
 46
 47    /**
 48     * Creates a new {@link Closer}.
 49     */
 50    public static Closer create() {
 51        return new Closer(SUPPRESSOR);
 52    }
 53
 54    @VisibleForTesting
 55    final Suppressor suppressor;
 56
 57    // only need space for 2 elements in most cases, so try to use the smallest array possible
 58    private final Deque<Closeable> stack = new ArrayDeque<Closeable>(4);
 59    private final Deque<ZipFile> zipStack = new ArrayDeque<ZipFile>(4);
 60    private Throwable thrown;
 61
 62    @VisibleForTesting Closer(Suppressor suppressor) {
 63        this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests
 64    }
 65
 66    /**
 67     * Registers the given {@code closeable} to be closed when this {@code Closer} is
 68     * {@linkplain #close closed}.
 69     *
 70     * @return the given {@code closeable}
 71     */
 72    // close. this word no longer has any meaning to me.
 73    public <C extends Closeable> C register(C closeable) {
 74        stack.push(closeable);
 75        return closeable;
 76    }
 77
 78    /**
 79     * Registers the given {@code zipFile} to be closed when this {@code Closer} is
 80     * {@linkplain #close closed}.
 81     *
 82     * @return the given {@code closeable}
 83     */
 84    public <Z extends ZipFile> Z register(Z zipFile) {
 85        zipStack.push(zipFile);
 86        return zipFile;
 87    }
 88
 89    /**
 90     * Stores the given throwable and rethrows it. It will be rethrown as is if it is an
 91     * {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown
 92     * wrapped in a {@code RuntimeException}. <b>Note:</b> Be sure to declare all of the checked
 93     * exception types your try block can throw when calling an overload of this method so as to avoid
 94     * losing the original exception type.
 95     *
 96     * <p>This method always throws, and as such should be called as
 97     * {@code throw closer.rethrow(e);} to ensure the compiler knows that it will throw.
 98     *
 99     * @return this method does not return; it always throws
100     * @throws IOException when the given throwable is an IOException
101     */
102    public RuntimeException rethrow(Throwable e) throws IOException {
103        thrown = e;
104        Throwables.propagateIfPossible(e, IOException.class);
105        throw Throwables.propagate(e);
106    }
107
108    /**
109     * Stores the given throwable and rethrows it. It will be rethrown as is if it is an
110     * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the
111     * given type. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. <b>Note:</b>
112     * Be sure to declare all of the checked exception types your try block can throw when calling an
113     * overload of this method so as to avoid losing the original exception type.
114     *
115     * <p>This method always throws, and as such should be called as
116     * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw.
117     *
118     * @return this method does not return; it always throws
119     * @throws IOException when the given throwable is an IOException
120     * @throws X when the given throwable is of the declared type X
121     */
122    public <X extends Exception> RuntimeException rethrow(Throwable e,
123                                                          Class<X> declaredType) throws IOException, X {
124        thrown = e;
125        Throwables.propagateIfPossible(e, IOException.class);
126        Throwables.propagateIfPossible(e, declaredType);
127        throw Throwables.propagate(e);
128    }
129
130    /**
131     * Stores the given throwable and rethrows it. It will be rethrown as is if it is an
132     * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either
133     * of the given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}.
134     * <b>Note:</b> Be sure to declare all of the checked exception types your try block can throw
135     * when calling an overload of this method so as to avoid losing the original exception type.
136     *
137     * <p>This method always throws, and as such should be called as
138     * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw.
139     *
140     * @return this method does not return; it always throws
141     * @throws IOException when the given throwable is an IOException
142     * @throws X1 when the given throwable is of the declared type X1
143     * @throws X2 when the given throwable is of the declared type X2
144     */
145    public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow(
146            Throwable e, Class<X1> declaredType1, Class<X2> declaredType2) throws IOException, X1, X2 {
147        thrown = e;
148        Throwables.propagateIfPossible(e, IOException.class);
149        Throwables.propagateIfPossible(e, declaredType1, declaredType2);
150        throw Throwables.propagate(e);
151    }
152
153    /**
154     * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an
155     * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods,
156     * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the
157     * <i>first</i> exception to be thrown from an attempt to close a closeable will be thrown and any
158     * additional exceptions that are thrown after that will be suppressed.
159     */
160    @Override
161    public void close() throws IOException {
162        Throwable throwable = thrown;
163
164        // close closeables in LIFO order
165        while (!stack.isEmpty()) {
166            Closeable closeable = stack.pop();
167            try {
168                closeable.close();
169            } catch (Throwable e) {
170                if (throwable == null) {
171                    throwable = e;
172                } else {
173                    suppressor.suppress(closeable, throwable, e);
174                }
175            }
176        }
177        while (!zipStack.isEmpty()) {
178            ZipFile zipFile = zipStack.pop();
179            try {
180                zipFile.close();
181            } catch (Throwable e) {
182                if (throwable == null) {
183                    throwable = e;
184                } else {
185                    suppressor.suppress(zipFile, throwable, e);
186                }
187            }
188        }
189
190        if (thrown == null && throwable != null) {
191            Throwables.propagateIfPossible(throwable, IOException.class);
192            throw new AssertionError(throwable); // not possible
193        }
194    }
195
196    /**
197     * Suppression strategy interface.
198     */
199    @VisibleForTesting interface Suppressor {
200        /**
201         * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close
202         * the given closeable. {@code thrown} is the exception that is actually being thrown from the
203         * method. Implementations of this method should not throw under any circumstances.
204         */
205        void suppress(Object closeable, Throwable thrown, Throwable suppressed);
206    }
207
208    /**
209     * Suppresses exceptions by logging them.
210     */
211    @VisibleForTesting static final class LoggingSuppressor implements Suppressor {
212
213        static final LoggingSuppressor INSTANCE = new LoggingSuppressor();
214
215        @Override
216        public void suppress(Object closeable, Throwable thrown, Throwable suppressed) {
217            // log to the same place as Closeables
218            logger.log(Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed);
219        }
220    }
221
222    /**
223     * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's
224     * addSuppressed(Throwable) mechanism.
225     */
226    @VisibleForTesting static final class SuppressingSuppressor implements Suppressor {
227
228        static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor();
229
230        static boolean isAvailable() {
231            return addSuppressed != null;
232        }
233
234        static final Method addSuppressed = getAddSuppressed();
235
236        private static Method getAddSuppressed() {
237            try {
238                return Throwable.class.getMethod("addSuppressed", Throwable.class);
239            } catch (Throwable e) {
240                return null;
241            }
242        }
243
244        @Override
245        public void suppress(Object closeable, Throwable thrown, Throwable suppressed) {
246            // ensure no exceptions from addSuppressed
247            if (thrown == suppressed) {
248                return;
249            }
250            try {
251                addSuppressed.invoke(thrown, suppressed);
252            } catch (Throwable e) {
253                // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging
254                LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed);
255            }
256        }
257    }
258}