001/* 002 * Copyright (C) 2012 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.io; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020 021import com.google.common.annotations.Beta; 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Throwables; 024 025import java.io.Closeable; 026import java.io.IOException; 027import java.lang.reflect.Method; 028import java.util.ArrayDeque; 029import java.util.Deque; 030import java.util.logging.Level; 031 032/** 033 * A {@link Closeable} that collects {@code Closeable} resources and closes them all when it is 034 * {@linkplain #close closed}. This is intended to approximately emulate the behavior of Java 7's 035 * <a href="http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html"> 036 * try-with-resources</a> statement in JDK6-compatible code. Running on Java 7, code using this 037 * should be approximately equivalent in behavior to the same code written with try-with-resources. 038 * Running on Java 6, exceptions that cannot be thrown must be logged rather than being added to the 039 * thrown exception as a suppressed exception. 040 * 041 * <p>This class is intended to to be used in the following pattern: 042 * 043 * <pre>{@code 044 * Closer closer = Closer.create(); 045 * try { 046 * InputStream in = closer.register(openInputStream()); 047 * OutputStream out = closer.register(openOutputStream()); 048 * // do stuff 049 * } catch (Throwable e) { 050 * // ensure that any checked exception types other than IOException that could be thrown are 051 * // provided here, e.g. throw closer.rethrow(e, CheckedException.class); 052 * throw closer.rethrow(e); 053 * } finally { 054 * closer.close(); 055 * } 056 * }</pre> 057 * 058 * <p>Note that this try-catch-finally block is not equivalent to a try-catch-finally block using 059 * try-with-resources. To get the equivalent of that, you must wrap the above code in <i>another</i> 060 * try block in order to catch any exception that may be thrown (including from the call to 061 * {@code close()}). 062 * 063 * <p>This pattern ensures the following: 064 * <ul> 065 * <li>Each {@code Closeable} resource that is successfully registered will be closed later.</li> 066 * <li>If a {@code Throwable} is thrown in the try block, no exceptions that occur when attempting 067 * to close resources will be thrown from the finally block. The throwable from the try block will 068 * be thrown.</li> 069 * <li>If no exceptions or errors were thrown in the try block, the <i>first</i> exception thrown 070 * by an attempt to close a resource will be thrown.</li> 071 * <li>Any exception caught when attempting to close a resource that is <i>not</i> thrown 072 * (because another exception is already being thrown) is <i>suppressed</i>.</li> 073 * </ul> 074 * 075 * An exception that is suppressed is not thrown. The method of suppression used depends on the 076 * version of Java the code is running on: 077 * 078 * <ul> 079 * <li><b>Java 7+:</b> Exceptions are suppressed by adding them to the exception that <i>will</i> 080 * be thrown using {@code Throwable.addSuppressed(Throwable)}.</li> 081 * <li><b>Java 6:</b> Exceptions are suppressed by logging them instead.</li> 082 * </ul> 083 * 084 * @author Colin Decker 085 * @since 14.0 086 */ 087// Coffee's for {@link Closer closers} only. 088@Beta 089public final class Closer implements Closeable { 090 091 /** 092 * The suppressor implementation to use for the current Java version. 093 */ 094 private static final Suppressor SUPPRESSOR = SuppressingSuppressor.isAvailable() 095 ? SuppressingSuppressor.INSTANCE 096 : LoggingSuppressor.INSTANCE; 097 098 /** 099 * Creates a new {@link Closer}. 100 */ 101 public static Closer create() { 102 return new Closer(SUPPRESSOR); 103 } 104 105 @VisibleForTesting final Suppressor suppressor; 106 107 // only need space for 2 elements in most cases, so try to use the smallest array possible 108 private final Deque<Closeable> stack = new ArrayDeque<Closeable>(4); 109 private Throwable thrown; 110 111 @VisibleForTesting Closer(Suppressor suppressor) { 112 this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests 113 } 114 115 /** 116 * Registers the given {@code closeable} to be closed when this {@code Closer} is 117 * {@linkplain #close closed}. 118 * 119 * @return the given {@code closeable} 120 */ 121 // close. this word no longer has any meaning to me. 122 public <C extends Closeable> C register(C closeable) { 123 stack.push(closeable); 124 return closeable; 125 } 126 127 /** 128 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 129 * {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown 130 * wrapped in a {@code RuntimeException}. <b>Note:</b> Be sure to declare all of the checked 131 * exception types your try block can throw when calling an overload of this method so as to avoid 132 * losing the original exception type. 133 * 134 * <p>This method always throws, and as such should be called as 135 * {@code throw closer.rethrow(e);} to ensure the compiler knows that it will throw. 136 * 137 * @return this method does not return; it always throws 138 * @throws IOException when the given throwable is an IOException 139 */ 140 public RuntimeException rethrow(Throwable e) throws IOException { 141 thrown = e; 142 Throwables.propagateIfPossible(e, IOException.class); 143 throw Throwables.propagate(e); 144 } 145 146 /** 147 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 148 * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the 149 * given type. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. <b>Note:</b> 150 * Be sure to declare all of the checked exception types your try block can throw when calling an 151 * overload of this method so as to avoid losing the original exception type. 152 * 153 * <p>This method always throws, and as such should be called as 154 * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. 155 * 156 * @return this method does not return; it always throws 157 * @throws IOException when the given throwable is an IOException 158 * @throws X when the given throwable is of the declared type X 159 */ 160 public <X extends Exception> RuntimeException rethrow(Throwable e, 161 Class<X> declaredType) throws IOException, X { 162 thrown = e; 163 Throwables.propagateIfPossible(e, IOException.class); 164 Throwables.propagateIfPossible(e, declaredType); 165 throw Throwables.propagate(e); 166 } 167 168 /** 169 * Stores the given throwable and rethrows it. It will be rethrown as is if it is an 170 * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either 171 * of the given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. 172 * <b>Note:</b> Be sure to declare all of the checked exception types your try block can throw 173 * when calling an overload of this method so as to avoid losing the original exception type. 174 * 175 * <p>This method always throws, and as such should be called as 176 * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. 177 * 178 * @return this method does not return; it always throws 179 * @throws IOException when the given throwable is an IOException 180 * @throws X1 when the given throwable is of the declared type X1 181 * @throws X2 when the given throwable is of the declared type X2 182 */ 183 public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow( 184 Throwable e, Class<X1> declaredType1, Class<X2> declaredType2) throws IOException, X1, X2 { 185 thrown = e; 186 Throwables.propagateIfPossible(e, IOException.class); 187 Throwables.propagateIfPossible(e, declaredType1, declaredType2); 188 throw Throwables.propagate(e); 189 } 190 191 /** 192 * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an 193 * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods, 194 * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the 195 * <i>first</i> exception to be thrown from an attempt to close a closeable will be thrown and any 196 * additional exceptions that are thrown after that will be suppressed. 197 */ 198 @Override 199 public void close() throws IOException { 200 Throwable throwable = thrown; 201 202 // close closeables in LIFO order 203 while (!stack.isEmpty()) { 204 Closeable closeable = stack.pop(); 205 try { 206 closeable.close(); 207 } catch (Throwable e) { 208 if (throwable == null) { 209 throwable = e; 210 } else { 211 suppressor.suppress(closeable, throwable, e); 212 } 213 } 214 } 215 216 if (thrown == null && throwable != null) { 217 Throwables.propagateIfPossible(throwable, IOException.class); 218 throw new AssertionError(throwable); // not possible 219 } 220 } 221 222 /** 223 * Suppression strategy interface. 224 */ 225 @VisibleForTesting interface Suppressor { 226 /** 227 * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close 228 * the given closeable. {@code thrown} is the exception that is actually being thrown from the 229 * method. Implementations of this method should not throw under any circumstances. 230 */ 231 void suppress(Closeable closeable, Throwable thrown, Throwable suppressed); 232 } 233 234 /** 235 * Suppresses exceptions by logging them. 236 */ 237 @VisibleForTesting static final class LoggingSuppressor implements Suppressor { 238 239 static final LoggingSuppressor INSTANCE = new LoggingSuppressor(); 240 241 @Override 242 public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { 243 // log to the same place as Closeables 244 Closeables.logger.log(Level.WARNING, 245 "Suppressing exception thrown when closing " + closeable, suppressed); 246 } 247 } 248 249 /** 250 * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's 251 * addSuppressed(Throwable) mechanism. 252 */ 253 @VisibleForTesting static final class SuppressingSuppressor implements Suppressor { 254 255 static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor(); 256 257 static boolean isAvailable() { 258 return addSuppressed != null; 259 } 260 261 static final Method addSuppressed = getAddSuppressed(); 262 263 private static Method getAddSuppressed() { 264 try { 265 return Throwable.class.getMethod("addSuppressed", Throwable.class); 266 } catch (Throwable e) { 267 return null; 268 } 269 } 270 271 @Override 272 public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { 273 // ensure no exceptions from addSuppressed 274 if (thrown == suppressed) { 275 return; 276 } 277 try { 278 addSuppressed.invoke(thrown, suppressed); 279 } catch (Throwable e) { 280 // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging 281 LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed); 282 } 283 } 284 } 285}