All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.util.exceptions.Exceptions Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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.annotations.Beta;
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 {

    /** {@link Throwable} types whose existence is unhelpful in a message. */
    private static final List> ALWAYS_BORING_MESSAGE_THROWABLE_SUPERTYPES = ImmutableList.>of(
        ExecutionException.class, InvocationTargetException.class, UndeclaredThrowableException.class);
    /** As {@link #ALWAYS_BORING_MESSAGE_THROWABLE_SUPERTYPES} but might carry an interesting message. */
    private static final List> BORING_IF_NO_MESSAGE_THROWABLE_SUPERTYPES = ImmutableList.>of(
        PropagatedRuntimeException.class);

    /** NB: might be useful for stack trace, e.g. {@link ExecutionException} */
    private static boolean isBoringForMessage(Throwable t) {
        for (Class type: ALWAYS_BORING_MESSAGE_THROWABLE_SUPERTYPES)
            if (type.isInstance(t)) return true;
        if (Strings.isBlank(t.getMessage())) {
            for (Class type: BORING_IF_NO_MESSAGE_THROWABLE_SUPERTYPES)
                if (type.isInstance(t)) return true;
        }
        return false;
    }

    private static final Predicate IS_THROWABLE_BORING_FOR_MESSAGE = new Predicate() {
        @Override
        public boolean apply(Throwable input) {
            return isBoringForMessage(input);
        }
    };

    private static List> BORING_PREFIX_THROWABLE_EXACT_TYPES = ImmutableList.>of(
        RuntimeException.class, Exception.class, Throwable.class,
        IllegalStateException.class, IllegalArgumentException.class);
    
    private static List> BORING_PREFIX_THROWABLE_SUPERTYPES = ImmutableList.>of(
        ClassCastException.class, CompoundRuntimeException.class, PropagatedRuntimeException.class);

    /** Returns whether the prefix is throwable either known to be boring or to have an unhelpful type name (prefix)
     * which should be suppressed in messages. (They may be important in stack traces.)
     * 

* null is accepted but treated as not boring. */ public static boolean isPrefixBoring(Throwable t) { if (t==null) return false; if (isBoringForMessage(t)) return true; if (t instanceof UserFacingException) return true; for (Class type: BORING_PREFIX_THROWABLE_EXACT_TYPES) if (t.getClass().equals(type)) return true; for (Class type: BORING_PREFIX_THROWABLE_SUPERTYPES) if (type.isInstance(t)) return true; return false; } /** @deprecated never used, but kept as it might be useful */ @Deprecated static String stripBoringPrefixes(String s) { ArrayList prefixes = Lists.newArrayListWithCapacity(2 + BORING_PREFIX_THROWABLE_EXACT_TYPES.size() * 3); for (Class 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}. *

* Like Guava {@link Throwables#propagate(Throwable)} but: *

  • throws {@link RuntimeInterruptedException} to handle {@link InterruptedException}s; and *
  • 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)}. *

    * The given message is included only if the given {@link Throwable} * needs to be wrapped; otherwise the message is not used. * To always include the message, use {@link #propagateAnnotated(String, Throwable)}. */ public static RuntimeException propagate(String msg, Throwable throwable) { return propagate(msg, throwable, false); } /** As {@link #propagate(String, Throwable)} but always re-wraps including the given message. */ public static RuntimeException propagateAnnotated(String msg, Throwable throwable) { return propagate(msg, throwable, true); } private static RuntimeException propagate(String msg, Throwable throwable, boolean alwaysAnnotate) { if (throwable instanceof InterruptedException) { throw new RuntimeInterruptedException(msg, (InterruptedException) throwable); } else if (throwable instanceof RuntimeInterruptedException) { Thread.currentThread().interrupt(); if (alwaysAnnotate) { throw new RuntimeInterruptedException(msg, (RuntimeInterruptedException) throwable); } else { throw (RuntimeInterruptedException) throwable; } } if (throwable==null) { throw new PropagatedRuntimeException(msg, new NullPointerException("No throwable supplied.")); } if (!alwaysAnnotate) { Throwables.propagateIfPossible(checkNotNull(throwable)); } throw new PropagatedRuntimeException(msg, throwable); } /** * Propagate exceptions which are fatal. *

    * 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 getFirstThrowableOfType(Throwable from, Class 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 filter) { return Iterables.tryFind(getCausalChain(from), filter).orNull(); } /** returns the first exception in the call chain which whose message is potentially interesting, * in the sense that it is has some chance of giving helpful information as the cause. *

    * more specifically this drops those which typically wrap such causes giving chain / thread info, * reporting rather than causal explanation or important context -- * ie excluding {@link ExecutionException} always, * and {@link PropagatedRuntimeException} if it has no message, * and similar such. *

    * if all are "uninteresting" in this sense (which should not normally be the case) * this method just returns the original. *

    * often looking for a {@link UserFacingException} eg using {@link #getFirstThrowableOfType(Throwable, Class)} * is a better way to give a user-facing message. */ public static Throwable getFirstInteresting(Throwable throwable) { return Iterables.tryFind(getCausalChain(throwable), Predicates.not(IS_THROWABLE_BORING_FOR_MESSAGE)).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.of(), new Object[0]); } /** 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.of(), new Object[0]); } private static Throwable collapse(Throwable source, boolean collapseCausalChain, boolean includeAllCausalMessages, Set visited, Object contexts[]) { visited = MutableSet.copyOf(visited); String message = ""; Throwable collapsed = source; int collapseCount = 0; boolean messageIsFinal = false; // remove boring exceptions at the head; if message is interesting append it while ((isBoringForMessage(collapsed) || isSkippableInContext(collapsed, contexts)) && !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 = collapsedS; messageIsFinal = true; } 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 = getMessageWithAppropriatePrefix(messagesCause); messagesCause = messagesCause.getCause(); } if (messagesCause!=null && !messageIsFinal) { String extraMessage = collapseText(messagesCause, includeAllCausalMessages, ImmutableSet.copyOf(visited), contexts); message = appendSeparator(message, extraMessage); } if (message==null) message = ""; return new PropagatedRuntimeException(message, collapseCausalChain ? collapsed : source, Strings.isNonBlank(message)); } /** True if the given exception is skippable in any of the supplied contexts. */ public static boolean isSkippableInContext(Throwable e, Object... contexts) { if (!(e instanceof CanSkipInContext)) return false; for (Object c: contexts) { if (((CanSkipInContext)e).canSkipInContext(c)) { return true; } } return false; } 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); } public static String collapseTextInContext(Throwable t, Object ...contexts) { return collapseText(t, false, ImmutableSet.of(), contexts); } /** 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.of(), new Object[0]); } private static String collapseText(Throwable t, boolean includeAllCausalMessages, Set visited, Object contexts[]) { if (t == null) return null; if (visited.contains(t)) { // If a boring-prefix class has no message it will render as multiply-visited. // Additionally IllegalStateException sometimes refers to itself as its cause. // In both cases, don't stack overflow! if (Strings.isNonBlank(t.getMessage())) return t.getMessage(); if (t.getCause()!=null) return t.getCause().getClass().getName(); return t.getClass().getName(); } Throwable t2 = collapse(t, true, includeAllCausalMessages, visited, contexts); 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), contexts); return ""+t2.getClass(); } String result = getMessageWithAppropriatePrefix(t2); if (!includeAllCausalMessages) { return result; } Throwable cause = t2.getCause(); if (cause != null) { String causeResult = collapseTextInContext(new PropagatedRuntimeException(cause), contexts); if (result.indexOf(causeResult)>=0) return result; return result + "; caused by "+causeResult; } return result; } public static RuntimeException propagate(Collection exceptions) { throw propagate(create(exceptions)); } public static RuntimeException propagate(String prefix, Collection 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 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 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. */ @Beta public static boolean isPrefixRequiredForMessageToMakeSense(Throwable t) { if (t instanceof NoClassDefFoundError) return true; return false; } /** For {@link Throwable} instances where know {@link #isPrefixRequiredForMessageToMakeSense(Throwable)}, * this returns a nice message for use as a prefix; * returns empty string if {@link #isPrefixBoring(Throwable)} is true; * otherwise this returns the simplified class name. */ private static String getPrefixText(Throwable t) { if (t instanceof NoClassDefFoundError) return "Invalid java type"; if (isPrefixBoring(t)) return ""; return JavaClassNames.cleanSimpleClassName(t); } /** Like {@link Throwable#toString()} except suppresses boring prefixes and replaces prefixes with sensible messages where required */ public static String getMessageWithAppropriatePrefix(Throwable t) { return appendSeparator(getPrefixText(t), t.getMessage()); } }





  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy