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