diff --git a/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java b/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java index 00787e765d2e5b66da157bd9e9ead174b908f344..c874c449eb06cf31a26d1af807f9f6dba9d2cb8f 100644 --- a/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java +++ b/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java @@ -17,6 +17,8 @@ package ch.systemsx.cisd.base.exceptions; import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; /** * An exception for tunneling checked exception through code that doesn't expect it. @@ -49,6 +51,148 @@ public class CheckedExceptionTunnel extends RuntimeException { } + @Override + public String getMessage() + { + if (getCause() != null && getCause().getMessage() != null) + { + return getCause().getMessage(); + } + return super.getMessage(); + } + + @Override + public String toString() + { + if (getCause() != null) + { + return getCause().toString(); + } + return super.toString(); + } + + @Override + public void printStackTrace(PrintStream s) + { + if (getCause() != null) + { + getCause().printStackTrace(s); + } else + { + super.printStackTrace(s); + } + } + + @Override + public void printStackTrace(PrintWriter s) + { + if (getCause() != null) + { + getCause().printStackTrace(s); + } else + { + super.printStackTrace(s); + } + } + + /** + * Like {@link #printStackTrace()}, but includes the tunnel's stacktrace as well. + */ + public void printFullStackTrace() + { + printFullStackTrace(System.err); + } + + /** + * Like {@link #printStackTrace(PrintStream)}, but includes the tunnel's stacktrace as well. + */ + public void printFullStackTrace(PrintStream s) + { + synchronized (s) { + s.println(super.toString()); + StackTraceElement[] trace = getStackTrace(); + for (int i=0; i < trace.length; i++) + s.println("\tat " + trace[i]); + + Throwable ourCause = getCause(); + if (ourCause != null) + { + printStackTraceAsCause(ourCause, s, trace); + } + } + } + + /** + * Print our stack trace as a cause for the specified stack trace. + */ + private static void printStackTraceAsCause(Throwable cause, PrintStream s, + StackTraceElement[] causedTrace) + { + final StackTraceElement[] trace = cause.getStackTrace(); + int m = trace.length-1, n = causedTrace.length-1; + while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) { + m--; n--; + } + final int framesInCommon = trace.length - 1 - m; + + s.println("Caused by: " + cause); + for (int i=0; i <= m; i++) + s.println("\tat " + trace[i]); + if (framesInCommon != 0) + s.println("\t... " + framesInCommon + " more"); + + final Throwable ourCauseesCause = cause.getCause(); + if (ourCauseesCause != null) + { + printStackTraceAsCause(ourCauseesCause, s, trace); + } + } + + /** + * Like {@link #printStackTrace(PrintWriter)}, but includes the tunnel's stacktrace as well. + */ + public void printFullStackTrace(PrintWriter s) + { + synchronized (s) { + s.println(super.toString()); + StackTraceElement[] trace = getStackTrace(); + for (int i=0; i < trace.length; i++) + s.println("\tat " + trace[i]); + + Throwable ourCause = getCause(); + if (ourCause != null) + { + printStackTraceAsCause(ourCause, s, trace); + } + } + } + + /** + * Print our stack trace as a cause for the specified stack trace. + */ + private static void printStackTraceAsCause(Throwable cause, PrintWriter s, + StackTraceElement[] causedTrace) + { + final StackTraceElement[] trace = cause.getStackTrace(); + int m = trace.length-1, n = causedTrace.length-1; + while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) { + m--; n--; + } + final int framesInCommon = trace.length - 1 - m; + + s.println("Caused by: " + cause); + for (int i=0; i <= m; i++) + s.println("\tat " + trace[i]); + if (framesInCommon != 0) + s.println("\t... " + framesInCommon + " more"); + + final Throwable ourCauseesCause = cause.getCause(); + if (ourCauseesCause != null) + { + printStackTraceAsCause(ourCauseesCause, s, trace); + } + } + /** * Convenience wrapper for {@link #wrapIfNecessary(Exception)}. If <var>throwable</var> is an * {@link Error}, this method will not return but the error will be thrown. diff --git a/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java b/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..04e34ebe933fde3ece17f97e7e38de91e2a40b92 --- /dev/null +++ b/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed 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 ch.systemsx.cisd.base.exceptions; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.commons.lang.StringUtils; +import org.testng.annotations.Test; + +import static org.testng.AssertJUnit.*; + +/** + * Test cases for {@link CheckedExceptionTunnel} + * + * @author Bernd Rinn + */ +public class CheckedExceptionTunnelTest +{ + + @Test + public void testGetMessage() + { + final IOException ioe = new IOException("This is the message"); + final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ioe); + assertEquals("This is the message", re.getMessage()); + } + + @Test + public void testToString() + { + final InterruptedException ioe = new InterruptedException("Somehow got interrupted"); + final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ioe); + assertEquals("java.lang.InterruptedException: Somehow got interrupted", re.toString()); + } + + @Test + public void testPrintStackTrace() + { + final InterruptedException ie = new InterruptedException("Somehow got interrupted"); + ie.fillInStackTrace(); + final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ie); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + re.printStackTrace(pw); + final String[] lines = StringUtils.split(sw.toString(), '\n'); + assertEquals("java.lang.InterruptedException: Somehow got interrupted", lines[0]); + assertTrue( + lines[1], + lines[1].startsWith("\tat ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnelTest." + + "testPrintStackTrace(CheckedExceptionTunnelTest.java")); + } + + @Test + public void testPrintFullStackTrace() + { + final Exception e = new Exception("Some exceptional condition"); + e.fillInStackTrace(); + final CheckedExceptionTunnel re = new CheckedExceptionTunnel(e); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + re.printFullStackTrace(); + re.printFullStackTrace(pw); + final String[] lines = StringUtils.split(sw.toString(), '\n'); + assertEquals( + "ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel: Some exceptional condition", + lines[0]); + assertTrue( + lines[1], + lines[1].startsWith("\tat ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnelTest." + + "testPrintFullStackTrace(CheckedExceptionTunnelTest.java:")); + } +}