org.apache.brooklyn.util.exceptions.Exceptions.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.brooklyn.util.exceptions.Exceptions.java

Source

/*
 * 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);
    }

}