001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
020 */
021
022package org.nuxeo.common.utils;
023
024import java.io.InterruptedIOException;
025import java.lang.reflect.InvocationTargetException;
026import java.nio.channels.ClosedByInterruptException;
027import java.util.ArrayList;
028import java.util.List;
029
030/**
031 * Provides utility methods for manipulating and examining exceptions in a generic way.
032 *
033 * @author DM
034 */
035public final class ExceptionUtils {
036
037    // This is an utility class.
038    private ExceptionUtils() {
039    }
040
041    /**
042     * Gets the root cause of the given <code>Throwable</code>.
043     * <p>
044     * This method walks through the exception chain up to the root of the exceptions tree using
045     * {@link Throwable#getCause()}, and returns the root exception.
046     *
047     * @param throwable the throwable to get the root cause for, may be null - this is to avoid throwing other
048     *            un-interesting exception when handling a business-important exception
049     * @return the root cause of the <code>Throwable</code>, <code>null</code> if none found or null throwable input
050     */
051    public static Throwable getRootCause(Throwable throwable) {
052        // This code is taken from Apache commons utils org.apache.commons.lang3.exception.ExceptionUtils
053        final List<Throwable> list = getThrowableList(throwable);
054        return list.size() < 2 ? null : (Throwable) list.get(list.size() - 1);
055    }
056
057    public static List<Throwable> getThrowableList(Throwable throwable) {
058        final List<Throwable> list = new ArrayList<>();
059        while (throwable != null && list.contains(throwable) == false) {
060            list.add(throwable);
061            throwable = getCause(throwable);
062        }
063        return list;
064    }
065
066    protected static Throwable getCause(Throwable throwable) {
067        if (throwable != null) {
068            return throwable.getCause();
069        }
070        return null;
071    }
072
073    /**
074     * Throws a {@link RuntimeException} if the passed exception is an {@link InterruptedException} or
075     * {@link InterruptedIOException}, or if the current thread is marked interrupted.
076     *
077     * @param e the exception to check
078     * @throws RuntimeException if there was an interrupt
079     * @since 7.1
080     */
081    public static void checkInterrupt(Exception e) {
082        if (isInterrupted(e)) {
083            // reset interrupted status
084            Thread.currentThread().interrupt();
085            // continue interrupt
086            throw new RuntimeException(e);
087        }
088        if (Thread.currentThread().isInterrupted()) {
089            // if an InterruptedException occurred earlier but was wrapped,
090            // continue interrupt
091            if (e instanceof RuntimeException) {
092                throw (RuntimeException) e;
093            } else {
094                throw new RuntimeException(e);
095            }
096        }
097    }
098
099    /**
100     * Unwraps the exception if it's an {@link InvocationTargetException}.
101     * <p>
102     * Also deals with interrupts by immediately throwing an exception.
103     *
104     * @param e the exception to unwrap
105     * @return the unwrapped exception
106     * @throws RuntimeException if there was an interrupt
107     * @since 7.1
108     */
109    public static Exception unwrapInvoke(Exception e) {
110        if (e instanceof InvocationTargetException) {
111            Throwable cause = e.getCause();
112            if (cause instanceof Error) {
113                // Error, throw immediately
114                throw (Error) cause;
115            } else if (cause instanceof Exception) {
116                e = (Exception) cause;
117            } else {
118                // Throwable direct subclass?!
119                e = new RuntimeException(cause);
120            }
121        }
122        checkInterrupt(e);
123        return e;
124    }
125
126    /**
127     * Wraps the exception into a {@link RuntimeException}, if needed, for re-throw.
128     * <p>
129     * Deals with {@link InvocationTargetException}, {@link InterruptedException} and {@link InterruptedIOException}.
130     *
131     * @param e the exception to wrap
132     * @return a {@link RuntimeException}
133     * @throws RuntimeException if there was an interrupt
134     * @since 7.1
135     */
136    public static RuntimeException runtimeException(Exception e) {
137        e = unwrapInvoke(e);
138        if (e instanceof RuntimeException) {
139            return (RuntimeException) e;
140        } else {
141            return new RuntimeException(e);
142        }
143    }
144
145    /**
146     * DON'T USE THIS METHOD - INTERNAL API.
147     * <p />
148     * This helper method is used to detect if an exception is caused by an {@link InterruptedException} or something
149     * equivalent (for example {@link ClosedByInterruptException}. This is a temporary method, we should rely on the
150     * {@link Thread#isInterrupted()} status in the future.
151     * 
152     * @since 9.3
153     */
154    public static boolean hasInterruptedCause(Throwable e) {
155        Throwable t = e;
156        while (t != null) {
157            if (isInterrupted(t)) {
158                return true;
159            }
160            t = t.getCause();
161        }
162        return false;
163    }
164
165    /**
166     * @since 9.3
167     */
168    public static boolean isInterrupted(Throwable t) {
169        return t instanceof InterruptedException || t instanceof InterruptedIOException
170                || t instanceof ClosedByInterruptException;
171    }
172
173}