ExceptionUtils.java

package fr.sii.ogham.core.util;

import java.util.function.Predicate;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility class for exceptions
 * 
 * @author Aurélien Baudet
 *
 */
public final class ExceptionUtils {
	private static final Logger LOG = LoggerFactory.getLogger(ExceptionUtils.class);
	private static final Pattern INDENT = Pattern.compile("^", Pattern.MULTILINE);

	/**
	 * Predicate that returns {@code true} if any cause in the exception stack
	 * matches the cause predicate.
	 * 
	 * <p>
	 * If a cause matches the predicate, then it return {@code true} immediately
	 * (skipping other checks).
	 * 
	 * @param error
	 *            the root error to analyze
	 * @param causePredicate
	 *            the predicate to apply to causes recursively
	 * @return true if the cause predicate returns true for at least one cause
	 *         in the exception stack
	 */
	public static boolean hasAnyCause(Throwable error, Predicate<Throwable> causePredicate) {
		Throwable cause = error;
		while (cause != null) {
			if (causePredicate.test(cause)) {
				return true;
			}
			cause = cause.getCause();
		}
		return false;
	}

	/**
	 * Checks whether the error has been raised due to a Java {@link Error}.
	 * {@link Error}s should not be ignored. For example, if there is a
	 * {@link OutOfMemoryError}, retrying may result in consuming more memory
	 * and totally crash the JVM or hang the system.
	 * 
	 * @param error
	 *            the raised error
	 * @return true if the error is fatal JVM error
	 */
	public static boolean fatalJvmError(Throwable error) {
		return error instanceof Error;
	}

	/**
	 * Generate a String based on the exception. Unlike default
	 * {@link Throwable#toString()} method, the {@link Throwable#getCause()} may
	 * also be included in the generated string (depending of logging
	 * configuration). If logger associated to this class is configured to
	 * {@code DEBUG} or {@code TRACE}, then the cause is included.
	 * 
	 * @param e
	 *            the error to convert to a string
	 * @return the error string
	 */
	public static String toString(Throwable e) {
		return toString(e, "");
	}

	/**
	 * Generate a String based on the exception. Unlike default
	 * {@link Throwable#toString()} method, the {@link Throwable#getCause()} may
	 * also be included in the generated string (depending of logging
	 * configuration). If logger associated to this class is configured to
	 * {@code DEBUG} or {@code TRACE}, then the cause is included.
	 * 
	 * @param e
	 *            the error to convert to a string
	 * @param indentation
	 *            the initial indentation
	 * @return the error string
	 */
	public static String toString(Throwable e, String indentation) {
		String str = toThrowableString(e, indentation);
		if (LOG.isDebugEnabled() || LOG.isTraceEnabled()) {
			str += toCauseString(e, indentation + " ");
		}
		return str;
	}

	private static String toThrowableString(Throwable e, String indentation) {
		if (e == null) {
			return "";
		}
		String s = e.getClass().getName();
		String message = e.getLocalizedMessage();
		return indent(indentation, (message != null) ? (s + ": " + message) : s);
	}

	private static String toCauseString(Throwable e, String indentation) {
		if (e == null) {
			return "";
		}
		Throwable cause = e.getCause();
		if (cause == null) {
			return "";
		}
		return "\n" + indent(indentation, toString(cause, indentation));
	}

	private static String indent(String indentation, String str) {
		return INDENT.matcher(str).replaceAll(indentation);
	}

	private ExceptionUtils() {
		super();
	}
}