RaiseException.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
  15.  * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  16.  * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
  17.  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  18.  * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
  19.  * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
  20.  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  21.  * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  25.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the EPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the EPL, the GPL or the LGPL.
  34.  ***** END LICENSE BLOCK *****/
  35. package org.jruby.exceptions;

  36. import java.io.PrintWriter;
  37. import java.io.StringWriter;

  38. import java.lang.reflect.Member;
  39. import org.jruby.NativeException;
  40. import org.jruby.Ruby;
  41. import org.jruby.RubyClass;
  42. import org.jruby.RubyException;
  43. import org.jruby.RubyInstanceConfig;
  44. import org.jruby.RubyString;
  45. import org.jruby.runtime.Helpers;
  46. import org.jruby.runtime.RubyEvent;
  47. import org.jruby.runtime.ThreadContext;
  48. import org.jruby.runtime.backtrace.RubyStackTraceElement;
  49. import org.jruby.runtime.backtrace.TraceType;
  50. import org.jruby.runtime.builtin.IRubyObject;

  51. public class RaiseException extends JumpException {
  52.     public static final boolean DEBUG = false;
  53.     private static final long serialVersionUID = -7612079169559973951L;
  54.    
  55.     private RubyException exception;
  56.     private String providedMessage;
  57.     private boolean nativeException;

  58.     /**
  59.      * Construct a new RaiseException to wrap the given Ruby exception for Java-land
  60.      * throwing purposes.
  61.      *
  62.      * This constructor will generate a backtrace using the Java
  63.      * stack trace and the interpreted Ruby frames for the current thread.
  64.      *
  65.      * @param actException The Ruby exception to wrap
  66.      */
  67.     public RaiseException(RubyException actException) {
  68.         this(actException, false);
  69.     }

  70.     /**
  71.      * Construct a new RaiseException to wrap the given Ruby exception for Java-land
  72.      * throwing purposes.
  73.      *
  74.      * This constructor will not generate a backtrace and will instead use the
  75.      * one specified by the
  76.      *
  77.      * @param exception The Ruby exception to wrap
  78.      * @param backtrace
  79.      */
  80.     public RaiseException(RubyException exception, IRubyObject backtrace) {
  81.         super(exception.message.toString());
  82.         if (DEBUG) {
  83.             Thread.dumpStack();
  84.         }
  85.         setException(exception, false);
  86.         preRaise(exception.getRuntime().getCurrentContext(), backtrace);
  87.     }

  88.     public RaiseException(Ruby runtime, RubyClass excptnClass, String msg, boolean nativeException) {
  89.         super(msg);
  90.         if (msg == null) {
  91.             msg = "No message available";
  92.         }
  93.         providedMessage = "(" + excptnClass.getName() + ") " + msg;
  94.         this.nativeException = nativeException;
  95.         if (DEBUG) {
  96.             Thread.dumpStack();
  97.         }
  98.         setException((RubyException) Helpers.invoke(
  99.                 runtime.getCurrentContext(),
  100.                 excptnClass,
  101.                 "new",
  102.                 RubyString.newUnicodeString(excptnClass.getRuntime(), msg)),
  103.                 nativeException);
  104.         preRaise(runtime.getCurrentContext());
  105.     }

  106.     public RaiseException(Ruby runtime, RubyClass excptnClass, String msg, IRubyObject backtrace, boolean nativeException) {
  107.         super(msg);
  108.         if (msg == null) {
  109.             msg = "No message available";
  110.         }
  111.         providedMessage = "(" + excptnClass.getName() + ") " + msg;
  112.         this.nativeException = nativeException;
  113.         if (DEBUG) {
  114.             Thread.dumpStack();
  115.         }
  116.         setException((RubyException) Helpers.invoke(
  117.                 runtime.getCurrentContext(),
  118.                 excptnClass,
  119.                 "new",
  120.                 RubyString.newUnicodeString(excptnClass.getRuntime(), msg)),
  121.                 nativeException);
  122.         preRaise(runtime.getCurrentContext(), backtrace);
  123.     }

  124.     public RaiseException(RubyException exception, boolean isNativeException) {
  125.         super(exception.message.toString());
  126.         if (DEBUG) {
  127.             Thread.dumpStack();
  128.         }
  129.         this.nativeException = isNativeException;
  130.         setException(exception, isNativeException);
  131.         preRaise(exception.getRuntime().getCurrentContext());
  132.     }

  133.     public RaiseException(Throwable cause, NativeException nativeException) {
  134.         super(buildMessage(cause), cause);
  135.         providedMessage = buildMessage(cause);
  136.         setException(nativeException, true);
  137.         preRaise(nativeException.getRuntime().getCurrentContext(), nativeException.getCause().getStackTrace());
  138.         setStackTrace(RaiseException.javaTraceFromRubyTrace(exception.getBacktraceElements()));
  139.     }

  140.     /**
  141.      * Method still in use by jruby-openssl <= 0.5.2
  142.      */
  143.     public static RaiseException createNativeRaiseException(Ruby runtime, Throwable cause) {
  144.         return createNativeRaiseException(runtime, cause, null);
  145.     }

  146.     public static RaiseException createNativeRaiseException(Ruby runtime, Throwable cause, Member target) {
  147.         NativeException nativeException = new NativeException(runtime, runtime.getNativeException(), cause);

  148.         // FIXME: someday, add back filtering of reflection/handle methods between JRuby and target

  149.         return new RaiseException(cause, nativeException);
  150.     }

  151.     private static String buildMessage(Throwable exception) {
  152.         StringBuilder sb = new StringBuilder();
  153.         StringWriter stackTrace = new StringWriter();
  154.         exception.printStackTrace(new PrintWriter(stackTrace));
  155.    
  156.         sb.append("Native Exception: '").append(exception.getClass()).append("'; ");
  157.         sb.append("Message: ").append(exception.getMessage()).append("; ");
  158.         sb.append("StackTrace: ").append(stackTrace.getBuffer().toString());

  159.         return sb.toString();
  160.     }

  161.     @Override
  162.     public String getMessage() {
  163.         if (providedMessage == null) {
  164.             providedMessage = "(" + exception.getMetaClass().getBaseName() + ") " + exception.message(exception.getRuntime().getCurrentContext()).asJavaString();
  165.         }
  166.         return providedMessage;
  167.     }

  168.     /**
  169.      * Gets the exception
  170.      * @return Returns a RubyException
  171.      */
  172.     public RubyException getException() {
  173.         return exception;
  174.     }

  175.     private void preRaise(ThreadContext context) {
  176.         preRaise(context, (IRubyObject)null);
  177.     }

  178.     private void preRaise(ThreadContext context, StackTraceElement[] javaTrace) {
  179.         context.runtime.incrementExceptionCount();
  180.         doSetLastError(context);
  181.         doCallEventHook(context);

  182.         if (RubyInstanceConfig.LOG_EXCEPTIONS) TraceType.dumpException(exception);

  183.         exception.prepareIntegratedBacktrace(context, javaTrace);
  184.     }

  185.     private void preRaise(ThreadContext context, IRubyObject backtrace) {
  186.         context.runtime.incrementExceptionCount();
  187.         doSetLastError(context);
  188.         doCallEventHook(context);

  189.         if (RubyInstanceConfig.LOG_EXCEPTIONS) TraceType.dumpException(exception);

  190.         if (backtrace == null) {
  191.             exception.prepareBacktrace(context, nativeException);
  192.         } else {
  193.             exception.forceBacktrace(backtrace);
  194.         }

  195.         // call Throwable.setStackTrace so that when RaiseException appears nested inside another exception,
  196.         // Ruby stack trace gets displayed

  197.         // JRUBY-2673: if wrapping a NativeException, use the actual Java exception's trace as our Java trace
  198.         if (exception instanceof NativeException) {
  199.             setStackTrace(((NativeException)exception).getCause().getStackTrace());
  200.         } else {
  201.             setStackTrace(RaiseException.javaTraceFromRubyTrace(exception.getBacktraceElements()));
  202.         }
  203.     }

  204.     private void doCallEventHook(ThreadContext context) {
  205.         if (context.runtime.hasEventHooks()) {
  206.             context.runtime.callEventHooks(context, RubyEvent.RAISE, context.getFile(), context.getLine(), context.getFrameName(), context.getFrameKlazz());
  207.         }
  208.     }

  209.     private void doSetLastError(ThreadContext context) {
  210.         context.runtime.getGlobalVariables().set("$!", exception);
  211.     }
  212.    
  213.     /**
  214.      * Sets the exception
  215.      * @param newException The exception to set
  216.      */
  217.     protected void setException(RubyException newException, boolean nativeException) {
  218.         this.exception = newException;
  219.         this.nativeException = nativeException;
  220.     }

  221.     public static StackTraceElement[] javaTraceFromRubyTrace(RubyStackTraceElement[] trace) {
  222.         StackTraceElement[] newTrace = new StackTraceElement[trace.length];
  223.         for (int i = 0; i < newTrace.length; i++) {
  224.             newTrace[i] = trace[i].getElement();
  225.         }
  226.         return newTrace;
  227.     }
  228. }