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

de.unkrig.commons.lang.ExceptionUtil Maven / Gradle / Ivy


/*
 * de.unkrig.commons - A general-purpose Java class library
 *
 * Copyright (c) 2011, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.commons.lang;

import java.lang.reflect.Field;
import java.util.Iterator;

import org.xml.sax.SAXParseException;

import de.unkrig.commons.nullanalysis.Nullable;

/**
 * Various {@link Exception}-related utility methods.
 */
public final
class ExceptionUtil {

    private
    ExceptionUtil() {}

    private static final Field
    THROWABLE_DETAIL_MESSAGE_FIELD = ExceptionUtil.getDeclaredField(Throwable.class, "detailMessage");

    private static Field
    getDeclaredField(Class declaringClass, String fieldName) {

        try {
            Field f = declaringClass.getDeclaredField(fieldName);
            f.setAccessible(true);
            return f;
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * Wraps a given 'cause' in another throwable of the same type, with a detail message composed from {@code
     * prefix}, a colon, a space, and the cause.
     * 

* This is useful for adding context information to a throwable, e.g. which file is currently being processed, * the current line number, etc. *

* * @param prefix The text to prepend to the cause throwable's detail message * @param cause The throwable to wrap * @return The wrapping throwable */ @SuppressWarnings("unchecked") public static T wrap(@Nullable String prefix, T cause) { Class causeClass = cause.getClass(); // Determine the new detail message and the root cause. String message; { String causeMessage; try { // Some classes override "getMessage()", so it is better to get the message from the // "Throwable.detailMessage" field. causeMessage = (String) ExceptionUtil.THROWABLE_DETAIL_MESSAGE_FIELD.get(cause); } catch (Exception e) { causeMessage = cause.getMessage(); } message = prefix == null ? causeMessage : causeMessage == null ? prefix : prefix + ": " + causeMessage; } // Try "new TargetThrowable(String message, Throwable cause)". 95% of all throwables should have such a // constructor. T wrapper; WRAP: { try { wrapper = (T) causeClass.getConstructor(String.class, Throwable.class).newInstance(message, cause); break WRAP; } catch (Exception e) { ; } // Try "new TargetThrowable(String message)", plus "initCause(Throwable cause)". try { wrapper = (T) causeClass.getConstructor(String.class).newInstance(message); wrapper.initCause(cause); break WRAP; } catch (Exception e) { ; } // Try "new TargetThrowable(Object message)", plus "initCause(Throwable cause)". try { wrapper = (T) causeClass.getConstructor(Object.class).newInstance(message); wrapper.initCause(cause); break WRAP; } catch (Exception e3) { ; } // Special handling for SAXParseException. if (cause instanceof SAXParseException) { SAXParseException spe = (SAXParseException) cause; wrapper = (T) new SAXParseException( message, spe.getPublicId(), spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber() ); wrapper.initCause(cause); break WRAP; } // Special handling for JUNIT's "org.junit.ComparisonFailure" error. if (cause.getClass().getName().equals("org.junit.ComparisonFailure")) { try { wrapper = (T) causeClass.getConstructor(String.class, String.class, String.class).newInstance( message, // message causeClass.getMethod("getExpected").invoke(cause), // expected causeClass.getMethod("getActual").invoke(cause) // actual ); wrapper.initCause(cause); break WRAP; } catch (Exception e) { ; } } // Don't know how to wrap the target throwable - give up. return cause; } // Eliminate the top frames up to and including this "wrap()" method. StackTraceElement[] st = wrapper.getStackTrace(); for (int i = 0;; i++) { if ("wrap".equals(st[i].getMethodName())) { i++; StackTraceElement[] st2 = new StackTraceElement[st.length - i]; System.arraycopy(st, i, st2, 0, st2.length); wrapper.setStackTrace(st2); break; } } return wrapper; } /** * Wraps a given 'cause' in another throwable of the given wrapper class type, with a detail message composed * from prefix, a colon, a space, and the cause. * * @param prefix The text to prepend to the cause throwable's detail message * @param cause The throwable to wrap * @param wrapperClass The type of the wrapping throwable * @return The wrapping throwable */ public static T wrap(@Nullable String prefix, Throwable cause, Class wrapperClass) { // Compose the new detail message. StringBuilder sb = new StringBuilder(); if (prefix != null) sb.append(prefix).append(": "); sb.append(cause.getClass().getName()); String causeMessage = cause.getMessage(); if (causeMessage != null) { sb.append(": ").append(causeMessage); } else { for (Throwable t = cause.getCause(); t != null; t = t.getCause()) { sb.append(": ").append(t.getClass().getName()); String tMessage = t.getMessage(); if (tMessage != null) { sb.append(": ").append(tMessage); break; } } } String message = sb.toString(); // Try "new Wrapper(String message, Throwable cause)". try { return wrapperClass.getConstructor(String.class, Throwable.class).newInstance(message, cause); } catch (Exception e) { ; } // Try "new Wrapper(String message)", plus "initCause(Throwable cause)". try { T wrapper = wrapperClass.getConstructor(String.class).newInstance(message); wrapper.initCause(cause); return wrapper; } catch (Exception e) { ; } // Try "new Wrapper(Object message)", plus "initCause(Throwable cause)". (This is mainly for the // AssertionError, which, strange enough, has no "AssertionError(String, Throwable)" constructor.) try { T wrapper = wrapperClass.getConstructor(Object.class).newInstance(message); wrapper.initCause(cause); return wrapper; } catch (Exception e) { ; } // Try "new Wrapper()", plus "initCause(Throwable cause)". try { T wrapper = wrapperClass.newInstance(); wrapper.initCause(cause); return wrapper; } catch (Exception e) { ; } if (wrapperClass.isAssignableFrom(cause.getClass())) { @SuppressWarnings("unchecked") T result = (T) cause; return result; } AssertionError ae = new AssertionError("Exception class '" + wrapperClass.getName() + "' has no suitable constructor"); ae.initCause(cause); throw ae; } /** * Throws the given {@link Exception}, although it does not declare any exceptions. *

* This feature exploits the fact that the per-method declaration of thrown exceptions (the THROWS clause) is a * compiler feature and not a runtime feature, and can be circumvented with some trickery. *

*

* This is useful e.g. for implementations of {@link Iterator}, {@link Runnable} and other "service classes" * that want to throw checked exceptions. (Wrapping these in {@link RuntimeException}s is * sometimes not an option.) *

*

* Notice that the only way to catch such undeclared checked exceptions is "try { ... } catch (Exception e) * { ... }". *

*

* Notice that this method breaks Java's concept of exception checking entirely. * It also renders the exception-type-parametrized interfaces {@code *WhichThrows} (e.g. {@link * de.unkrig.commons.lang.protocol.RunnableWhichThrows}) useless. One should use one or the other, but never both. *

*

* Usage example: *

*
     * import static de.unkrig.commons.lang.ExceptionUtil.throwUndeclared;
     *
     * class MyIterator<E> implements Iterator<E> {
     *
     *     public E next() {
     *         // ...
     *         throwUndeclared(new IOException());
     *     }
     *
     *     // ...
     * }
* * @param e The exception to be thrown */ public static void throwUndeclared(Exception e) { ExceptionUtil.throwUndeclared2(e); } @SuppressWarnings("unchecked") private static void throwUndeclared2(Exception e) throws EX { // The trick is that because generic methods are implemented by ERASURE, the following CAST is removed during // compilation, and hence ANY exception can be thrown: throw (EX) e; } /** * Identical with "{@code throw throwable}", but has a return type {@code T}, so it can be used in an expression. */ public static T throW(EX throwable) throws EX { throw throwable; } /** * Identical with "{@code throw new AssertionError(object)}", but has a return type {@code T}, so it can be used * in an expression. */ public static T throwAssertionError(Object object) { throw new AssertionError(object); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy