1 /* 2 * Copyright (C) 2012 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.io; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.VisibleForTesting; 23 import com.google.common.base.Throwables; 24 25 import java.io.Closeable; 26 import java.io.IOException; 27 import java.lang.reflect.Method; 28 import java.util.LinkedList; 29 import java.util.logging.Level; 30 31 import javax.annotation.Nullable; 32 33 /** 34 * A {@link Closeable} that collects {@code Closeable} resources and closes them all when it is 35 * {@linkplain #close closed}. This is intended to approximately emulate the behavior of Java 7's 36 * <a href="http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html"> 37 * try-with-resources</a> statement in JDK6-compatible code. Running on Java 7, code using this 38 * should be approximately equivalent in behavior to the same code written with try-with-resources. 39 * Running on Java 6, exceptions that cannot be thrown must be logged rather than being added to the 40 * thrown exception as a suppressed exception. 41 * 42 * <p>This class is intended to be used in the following pattern: 43 * 44 * <pre> {@code 45 * Closer closer = Closer.create(); 46 * try { 47 * InputStream in = closer.register(openInputStream()); 48 * OutputStream out = closer.register(openOutputStream()); 49 * // do stuff 50 * } catch (Throwable e) { 51 * // ensure that any checked exception types other than IOException that could be thrown are 52 * // provided here, e.g. throw closer.rethrow(e, CheckedException.class); 53 * throw closer.rethrow(e); 54 * } finally { 55 * closer.close(); 56 * }}</pre> 57 * 58 * <p>Note that this try-catch-finally block is not equivalent to a try-catch-finally block using 59 * try-with-resources. To get the equivalent of that, you must wrap the above code in <i>another</i> 60 * try block in order to catch any exception that may be thrown (including from the call to 61 * {@code close()}). 62 * 63 * <p>This pattern ensures the following: 64 * 65 * <ul> 66 * <li>Each {@code Closeable} resource that is successfully registered will be closed later.</li> 67 * <li>If a {@code Throwable} is thrown in the try block, no exceptions that occur when attempting 68 * to close resources will be thrown from the finally block. The throwable from the try block will 69 * be thrown.</li> 70 * <li>If no exceptions or errors were thrown in the try block, the <i>first</i> exception thrown 71 * by an attempt to close a resource will be thrown.</li> 72 * <li>Any exception caught when attempting to close a resource that is <i>not</i> thrown 73 * (because another exception is already being thrown) is <i>suppressed</i>.</li> 74 * </ul> 75 * 76 * <p>An exception that is suppressed is not thrown. The method of suppression used depends on the 77 * version of Java the code is running on: 78 * 79 * <ul> 80 * <li><b>Java 7+:</b> Exceptions are suppressed by adding them to the exception that <i>will</i> 81 * be thrown using {@code Throwable.addSuppressed(Throwable)}.</li> 82 * <li><b>Java 6:</b> Exceptions are suppressed by logging them instead.</li> 83 * </ul> 84 * 85 * @author Colin Decker 86 * @since 14.0 87 */ 88 // Coffee's for {@link Closer closers} only. 89 @Beta 90 public final class Closer implements Closeable { 91 92 /** 93 * The suppressor implementation to use for the current Java version. 94 */ 95 private static final Suppressor SUPPRESSOR = SuppressingSuppressor.isAvailable() 96 ? SuppressingSuppressor.INSTANCE 97 : LoggingSuppressor.INSTANCE; 98 99 /** 100 * Creates a new {@link Closer}. 101 */ create()102 public static Closer create() { 103 return new Closer(SUPPRESSOR); 104 } 105 106 @VisibleForTesting final Suppressor suppressor; 107 108 // only need space for 2 elements in most cases, so try to use the smallest array possible 109 private final LinkedList<Closeable> stack = new LinkedList<Closeable>(); 110 private Throwable thrown; 111 Closer(Suppressor suppressor)112 @VisibleForTesting Closer(Suppressor suppressor) { 113 this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests 114 } 115 116 /** 117 * Registers the given {@code closeable} to be closed when this {@code Closer} is 118 * {@linkplain #close closed}. 119 * 120 * @return the given {@code closeable} 121 */ 122 // close. this word no longer has any meaning to me. register(@ullable C closeable)123 public <C extends Closeable> C register(@Nullable C closeable) { 124 if (closeable != null) { 125 stack.addFirst(closeable); 126 } 127 128 return closeable; 129 } 130 131 /** 132 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 133 * {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown 134 * wrapped in a {@code RuntimeException}. <b>Note:</b> Be sure to declare all of the checked 135 * exception types your try block can throw when calling an overload of this method so as to avoid 136 * losing the original exception type. 137 * 138 * <p>This method always throws, and as such should be called as 139 * {@code throw closer.rethrow(e);} to ensure the compiler knows that it will throw. 140 * 141 * @return this method does not return; it always throws 142 * @throws IOException when the given throwable is an IOException 143 */ rethrow(Throwable e)144 public RuntimeException rethrow(Throwable e) throws IOException { 145 checkNotNull(e); 146 thrown = e; 147 Throwables.propagateIfPossible(e, IOException.class); 148 throw new RuntimeException(e); 149 } 150 151 /** 152 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 153 * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the 154 * given type. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. <b>Note:</b> 155 * Be sure to declare all of the checked exception types your try block can throw when calling an 156 * overload of this method so as to avoid losing the original exception type. 157 * 158 * <p>This method always throws, and as such should be called as 159 * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. 160 * 161 * @return this method does not return; it always throws 162 * @throws IOException when the given throwable is an IOException 163 * @throws X when the given throwable is of the declared type X 164 */ rethrow(Throwable e, Class<X> declaredType)165 public <X extends Exception> RuntimeException rethrow(Throwable e, 166 Class<X> declaredType) throws IOException, X { 167 checkNotNull(e); 168 thrown = e; 169 Throwables.propagateIfPossible(e, IOException.class); 170 Throwables.propagateIfPossible(e, declaredType); 171 throw new RuntimeException(e); 172 } 173 174 /** 175 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 176 * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either 177 * of the given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. 178 * <b>Note:</b> Be sure to declare all of the checked exception types your try block can throw 179 * when calling an overload of this method so as to avoid losing the original exception type. 180 * 181 * <p>This method always throws, and as such should be called as 182 * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. 183 * 184 * @return this method does not return; it always throws 185 * @throws IOException when the given throwable is an IOException 186 * @throws X1 when the given throwable is of the declared type X1 187 * @throws X2 when the given throwable is of the declared type X2 188 */ rethrow( Throwable e, Class<X1> declaredType1, Class<X2> declaredType2)189 public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow( 190 Throwable e, Class<X1> declaredType1, Class<X2> declaredType2) throws IOException, X1, X2 { 191 checkNotNull(e); 192 thrown = e; 193 Throwables.propagateIfPossible(e, IOException.class); 194 Throwables.propagateIfPossible(e, declaredType1, declaredType2); 195 throw new RuntimeException(e); 196 } 197 198 /** 199 * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an 200 * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods, 201 * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the 202 * <i>first</i> exception to be thrown from an attempt to close a closeable will be thrown and any 203 * additional exceptions that are thrown after that will be suppressed. 204 */ 205 @Override close()206 public void close() throws IOException { 207 Throwable throwable = thrown; 208 209 // close closeables in LIFO order 210 while (!stack.isEmpty()) { 211 Closeable closeable = stack.removeFirst(); 212 try { 213 closeable.close(); 214 } catch (Throwable e) { 215 if (throwable == null) { 216 throwable = e; 217 } else { 218 suppressor.suppress(closeable, throwable, e); 219 } 220 } 221 } 222 223 if (thrown == null && throwable != null) { 224 Throwables.propagateIfPossible(throwable, IOException.class); 225 throw new AssertionError(throwable); // not possible 226 } 227 } 228 229 /** 230 * Suppression strategy interface. 231 */ 232 @VisibleForTesting interface Suppressor { 233 /** 234 * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close 235 * the given closeable. {@code thrown} is the exception that is actually being thrown from the 236 * method. Implementations of this method should not throw under any circumstances. 237 */ suppress(Closeable closeable, Throwable thrown, Throwable suppressed)238 void suppress(Closeable closeable, Throwable thrown, Throwable suppressed); 239 } 240 241 /** 242 * Suppresses exceptions by logging them. 243 */ 244 @VisibleForTesting static final class LoggingSuppressor implements Suppressor { 245 246 static final LoggingSuppressor INSTANCE = new LoggingSuppressor(); 247 248 @Override suppress(Closeable closeable, Throwable thrown, Throwable suppressed)249 public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { 250 // log to the same place as Closeables 251 Closeables.logger.log(Level.WARNING, 252 "Suppressing exception thrown when closing " + closeable, suppressed); 253 } 254 } 255 256 /** 257 * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's 258 * addSuppressed(Throwable) mechanism. 259 */ 260 @VisibleForTesting static final class SuppressingSuppressor implements Suppressor { 261 262 static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor(); 263 isAvailable()264 static boolean isAvailable() { 265 return addSuppressed != null; 266 } 267 268 static final Method addSuppressed = getAddSuppressed(); 269 getAddSuppressed()270 private static Method getAddSuppressed() { 271 try { 272 return Throwable.class.getMethod("addSuppressed", Throwable.class); 273 } catch (Throwable e) { 274 return null; 275 } 276 } 277 278 @Override suppress(Closeable closeable, Throwable thrown, Throwable suppressed)279 public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { 280 // ensure no exceptions from addSuppressed 281 if (thrown == suppressed) { 282 return; 283 } 284 try { 285 addSuppressed.invoke(thrown, suppressed); 286 } catch (Throwable e) { 287 // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging 288 LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed); 289 } 290 } 291 } 292 } 293