| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.brooklyn.util.exceptions; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Predicates.instanceOf; |
| import static com.google.common.base.Throwables.getCausalChain; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.UndeclaredThrowableException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| |
| import javax.annotation.Nullable; |
| |
| import org.apache.brooklyn.util.collections.MutableSet; |
| import org.apache.brooklyn.util.javalang.JavaClassNames; |
| import org.apache.brooklyn.util.text.Strings; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| |
| public class Exceptions { |
| |
| private static final List<Class<? extends Throwable>> BORING_THROWABLE_SUPERTYPES = ImmutableList.<Class<? extends Throwable>>of( |
| ExecutionException.class, InvocationTargetException.class, PropagatedRuntimeException.class, UndeclaredThrowableException.class); |
| |
| private static boolean isBoring(Throwable t) { |
| for (Class<? extends Throwable> type: BORING_THROWABLE_SUPERTYPES) |
| if (type.isInstance(t)) return true; |
| return false; |
| } |
| |
| private static final Predicate<Throwable> IS_THROWABLE_BORING = new Predicate<Throwable>() { |
| @Override |
| public boolean apply(Throwable input) { |
| return isBoring(input); |
| } |
| }; |
| |
| private static List<Class<? extends Throwable>> BORING_PREFIX_THROWABLE_EXACT_TYPES = ImmutableList.<Class<? extends Throwable>>of( |
| IllegalStateException.class, RuntimeException.class, CompoundRuntimeException.class); |
| |
| /** Returns whether this is throwable either known to be boring or to have an unhelpful type name (prefix) |
| * which should be suppressed. null is accepted but treated as not boring. */ |
| public static boolean isPrefixBoring(Throwable t) { |
| if (t==null) return false; |
| if (isBoring(t)) |
| return true; |
| if (t instanceof UserFacingException) return true; |
| for (Class<? extends Throwable> type: BORING_PREFIX_THROWABLE_EXACT_TYPES) |
| if (t.getClass().equals(type)) return true; |
| return false; |
| } |
| |
| private static String stripBoringPrefixes(String s) { |
| ArrayList<String> prefixes = Lists.newArrayListWithCapacity(2 + BORING_PREFIX_THROWABLE_EXACT_TYPES.size() * 3); |
| for (Class<? extends Throwable> type : BORING_PREFIX_THROWABLE_EXACT_TYPES) { |
| prefixes.add(type.getCanonicalName()); |
| prefixes.add(type.getName()); |
| prefixes.add(type.getSimpleName()); |
| } |
| prefixes.add(":"); |
| prefixes.add(" "); |
| String[] ps = prefixes.toArray(new String[prefixes.size()]); |
| return Strings.removeAllFromStart(s, ps); |
| } |
| |
| /** |
| * Propagate a {@link Throwable} as a {@link RuntimeException}. |
| * <p> |
| * Like Guava {@link Throwables#propagate(Throwable)} but: |
| * <li> throws {@link RuntimeInterruptedException} to handle {@link InterruptedException}s; and |
| * <li> wraps as PropagatedRuntimeException for easier filtering |
| */ |
| public static RuntimeException propagate(Throwable throwable) { |
| if (throwable instanceof InterruptedException) { |
| throw new RuntimeInterruptedException((InterruptedException) throwable); |
| } else if (throwable instanceof RuntimeInterruptedException) { |
| Thread.currentThread().interrupt(); |
| throw (RuntimeInterruptedException) throwable; |
| } |
| Throwables.propagateIfPossible(checkNotNull(throwable)); |
| throw new PropagatedRuntimeException(throwable); |
| } |
| |
| /** |
| * See {@link #propagate(Throwable)}. If wrapping the exception, then include the given message; |
| * otherwise the message is not used. |
| */ |
| public static RuntimeException propagate(String msg, Throwable throwable) { |
| if (throwable instanceof InterruptedException) { |
| throw new RuntimeInterruptedException(msg, (InterruptedException) throwable); |
| } else if (throwable instanceof RuntimeInterruptedException) { |
| Thread.currentThread().interrupt(); |
| throw (RuntimeInterruptedException) throwable; |
| } |
| Throwables.propagateIfPossible(checkNotNull(throwable)); |
| throw new PropagatedRuntimeException(msg, throwable); |
| } |
| |
| /** |
| * Propagate exceptions which are fatal. |
| * <p> |
| * Propagates only those exceptions which one rarely (if ever) wants to capture, |
| * such as {@link InterruptedException} and {@link Error}s. |
| */ |
| public static void propagateIfFatal(Throwable throwable) { |
| if (throwable instanceof InterruptedException) { |
| throw new RuntimeInterruptedException((InterruptedException) throwable); |
| } else if (throwable instanceof RuntimeInterruptedException) { |
| Thread.currentThread().interrupt(); |
| throw (RuntimeInterruptedException) throwable; |
| } else if (throwable instanceof Error) { |
| throw (Error) throwable; |
| } |
| } |
| |
| /** returns the first exception of the given type, or null */ |
| @SuppressWarnings("unchecked") |
| public static <T extends Throwable> T getFirstThrowableOfType(Throwable from, Class<T> clazz) { |
| return (T) Iterables.tryFind(getCausalChain(from), instanceOf(clazz)).orNull(); |
| } |
| |
| /** returns the first exception that matches the filter, or null */ |
| public static Throwable getFirstThrowableMatching(Throwable from, Predicate<? super Throwable> filter) { |
| return Iterables.tryFind(getCausalChain(from), filter).orNull(); |
| } |
| |
| /** returns the first exception in the call chain which is not of common uninteresting types |
| * (ie excluding ExecutionException and PropagatedRuntimeExceptions); |
| * or the original throwable if all are uninteresting |
| */ |
| public static Throwable getFirstInteresting(Throwable throwable) { |
| return Iterables.tryFind(getCausalChain(throwable), Predicates.not(IS_THROWABLE_BORING)).or(throwable); |
| } |
| |
| /** creates (but does not throw) a new {@link PropagatedRuntimeException} whose |
| * message and cause are taken from the first _interesting_ element in the source */ |
| public static Throwable collapse(Throwable source) { |
| return collapse(source, true); |
| } |
| |
| /** as {@link #collapse(Throwable)} but includes causal messages in the message as per {@link #collapseTextIncludingAllCausalMessages(Throwable)}; |
| * use with care (limit once) as repeated usage can result in multiple copies of the same message */ |
| public static Throwable collapseIncludingAllCausalMessages(Throwable source) { |
| return collapse(source, true, true, ImmutableSet.<Throwable>of()); |
| } |
| |
| /** creates (but does not throw) a new {@link PropagatedRuntimeException} whose |
| * message is taken from the first _interesting_ element in the source, |
| * and optionally also the causal chain */ |
| public static Throwable collapse(Throwable source, boolean collapseCausalChain) { |
| return collapse(source, collapseCausalChain, false, ImmutableSet.<Throwable>of()); |
| } |
| |
| private static Throwable collapse(Throwable source, boolean collapseCausalChain, boolean includeAllCausalMessages, Set<Throwable> visited) { |
| visited = MutableSet.copyOf(visited); |
| String message = ""; |
| Throwable collapsed = source; |
| int collapseCount = 0; |
| boolean messageIsFinal = false; |
| // remove boring stack traces at the head |
| while (isBoring(collapsed) && !messageIsFinal) { |
| collapseCount++; |
| Throwable cause = collapsed.getCause(); |
| if (cause==null) { |
| // everything in the tree is boring... |
| return source; |
| } |
| if (visited.add(collapsed)) { |
| // there is a recursive loop |
| break; |
| } |
| String collapsedS = collapsed.getMessage(); |
| if (collapsed instanceof PropagatedRuntimeException && ((PropagatedRuntimeException)collapsed).isCauseEmbeddedInMessage()) { |
| message = collapsed.getMessage(); |
| messageIsFinal = true; |
| } else if (Strings.isNonBlank(collapsedS)) { |
| collapsedS = Strings.removeAllFromEnd(collapsedS, cause.toString(), stripBoringPrefixes(cause.toString()), cause.getMessage()); |
| collapsedS = stripBoringPrefixes(collapsedS); |
| if (Strings.isNonBlank(collapsedS)) |
| message = appendSeparator(message, collapsedS); |
| } |
| collapsed = cause; |
| } |
| // if no messages so far (ie we will be the toString) then remove boring prefixes from the message |
| Throwable messagesCause = collapsed; |
| while (messagesCause!=null && isPrefixBoring(messagesCause) && Strings.isBlank(message)) { |
| collapseCount++; |
| if (Strings.isNonBlank(messagesCause.getMessage())) { |
| message = messagesCause.getMessage(); |
| messagesCause = messagesCause.getCause(); |
| break; |
| } |
| visited.add(messagesCause); messagesCause = messagesCause.getCause(); |
| } |
| |
| if (collapseCount==0 && !includeAllCausalMessages) |
| return source; |
| |
| if (collapseCount==0 && messagesCause!=null) { |
| message = messagesCause.toString(); |
| messagesCause = messagesCause.getCause(); |
| } |
| |
| if (messagesCause!=null && !messageIsFinal) { |
| String extraMessage = collapseText(messagesCause, includeAllCausalMessages, ImmutableSet.copyOf(visited)); |
| message = appendSeparator(message, extraMessage); |
| } |
| if (message==null) message = ""; |
| return new PropagatedRuntimeException(message, collapseCausalChain ? collapsed : source, true); |
| } |
| |
| static String appendSeparator(String message, String next) { |
| if (Strings.isBlank(message)) |
| return next; |
| if (Strings.isBlank(next)) |
| return message; |
| if (message.endsWith(next)) |
| return message; |
| if (message.trim().endsWith(":") || message.trim().endsWith(";")) |
| return message.trim()+" "+next; |
| return message + ": " + next; |
| } |
| |
| /** removes uninteresting items from the top of the call stack (but keeps interesting messages), and throws |
| * @deprecated since 0.7.0 same as {@link #propagate(Throwable)} */ |
| public static RuntimeException propagateCollapsed(Throwable source) { |
| throw propagate(source); |
| } |
| |
| /** like {@link #collapse(Throwable)} but returning a one-line message suitable for logging without traces */ |
| public static String collapseText(Throwable t) { |
| return collapseText(t, false); |
| } |
| |
| /** normally {@link #collapseText(Throwable)} will stop following causal chains when encountering an interesting exception |
| * with a message; this variant will continue to follow such causal chains, showing all messages. |
| * for use e.g. when verbose is desired in the single-line message. */ |
| public static String collapseTextIncludingAllCausalMessages(Throwable t) { |
| return collapseText(t, true); |
| } |
| |
| private static String collapseText(Throwable t, boolean includeAllCausalMessages) { |
| return collapseText(t, includeAllCausalMessages, ImmutableSet.<Throwable>of()); |
| } |
| private static String collapseText(Throwable t, boolean includeAllCausalMessages, Set<Throwable> visited) { |
| if (t == null) return null; |
| if (visited.contains(t)) { |
| // IllegalStateException sometimes refers to itself; guard against stack overflows |
| if (Strings.isNonBlank(t.getMessage())) return t.getMessage(); |
| else return "("+t.getClass().getName()+", recursive cause)"; |
| } |
| Throwable t2 = collapse(t, true, includeAllCausalMessages, visited); |
| visited = MutableSet.copyOf(visited); |
| visited.add(t); |
| visited.add(t2); |
| if (t2 instanceof PropagatedRuntimeException) { |
| if (((PropagatedRuntimeException)t2).isCauseEmbeddedInMessage()) |
| // normally |
| return t2.getMessage(); |
| else if (t2.getCause()!=null) |
| return collapseText(t2.getCause(), includeAllCausalMessages, ImmutableSet.copyOf(visited)); |
| return ""+t2.getClass(); |
| } |
| String result = t2.toString(); |
| if (!includeAllCausalMessages) { |
| return result; |
| } |
| Throwable cause = t2.getCause(); |
| if (cause != null) { |
| String causeResult = collapseText(new PropagatedRuntimeException(cause)); |
| if (result.indexOf(causeResult)>=0) |
| return result; |
| return result + "; caused by "+causeResult; |
| } |
| return result; |
| } |
| |
| public static RuntimeException propagate(Collection<? extends Throwable> exceptions) { |
| throw propagate(create(exceptions)); |
| } |
| public static RuntimeException propagate(String prefix, Collection<? extends Throwable> exceptions) { |
| throw propagate(create(prefix, exceptions)); |
| } |
| |
| /** creates the given exception, but without propagating it, for use when caller will be wrapping */ |
| public static Throwable create(Collection<? extends Throwable> exceptions) { |
| return create(null, exceptions); |
| } |
| /** creates the given exception, but without propagating it, for use when caller will be wrapping */ |
| public static RuntimeException create(@Nullable String prefix, Collection<? extends Throwable> exceptions) { |
| if (exceptions.size()==1) { |
| Throwable e = exceptions.iterator().next(); |
| if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e); |
| return new PropagatedRuntimeException(prefix + ": " + Exceptions.collapseText(e), e); |
| } |
| if (exceptions.isEmpty()) { |
| if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions); |
| return new CompoundRuntimeException(prefix, exceptions); |
| } |
| if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); |
| return new CompoundRuntimeException(prefix+"; "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); |
| } |
| |
| /** Some throwables require a prefix for the message to make sense, |
| * for instance NoClassDefFoundError's message is often just the type. |
| */ |
| public static boolean isPrefixImportant(Throwable t) { |
| if (t instanceof NoClassDefFoundError) return true; |
| return false; |
| } |
| |
| /** For {@link Throwable} instances where know {@link #isPrefixImportant(Throwable)}, |
| * this returns a nice message for use as a prefix; otherwise this returns the class name. |
| * Callers should typically suppress the prefix if {@link #isPrefixBoring(Throwable)} is true. */ |
| public static String getPrefixText(Throwable t) { |
| if (t instanceof NoClassDefFoundError) return "Type not found"; |
| return JavaClassNames.cleanSimpleClassName(t); |
| } |
| |
| } |