/* * Copyright (C) 2006 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.Ordering; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.J2ObjCIncompatible; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** * Static methods used to implement {@link Futures#getChecked(Future, Class)}. */ @GwtIncompatible final class FuturesGetChecked { @CanIgnoreReturnValue static <V, X extends Exception> V getChecked(Future<V> future, Class<X> exceptionClass) throws X { return getChecked(bestGetCheckedTypeValidator(), future, exceptionClass); } /** * Implementation of {@link Futures#getChecked(Future, Class)}. */ @CanIgnoreReturnValue @VisibleForTesting static <V, X extends Exception> V getChecked( GetCheckedTypeValidator validator, Future<V> future, Class<X> exceptionClass) throws X { validator.validateClass(exceptionClass); try { return future.get(); } catch (InterruptedException e) { currentThread().interrupt(); throw newWithCause(exceptionClass, e); } catch (ExecutionException e) { wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); throw new AssertionError(); } } /** * Implementation of {@link Futures#getChecked(Future, Class, long, TimeUnit)}. */ @CanIgnoreReturnValue static <V, X extends Exception> V getChecked( Future<V> future, Class<X> exceptionClass, long timeout, TimeUnit unit) throws X { // TODO(cpovirk): benchmark a version of this method that accepts a GetCheckedTypeValidator bestGetCheckedTypeValidator().validateClass(exceptionClass); try { return future.get(timeout, unit); } catch (InterruptedException e) { currentThread().interrupt(); throw newWithCause(exceptionClass, e); } catch (TimeoutException e) { throw newWithCause(exceptionClass, e); } catch (ExecutionException e) { wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); throw new AssertionError(); } } @VisibleForTesting interface GetCheckedTypeValidator { void validateClass(Class<? extends Exception> exceptionClass); } private static GetCheckedTypeValidator bestGetCheckedTypeValidator() { return GetCheckedTypeValidatorHolder.BEST_VALIDATOR; } @VisibleForTesting static GetCheckedTypeValidator weakSetValidator() { return GetCheckedTypeValidatorHolder.WeakSetValidator.INSTANCE; } @J2ObjCIncompatible // ClassValue @VisibleForTesting static GetCheckedTypeValidator classValueValidator() { return GetCheckedTypeValidatorHolder.ClassValueValidator.INSTANCE; } /** * Provides a check of whether an exception type is valid for use with * {@link FuturesGetChecked#getChecked(Future, Class)}, possibly using caching. * * <p>Uses reflection to gracefully fall back to when certain implementations aren't available. */ @VisibleForTesting static class GetCheckedTypeValidatorHolder { static final String CLASS_VALUE_VALIDATOR_NAME = GetCheckedTypeValidatorHolder.class.getName() + "$ClassValueValidator"; static final GetCheckedTypeValidator BEST_VALIDATOR = getBestValidator(); @IgnoreJRERequirement // getChecked falls back to another implementation if necessary @J2ObjCIncompatible // ClassValue enum ClassValueValidator implements GetCheckedTypeValidator { INSTANCE; /* * Static final fields are presumed to be fastest, based on our experience with * UnsignedBytesBenchmark. TODO(cpovirk): benchmark this */ private static final ClassValue<Boolean> isValidClass = new ClassValue<Boolean>() { @Override protected Boolean computeValue(Class<?> type) { checkExceptionClassValidity(type.asSubclass(Exception.class)); return true; } }; @Override public void validateClass(Class<? extends Exception> exceptionClass) { isValidClass.get(exceptionClass); // throws if invalid; returns safely (and caches) if valid } } enum WeakSetValidator implements GetCheckedTypeValidator { INSTANCE; /* * Static final fields are presumed to be fastest, based on our experience with * UnsignedBytesBenchmark. TODO(cpovirk): benchmark this */ /* * A CopyOnWriteArraySet<WeakReference> is faster than a newSetFromMap of a MapMaker map with * weakKeys() and concurrencyLevel(1), even up to at least 12 cached exception types. */ private static final Set<WeakReference<Class<? extends Exception>>> validClasses = new CopyOnWriteArraySet<WeakReference<Class<? extends Exception>>>(); @Override public void validateClass(Class<? extends Exception> exceptionClass) { for (WeakReference<Class<? extends Exception>> knownGood : validClasses) { if (exceptionClass.equals(knownGood.get())) { return; } // TODO(cpovirk): if reference has been cleared, remove it? } checkExceptionClassValidity(exceptionClass); /* * It's very unlikely that any loaded Futures class will see getChecked called with more * than a handful of exceptions. But it seems prudent to set a cap on how many we'll cache. * This avoids out-of-control memory consumption, and it keeps the cache from growing so * large that doing the lookup is noticeably slower than redoing the work would be. * * Ideally we'd have a real eviction policy, but until we see a problem in practice, I hope * that this will suffice. I have not even benchmarked with different size limits. */ if (validClasses.size() > 1000) { validClasses.clear(); } validClasses.add(new WeakReference<Class<? extends Exception>>(exceptionClass)); } } /** * Returns the ClassValue-using validator, or falls back to the "weak Set" implementation if * unable to do so. */ static GetCheckedTypeValidator getBestValidator() { try { Class<?> theClass = Class.forName(CLASS_VALUE_VALIDATOR_NAME); return (GetCheckedTypeValidator) theClass.getEnumConstants()[0]; } catch (Throwable t) { // ensure we really catch *everything* return weakSetValidator(); } } } // TODO(cpovirk): change parameter order to match other helper methods (Class, Throwable)? private static <X extends Exception> void wrapAndThrowExceptionOrError( Throwable cause, Class<X> exceptionClass) throws X { if (cause instanceof Error) { throw new ExecutionError((Error) cause); } if (cause instanceof RuntimeException) { throw new UncheckedExecutionException(cause); } throw newWithCause(exceptionClass, cause); } /* * TODO(user): FutureChecker interface for these to be static methods on? If so, refer to it in * the (static-method) Futures.getChecked documentation */ private static boolean hasConstructorUsableByGetChecked( Class<? extends Exception> exceptionClass) { try { Exception unused = newWithCause(exceptionClass, new Exception()); return true; } catch (Exception e) { return false; } } private static <X extends Exception> X newWithCause(Class<X> exceptionClass, Throwable cause) { // getConstructors() guarantees this as long as we don't modify the array. @SuppressWarnings({"unchecked", "rawtypes"}) List<Constructor<X>> constructors = (List) Arrays.asList(exceptionClass.getConstructors()); for (Constructor<X> constructor : preferringStrings(constructors)) { @Nullable X instance = newFromConstructor(constructor, cause); if (instance != null) { if (instance.getCause() == null) { instance.initCause(cause); } return instance; } } throw new IllegalArgumentException( "No appropriate constructor for exception of type " + exceptionClass + " in response to chained exception", cause); } private static <X extends Exception> List<Constructor<X>> preferringStrings( List<Constructor<X>> constructors) { return WITH_STRING_PARAM_FIRST.sortedCopy(constructors); } private static final Ordering<Constructor<?>> WITH_STRING_PARAM_FIRST = Ordering.natural() .onResultOf( new Function<Constructor<?>, Boolean>() { @Override public Boolean apply(Constructor<?> input) { return asList(input.getParameterTypes()).contains(String.class); } }) .reverse(); @Nullable private static <X> X newFromConstructor(Constructor<X> constructor, Throwable cause) { Class<?>[] paramTypes = constructor.getParameterTypes(); Object[] params = new Object[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { Class<?> paramType = paramTypes[i]; if (paramType.equals(String.class)) { params[i] = cause.toString(); } else if (paramType.equals(Throwable.class)) { params[i] = cause; } else { return null; } } try { return constructor.newInstance(params); } catch (IllegalArgumentException e) { return null; } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { return null; } catch (InvocationTargetException e) { return null; } } @VisibleForTesting static boolean isCheckedException(Class<? extends Exception> type) { return !RuntimeException.class.isAssignableFrom(type); } @VisibleForTesting static void checkExceptionClassValidity(Class<? extends Exception> exceptionClass) { checkArgument( isCheckedException(exceptionClass), "Futures.getChecked exception type (%s) must not be a RuntimeException", exceptionClass); checkArgument( hasConstructorUsableByGetChecked(exceptionClass), "Futures.getChecked exception type (%s) must be an accessible class with an accessible " + "constructor whose parameters (if any) must be of type String and/or Throwable", exceptionClass); } private FuturesGetChecked() {} }