de.unkrig.commons.lang.ExceptionUtil Maven / Gradle / Ivy
Show all versions of de-unkrig-commons Show documentation
/*
* 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. The name of the author may not be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.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() {}
/**
* 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 = 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 wrapping;
try {
wrapping = (T) causeClass.getConstructor(String.class, Throwable.class).newInstance(message, cause);
} catch (Exception e) {
// Try "new TargetThrowable(String message)", plus "initCause(Throwable cause)".
try {
wrapping = (T) causeClass.getConstructor(String.class).newInstance(message);
wrapping.initCause(cause);
} catch (Exception e2) {
// Try "new TargetThrowable(Object message)", plus "initCause(Throwable cause)".
try {
wrapping = (T) causeClass.getConstructor(Object.class).newInstance(message);
wrapping.initCause(cause);
} catch (Exception e3) {
// Special handling for SAXParEx
if (cause instanceof SAXParseException) {
SAXParseException spe = (SAXParseException) cause;
wrapping = (T) new SAXParseException(
message,
spe.getPublicId(),
spe.getSystemId(),
spe.getLineNumber(),
spe.getColumnNumber()
);
wrapping.initCause(cause);
} else {
// 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 = wrapping.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);
wrapping.setStackTrace(st2);
break;
}
}
return wrapping;
}
/**
* Wraps a given 'cause' in another throwable of the given wrapper class type, with a detail message composed
* from {@code 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()", plus "initCause(Throwable cause)".
try {
T wrapper = wrapperClass.newInstance();
wrapper.initCause(cause);
return wrapper;
} catch (Exception e) {
;
}
throw new Error("Exception class '" + wrapperClass.getName() + "' has no suitable constructor");
}
/**
* 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); }
}