1 // Copyright (C) 2000 - 2012 Philip Aston 2 // All rights reserved. 3 // 4 // This file is part of The Grinder software distribution. Refer to 5 // the file LICENSE which is part of The Grinder distribution for 6 // licensing details. The Grinder distribution is available on the 7 // Internet at http://grinder.sourceforge.net/ 8 // 9 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 10 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 12 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 13 // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 14 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 16 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 18 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 19 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 20 // OF THE POSSIBILITY OF SUCH DAMAGE. 21 22 package net.grinder.scriptengine.jython; 23 24 import net.grinder.scriptengine.ScriptExecutionException; 25 26 import org.python.core.Py; 27 import org.python.core.PyClass; 28 import org.python.core.PyException; 29 import org.python.core.PyTraceback; 30 31 32 /** 33 * Exception that wraps errors encountered when invoking Jython 34 * scripts. 35 * 36 * @author Philip Aston 37 */ 38 final class JythonScriptExecutionException extends ScriptExecutionException { 39 40 private final String m_message; 41 private final String m_shortMessage; 42 43 /** 44 * Constructor for exceptions arising from a problem with the script that 45 * did not arise from some other exception. 46 * 47 * @param message 48 */ 49 public JythonScriptExecutionException(final String message) { 50 super(message); 51 m_message = message; 52 m_shortMessage = message; 53 } 54 55 /** 56 * Creates a new <code>JythonScriptExecutionException</code> instance. 57 * 58 * @param doingWhat What we were doing. 59 * @param e <code>PyException</code> that we caught. 60 */ 61 public JythonScriptExecutionException(final String doingWhat, 62 final PyException e) { 63 super(""); 64 setStackTrace(new StackTraceElement[0]); 65 66 final Object javaError = e.value.__tojava__(Throwable.class); 67 68 if (javaError == null || javaError == Py.NoConversion) { 69 // Duplicate logic from the package scope Py.formatException(). 70 final StringBuilder pyExceptionMessage = new StringBuilder(); 71 72 if (e.type instanceof PyClass) { 73 pyExceptionMessage.append(((PyClass) e.type).__name__); 74 } 75 else { 76 pyExceptionMessage.append(e.type.__str__()); 77 } 78 79 if (e.value != Py.None) { 80 pyExceptionMessage.append(": "); 81 // The original Py.formatException check's if e.value's type is 82 // Py.SyntaxError and if so treats it as a tuple. This is clearly wrong, 83 // it should check whether e.type is Py.SyntaxError. We do something 84 // simple instead. 85 pyExceptionMessage.append(e.value.__str__()); 86 } 87 88 m_shortMessage = 89 "Jython exception, " + pyExceptionMessage + 90 " [" + doingWhat + "]"; 91 m_message = 92 tracebackToMessage(pyExceptionMessage.toString(), e.traceback); 93 initCause(null); 94 } 95 else { 96 m_shortMessage = "Java exception " + doingWhat; 97 m_message = tracebackToMessage(m_shortMessage, e.traceback); 98 initCause((Throwable)javaError); 99 } 100 } 101 102 /** 103 * A short message, without the Jython stack trace. We override 104 * {@link #getMessage} to include the Jython stack trace; sometimes we don't 105 * want the stack trace. 106 * 107 * @return A short message, without the Jython stack trace. 108 */ 109 @Override 110 public String getShortMessage() { 111 return m_shortMessage; 112 } 113 114 /** 115 * The detail message string for this throwable. 116 * 117 * @return The message. 118 */ 119 @Override 120 public String getMessage() { 121 return m_message; 122 } 123 124 /** 125 * Remove the class name from stack traces. 126 * 127 * @return A string representation of this instance. 128 */ 129 @Override 130 public String toString() { 131 return getLocalizedMessage(); 132 } 133 134 /** 135 * We fix various following problems with PyTraceback.dumpStack() to make it 136 * more suitable for incorporation with a Java stack trace. 137 * <ul> 138 * <li>PyTraceback doesn't use platform specific line separators.</li> 139 * <li>Stacks are printed with the innermost frame last.</li> 140 * <li>The indentation style is different.</li> 141 * </ul> 142 */ 143 private static String tracebackToMessage(final String prefix, 144 final PyTraceback traceback) { 145 final StringBuilder result = new StringBuilder(prefix); 146 147 if (traceback != null) { 148 final String[] frames = traceback.dumpStack().split("\n"); 149 150 for (int i = frames.length - 1; i >= 1; --i) { 151 result.append(System.getProperty("line.separator")); 152 result.append("\t"); 153 result.append(frames[i].trim()); 154 } 155 } 156 157 return result.toString(); 158 } 159 }