View Javadoc

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 }