Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add feature director:except for improved director exception handling …

…in Java

Closes #91
  • Loading branch information...
commit 6736e74127180f012dab11379a2159cd073461d4 1 parent ec1d5a5
@marvingreenberg marvingreenberg authored wsfulton committed
View
276 Doc/Manual/Java.html
@@ -87,6 +87,7 @@
<li><a href="#Java_directors_example">Simple directors example</a>
<li><a href="#Java_directors_threading">Director threading issues</a>
<li><a href="#Java_directors_performance">Director performance tuning</a>
+<li><a href="#Java_exceptions_from_directors">Java Exceptions from Directors</a>
</ul>
<li><a href="#Java_allprotected">Accessing protected members</a>
<li><a href="#Java_common_customization">Common customization features</a>
@@ -3555,6 +3556,281 @@
The disadvantage is that invocation of director methods from C++ when Java doesn't actually override the method will require an additional call up into Java and back to C++. As such, this option is only useful when overrides are extremely common and instantiation is frequent enough that its performance is critical.
</p>
+<H3><a name="Java_exceptions_from_directors"></a>24.5.7 Java Exceptions from Directors</H3>
+
+<p>
+With directors routing method calls to Java, and proxies routing them
+to C++, the handling of exceptions is an important concern. In Swig
+2.0, the director class methods ignored java exceptions that occurred
+during method calls dispatched to the Java director class and simply
+returned '$null' to the C++ caller. The default behavior now throws a
+Swig-defined <code>DirectorException</code> C++ exception. A facility
+is now provided to allow translation of thrown Java exceptions into
+C++ exceptions. This can be done in two different ways using
+the <code>%feature("director:except")</code> directive. In the
+simplest approach, a code block is attached to each director method to
+handle the mapping of java exceptions into C++ exceptions.
+</p>
+
+<div class="code">
+<pre>
+// All the rules to associate a feature with an element apply
+%feature("director:except") MyClass::method(int x) {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ if (Swig::ExceptionMatches(jenv,$error,"java/lang/IndexOutOfBoundsException"))
+ throw std::out_of_range(Swig::JavaExceptionMessage(jenv,$error).message());
+ else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException"))
+ throw MyCppException(Swig::JavaExceptionMessage(jenv,$error).message());
+ else
+ throw std::runtime_error("Unexpected exception thrown by MyClass::method");
+ }
+}
+
+class MyClass {
+ void method(int x); /* on C++ side, may get std::runtime_error or MyCppException */
+}
+</pre>
+</div>
+<p>
+This approach allows mapping java exceptions thrown by director methods into
+C++ exceptions, to match the exceptions expected by a C++ caller. There
+need not be any exception specification on the method. This approach gives
+complete flexibility to map exceptions thrown by a java director
+implementation into desired C++ exceptions. The
+function <code>Swig::ExceptionMatches</code>
+and class <code>Swig::JavaExceptionMessage</code> are provided to simplify
+writing code for wrappers that use <code>director:except</code>feature. These simplify
+testing the type of the java exception and constructing C++ exceptions. The
+function <code>Swig::ExceptionMatches</code> matches the type of the
+<code>jthrowable</code> thrown against a <b>fully qualified</b> JNI style class
+name, like <code>"java/lang/IOError"</code>. If the throwable class is the same
+type, or derives from the given type, it returns true. Care must be taken to
+provide the correct fully qualified name, since for wrapped exceptions the
+generated proxy class will have additional package qualification, depending on
+the '-package' argument and use of <a href="#Java_namespaces">nspace
+feature</a>. The variable $error is simply a unique variable name to allow
+assignment of the exception that occurred. The variable $packagepath is
+replaced by the outer package provided for swig generation by the -package
+option. The class <code>Swig::JavaExceptionMessage</code> is a holder
+object giving access to the message from the thrown java exception.
+The message() method returns the exception message as a <code>const char *</code>,
+which is only valid during the lifetime of the holder. Any code using this message
+needs to copy it, for example into a std::string or a newly constructed C++ exception.
+</p>
+
+<p>
+If many methods may throw different exceptions, using this approach to
+write handlers for a large number of methods will result in
+duplication of the code in the <code>director:except</code> feature
+code blocks, and will require separate feature definitions for every
+method. So an alternative approach is provided, using typemaps in a
+fashion analagous to
+the <a href="Typemaps.html#throws_typemap">"throws" typemap.</a> The
+"throws" typemap provides an approach to automatically map all the C++
+exceptions listed in a method's defined exceptions (either from
+an <em>exception specification</em> or a <code>%catches</code>
+feature) into Java exceptions, for the generated proxy classes. To
+provide the inverse mapping, the <code>directorthrows</code> typemap
+is provided.
+
+<p>Using directorthrows typemaps allows a
+generic <code>director:except</code> feature to be combined with
+method-specific handling to achieve the desired result. The
+default <code>director:except</code> feature, in combination
+with <code>directorthrows</code> typemaps generate exception mapping
+to C++ exceptions for all the exceptions defined for each method. The
+default definition is shown below.</p>
+
+<div class="code">
+<pre>
+%feature("director:except") %{
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ $directorthrowshandlers
+ throw Swig::DirectorException(jenv, $error);
+ }
+%}
+</pre>
+</div>
+
+<p>The code generated using the <code>director:except</code> feature
+replaces the <code>$directorthrowshandlers</code> with code that throws
+appropriate C++ exceptions from <code>directorthrows</code> typemaps
+for each exception defined for the method. Just as with
+the <a href="Typemaps.html#throws_typemap">"throws" typemap</a>, the
+possible exceptions may be defined either with an exception
+specification (<code> throw(MyException,std::runtime_error)</code> ) or
+using the <code>%catches</code> feature applied to the method.</p>
+
+<p><em>Note: Using the %catches feature to define the
+handled exceptions is preferred to using exception specifications. If
+the interface is defined with an exception specification the generated
+swig proxy classes will have the same exception specification. In C++
+if exceptions other than those in the specification are thrown, the
+program will be terminated. </em></p>
+
+<p>Because this default definition maps any unhandled java exceptions to
+Swig::DirectorException, any director methods that define exception
+specifications will cause program termination. To simply ignore
+unexpected exceptions, the default can be changed to:
+
+<div class="code">
+<pre>
+%feature("director:except") %{
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ $directorthrowshandlers
+ return $null;
+ }
+%}
+</pre>
+</div>
+</p>
+
+<p>Alternatively an exception compatible with the existing director
+method exception specifications may be thrown. Assuming that all
+methods allow std::runtime_error to be thrown,
+the <code>return&nbsp;$null;</code> could be changed to:
+
+<div class="code">
+<pre>
+ throw std::runtime_error(Swig::JavaExceptionMessage(jenv,$error).message());
+</pre>
+</div>
+</p>
+
+<p>In more complex situations, a separate <code>director:except</code> feature
+may need to be attached to specific methods.
+</p>
+
+<p>Below is a complete example demonstrating the use
+of <code>directorthrows</code> typemaps. The <code>directorthrows</code> typemap
+provides a code fragment to test for a pending java exception type, and the
+resulting C++ exception that will be thrown. In this example, a
+generic directorthrows typemap is appropriate for all three exceptions - all
+take single string constructors. If the constructors had different constructors,
+it would be neccessary to have separate typemaps for each exception type.
+
+
+<!-- All the DEFINE_ and DECLARE_EXCEPTIONS CAN BE OMITTED to make
+ this more succinct. They are included to make this a complete
+ example interface that could be generated and built. -->
+<div class="code">
+<pre>
+// Define exceptions in header section using std::runtime_error
+%define DEFINE_EXCEPTION(NAME)
+%{
+ #include &lt;exception&gt;
+ namespace MyNS {
+ struct NAME : public std::runtime_error { NAME(const std::string& what):runtime_error(what) {}; };
+ }
+%}
+%enddef
+// Expose c++ exceptions as java Exceptions with getMessage
+%define DECLARE_EXCEPTION(NAME)
+%typemap(javabase) MyNS::NAME "java.lang.Exception";
+%rename(getMessage,fullname=1) MyNS::NAME::what;
+namespace MyNS {
+ struct NAME {
+ NAME(const std::string& what);
+ const char * what();
+ };
+}
+%enddef
+
+DEFINE_EXCEPTION(ExceptionA)
+DEFINE_EXCEPTION(ExceptionB)
+DEFINE_EXCEPTION(Unknown)
+
+// Mark three methods to map director-thrown exceptions.
+// Standard rules for feature matching apply
+%feature("director:except") MyClass::meth1(int);
+%feature("director:except") MyClass::meth2;
+%feature("director:except") meth3;
+
+%typemap(directorthrows) MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected %{
+ if (Swig::ExceptionMatches(jenv,$error,"$packagepath/$javaclassname"))
+ throw $1_type(Swig::JavaExceptionMessage(jenv,$error).message());
+%}
+
+DECLARE_EXCEPTION(ExceptionA)
+DECLARE_EXCEPTION(ExceptionB)
+DECLARE_EXCEPTION(Unexpected)
+
+%catches(MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected) MyClass::meth2();
+
+%inline {
+ class MyClass {
+ public:
+ virtual void meth1(int x) throw(MyNS::ExceptionA, MyNS::ExceptionB) = 0;
+ virtual void meth2() = 0; /* throws MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected */
+ virtual void meth3(float x) throw(MyNS::Unexpected) = 0;
+ virtual ~MyClass() {};
+ };
+}
+</pre>
+</div>
+
+<p>
+In this case the three different <code>directorthrows</code> typemaps will be used
+to generate the three different exception handlers for
+<code>meth1</code>, <code>meth2</code> and <code>meth3</code>. The generated
+handlers will have "if" blocks for each exception type specified, in
+the exception specification or <code>%catches</code> feature. The code block
+in the directorthrows typemap should always throw a c++ exception.
+</p>
+
+<p>Note that the <code>directorthrows</code> typemaps are important
+only if it is important for the the exceptions passed through the C++
+layer to be mapped to distinct C++ exceptions. If director methods
+are being called by C++ code that is itself wrapped in a
+Swig-generated java wrapper and access is always through this wrapper,
+the default Swig::DirectorException class provides enough information
+to reconstruct the original exception. In this case removing the
+$directorthrowshandlers replacement variable from the
+default <code>director:except</code> feature and simply always
+throwing a Swig::DirectorException will achieve the desired result.
+Along with this a generic exception feature is added to convert any
+caught <code>Swig::DirectorException</code>s back into the underlying
+java exceptions, for each method which may get a generic
+DirectorException from a wrapped director method.</p>
+
+<div class="code">
+<pre>
+%feature ("except",throws="Exception") MyClass::myMeth %{
+ try { $action }
+ catch (Swig::DirectorException & direxcp) {
+ // jenv always available in JNI code
+ // raise the java exception that originally caused the DirectorException
+ direxcp.raiseJavaException(jenv);
+ return $null;
+ }
+%}
+</pre>
+</div>
+
+<p>The <code>throws="Exception"</code> attribute on the exception
+feature is necessary if any of the translated exceptions will be
+checked exceptions, since the java compiler will otherwise assert that
+no checked exceptions can be thrown by the method. This may be more
+specific that the completely generic "Exception" class, of course. A
+similar feature must be added to director methods to allow checked
+exceptions to be thrown from the director method implementations.
+Here, no actual exception handling is needed - the feature simply
+is being used to add a generic checked exception signature to the
+generated director method wrapper. </p>
+
+<div class="code">
+<pre>
+%feature ("except",throws="Exception") MyDirectorClass::myDirMeth %{ %}
+</pre>
+</div>
+
+
<H2><a name="Java_allprotected"></a>24.6 Accessing protected members</H2>
View
1  Doc/Manual/Warnings.html
@@ -499,6 +499,7 @@
<li>474. Method <em>method</em> usage of the optimal attribute ignored in the out typemap as the following cannot be used to generate optimal code: <em>code</em>
<li>475. Multiple calls to <em>method</em> might be generated due to optimal attribute usage in the out typemap.
<li>476. Initialization using std::initializer_list.
+<li>477. Feature director:except on <em>Class::method</em> with $directorthrowshandlers requires directorthrows typemap for exception <em>Exception</em>
</ul>
View
23 Examples/test-suite/director_exception.i
@@ -41,6 +41,29 @@ class DirectorMethodException: public Swig::DirectorException {};
#endif
+#ifdef SWIGJAVA
+
+// Default for director exception warns about unmapped exceptions now in java
+// Suppress warnings for this older test
+// %warnfilter(476) Bar;
+
+// Default for java is to throw Swig::DirectorException if no
+// direct:except feature. Since methods below have exception specification
+// cannot throw director exception.
+
+// Change back to old 2.0 default behavior
+
+%feature("director:except") {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ // Dont clear exception, still be active when return to java execution
+ // Essentially ignore exception occurred -- old behavior.
+ return $null;
+ }
+}
+
+#endif
+
#ifdef SWIGRUBY
%feature("director:except") {
View
16 Examples/test-suite/java/Makefile.in
@@ -24,6 +24,8 @@ CPP_TEST_CASES = \
java_constants \
java_director \
java_director_assumeoverride \
+ java_director_exception_feature \
+ java_director_exception_feature_nspace \
java_enums \
java_jnitypes \
java_lib_arrays_dimensionless \
@@ -44,8 +46,18 @@ JAVA_PACKAGE = $*
JAVA_PACKAGEOPT = -package $(JAVA_PACKAGE)
SWIGOPT += $(JAVA_PACKAGEOPT)
+RUNME_CLASSPATH = -classpath .
+ifeq (Cygwin,$(shell uname -o 2>/dev/null))
+SEP=;
+else
+SEP=:
+endif
+
# Custom tests - tests with additional commandline options
java_nspacewithoutpackage.%: JAVA_PACKAGEOPT =
+java_director_exception_feature_nspace.%: JAVA_PACKAGEOPT =
+java_director_exception_feature_nspace.%: RUNME_CLASSPATH = -classpath .$(SEP)./java_director_exception_feature_nspace
+
nspace.%: JAVA_PACKAGE = $*Package
nspace_extend.%: JAVA_PACKAGE = $*Package
director_nspace.%: JAVA_PACKAGE = $*Package
@@ -84,8 +96,8 @@ setup = \
run_testcase = \
cd $(JAVA_PACKAGE) && $(COMPILETOOL) $(JAVAC) -classpath . `find . -name "*.java"` && cd .. && \
if [ -f $(srcdir)/$(SCRIPTPREFIX)$*$(SCRIPTSUFFIX) ]; then \
- $(COMPILETOOL) $(JAVAC) -classpath . -d . $(srcdir)/$(SCRIPTPREFIX)$*$(SCRIPTSUFFIX) && \
- env LD_LIBRARY_PATH="$(JAVA_PACKAGE):$$LD_LIBRARY_PATH" PATH="$(JAVA_PACKAGE):$$PATH" SHLIB_PATH="$(JAVA_PACKAGE):$$SHLIB_PATH" DYLD_LIBRARY_PATH="$(JAVA_PACKAGE):$$DYLD_LIBRARY_PATH" $(RUNTOOL) $(JAVA) $(JAVAFLAGS) -classpath . $*_runme; \
+ $(COMPILETOOL) $(JAVAC) $(RUNME_CLASSPATH) -d . $(srcdir)/$(SCRIPTPREFIX)$*$(SCRIPTSUFFIX) && \
+ env LD_LIBRARY_PATH="$(JAVA_PACKAGE):$$LD_LIBRARY_PATH" PATH="$(JAVA_PACKAGE):$$PATH" SHLIB_PATH="$(JAVA_PACKAGE):$$SHLIB_PATH" DYLD_LIBRARY_PATH="$(JAVA_PACKAGE):$$DYLD_LIBRARY_PATH" $(RUNTOOL) $(JAVA) $(JAVAFLAGS) $(RUNME_CLASSPATH) $*_runme; \
fi
# Clean: remove testcase directories
View
150 Examples/test-suite/java/java_director_exception_feature_nspace_runme.java
@@ -0,0 +1,150 @@
+import MyNS.*;
+import MyNS_JNI.*;
+
+class java_director_exception_feature_nspace_Consts {
+ public static final String PINGEXCP1 = "Ping MyJavaException1"; // should get translated through an int on ping
+ public static final String PINGEXCP2 = "Ping MyJavaException2";
+
+ public static final String PONGEXCP1 = "Pong MyJavaException1";
+ public static final String PONGEXCP2 = "Pong MyJavaException2";
+ public static final String PONGUNEXPECTED = "Pong MyJavaUnexpected";
+ public static final String TRANSLATED_NPE = "Pong Translated NPE";
+
+ public static final String GENERICPONGEXCP1 = "GenericPong Wrapped MyJavaException1";
+ public static final String GENERICPONGEXCP2 = "GenericPong New Checked Exception";
+ public static final String GENERICPONGEXCP3 = "GenericPong New Unchecked Exception";
+ public static final String GENERICPONGEXCP4 = "GenericPong New Exception Without String ctor";
+}
+
+// an exception not mentioned or wrapped by the swig interface,
+// to reconstruct using generic DirectorException handling
+class NewCheckedException extends Exception {
+ public NewCheckedException(String s) {
+ super(s);
+ }
+}
+
+// an exception not mentioned or wrapped by the swig interface,
+// to reconstruct using generic DirectorException handling
+class NewUncheckedException extends RuntimeException {
+ public NewUncheckedException(String s) {
+ super(s);
+ }
+}
+
+// an exception not constructable from a string,
+// to test DirectorException fallback reconstruction
+class UnconstructableException extends Exception {
+ private int extrastate;
+ public UnconstructableException(int a, String s) {
+ super(s);
+ extrastate = a;
+ }
+}
+
+class java_director_exception_feature_nspace_MyFooDirectorImpl extends Foo {
+
+ public java_director_exception_feature_nspace_MyFooDirectorImpl() { };
+
+ @Override
+ public String ping(int excp) throws MyJavaException1, MyJavaException2 {
+ if (excp == 1) throw new MyJavaException1(java_director_exception_feature_nspace_Consts.PINGEXCP1);
+ if (excp == 2) throw new MyJavaException2(java_director_exception_feature_nspace_Consts.PINGEXCP2);
+ return "Ping director returned";
+ }
+ @Override
+ public String pong(int excp) throws MyJavaException1, MyJavaException2, MyJavaUnexpected {
+ if (excp == 1) throw new MyJavaException1(java_director_exception_feature_nspace_Consts.PONGEXCP1);
+ if (excp == 2) throw new MyJavaException2(java_director_exception_feature_nspace_Consts.PONGEXCP2);
+ if (excp == 3) throw new MyJavaUnexpected(java_director_exception_feature_nspace_Consts.PONGUNEXPECTED);
+ if (excp == 4) throw new java.lang.NullPointerException(java_director_exception_feature_nspace_Consts.TRANSLATED_NPE); // should be translated to ::Unexpected
+ return "Pong director returned";
+ }
+
+ @Override
+ public String genericpong(int excp) throws MyJavaException1, NewCheckedException, UnconstructableException {
+ if (excp == 1)
+ throw new MyJavaException1(java_director_exception_feature_nspace_Consts.GENERICPONGEXCP1);
+ if (excp == 2)
+ throw new NewCheckedException(java_director_exception_feature_nspace_Consts.GENERICPONGEXCP2);
+ if (excp == 3)
+ throw new NewUncheckedException(java_director_exception_feature_nspace_Consts.GENERICPONGEXCP3);
+ if (excp == 4)
+ throw new UnconstructableException(1, java_director_exception_feature_nspace_Consts.GENERICPONGEXCP4);
+ return "GenericPong director returned";
+ }
+}
+
+public class java_director_exception_feature_nspace_runme {
+
+ static {
+ try {
+ System.loadLibrary("java_director_exception_feature_nspace");
+ } catch (UnsatisfiedLinkError e) {
+ System.err.println("Native code library failed to load. See the chapter on Dynamic Linking Problems in the SWIG Java documentation for help.\n" + e);
+ System.exit(1);
+ }
+ }
+
+ public static void fail(String msg) {
+ System.err.println(msg); System.exit(1);
+ }
+ public static void failif(boolean cond, String msg) {
+ if (cond) fail(msg);
+ }
+
+
+ public static void main(String argv[]) {
+
+ Bar b = new Bar(new java_director_exception_feature_nspace_MyFooDirectorImpl());
+ try {
+
+ try { b.ping(0); } catch (Exception e)
+ { fail("Exception should not have been thrown: " + e + " from ping(0)"); }
+ try { b.ping(1); fail("No exception thrown in ping(1)"); } catch (MyJavaException1 e)
+ // Should say "Threw some integer", see java_director_exception_feature.i Foo::ping throws a "1"
+ { failif( ! "Threw some integer".equals(e.getMessage()), "Ping exception not translated through int: '" + e.getMessage() + "'"); }
+ try { b.ping(2); fail("No exception thrown in ping(2)"); } catch (MyJavaException2 e)
+ { failif( ! java_director_exception_feature_nspace_Consts.PINGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+
+ try { b.pong(0); } catch (Exception e)
+ { fail("Exception should not have been thrown: " + e + " from pong(0)"); }
+ try { b.pong(1); fail("No exception thrown in pong(1)"); } catch (MyJavaException1 e)
+ { failif( ! java_director_exception_feature_nspace_Consts.PONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(2); fail("No exception thrown in pong(2)");} catch (MyJavaException2 e)
+ { failif( ! java_director_exception_feature_nspace_Consts.PONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(3); fail("No exception thrown in pong(3)");} catch (MyJavaUnexpected e)
+ { failif( ! java_director_exception_feature_nspace_Consts.PONGUNEXPECTED.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(4); fail("No exception thrown in pong(4)"); } catch (MyJavaUnexpected e)
+ { failif( ! java_director_exception_feature_nspace_Consts.TRANSLATED_NPE.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+
+
+ try { b.genericpong(0); }
+ catch (Exception e) {
+ fail("Exception should not have been thrown: " + e + " from genericpong(0)");
+ }
+ try { b.genericpong(1); fail("No exception thrown in genericpong(1)"); }
+ catch (MyJavaException1 e) {
+ failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(2); fail("No exception thrown in genericpong(2)");}
+ catch (NewCheckedException e) {
+ failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(3); fail("No exception thrown in genericpong(3)");}
+ catch (NewUncheckedException e) {
+ failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP3.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(4); fail("No exception thrown in genericpong(4)");}
+ catch (RuntimeException e) {
+ failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RumtimeException");
+ failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP4.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexpected exception thrown or exception not mapped properly");
+ }
+
+ }
+}
View
152 Examples/test-suite/java/java_director_exception_feature_runme.java
@@ -0,0 +1,152 @@
+
+import java_director_exception_feature.*;
+
+class java_director_exception_feature_Consts {
+ public static final String PINGEXCP1 = "Ping MyJavaException1"; // should get translated through an int on ping
+ public static final String PINGEXCP2 = "Ping MyJavaException2";
+
+ public static final String PONGEXCP1 = "Pong MyJavaException1";
+ public static final String PONGEXCP2 = "Pong MyJavaException2";
+ public static final String PONGUNEXPECTED = "Pong MyJavaUnexpected";
+ public static final String TRANSLATED_NPE = "Pong Translated NPE";
+
+ public static final String GENERICPONGEXCP1 = "GenericPong Wrapped MyJavaException1";
+ public static final String GENERICPONGEXCP2 = "GenericPong New Checked Exception";
+ public static final String GENERICPONGEXCP3 = "GenericPong New Unchecked Exception";
+ public static final String GENERICPONGEXCP4 = "GenericPong New Exception Without String ctor";
+
+}
+
+// an exception not mentioned or wrapped by the swig interface,
+// to reconstruct using generic DirectorException handling
+class NewCheckedException extends Exception {
+ public NewCheckedException(String s) {
+ super(s);
+ }
+}
+
+// an exception not mentioned or wrapped by the swig interface,
+// to reconstruct using generic DirectorException handling
+class NewUncheckedException extends RuntimeException {
+ public NewUncheckedException(String s) {
+ super(s);
+ }
+}
+
+// an exception not constructable from a string,
+// to test DirectorException fallback reconstruction
+class UnconstructableException extends Exception {
+ private int extrastate;
+ public UnconstructableException(int a, String s) {
+ super(s);
+ extrastate = a;
+ }
+}
+
+class java_director_exception_feature_MyFooDirectorImpl extends Foo {
+
+ public java_director_exception_feature_MyFooDirectorImpl() { };
+
+ @Override
+ public String ping(int excp) throws MyJavaException1, MyJavaException2 {
+ if (excp == 1) throw new MyJavaException1(java_director_exception_feature_Consts.PINGEXCP1);
+ if (excp == 2) throw new MyJavaException2(java_director_exception_feature_Consts.PINGEXCP2);
+ return "Ping director returned";
+ }
+ @Override
+ public String pong(int excp) throws MyJavaException1, MyJavaException2, MyJavaUnexpected {
+ if (excp == 1) throw new MyJavaException1(java_director_exception_feature_Consts.PONGEXCP1);
+ if (excp == 2) throw new MyJavaException2(java_director_exception_feature_Consts.PONGEXCP2);
+ if (excp == 3) throw new MyJavaUnexpected(java_director_exception_feature_Consts.PONGUNEXPECTED);
+ if (excp == 4) throw new java.lang.NullPointerException(java_director_exception_feature_Consts.TRANSLATED_NPE); // should be translated to ::Unexpected
+ return "Pong director returned";
+ }
+
+ @Override
+ public String genericpong(int excp) throws MyJavaException1, NewCheckedException, UnconstructableException {
+ if (excp == 1)
+ throw new MyJavaException1(java_director_exception_feature_Consts.GENERICPONGEXCP1);
+ if (excp == 2)
+ throw new NewCheckedException(java_director_exception_feature_Consts.GENERICPONGEXCP2);
+ if (excp == 3)
+ throw new NewUncheckedException(java_director_exception_feature_Consts.GENERICPONGEXCP3);
+ if (excp == 4)
+ throw new UnconstructableException(1, java_director_exception_feature_Consts.GENERICPONGEXCP4);
+ return "GenericPong director returned";
+ }
+}
+
+public class java_director_exception_feature_runme {
+
+ static {
+ try {
+ System.loadLibrary("java_director_exception_feature");
+ } catch (UnsatisfiedLinkError e) {
+ System.err.println("Native code library failed to load. See the chapter on Dynamic Linking Problems in the SWIG Java documentation for help.\n" + e);
+ System.exit(1);
+ }
+ }
+
+ public static void fail(String msg) {
+ System.err.println(msg); System.exit(1);
+ }
+ public static void failif(boolean cond, String msg) {
+ if (cond) fail(msg);
+ }
+
+
+ public static void main(String argv[]) {
+
+ Bar b = new Bar(new java_director_exception_feature_MyFooDirectorImpl());
+ try {
+
+ try { b.ping(0); } catch (Exception e)
+ { fail("Exception should not have been thrown: " + e + " from ping(0)"); }
+ try { b.ping(1); fail("No exception thrown in ping(1)"); } catch (MyJavaException1 e)
+ // Should say "Threw some integer", see java_director_exception_feature.i Foo::ping throws a "1"
+ { failif( ! "Threw some integer".equals(e.getMessage()), "Ping exception not translated through int: '" + e.getMessage() + "'"); }
+ try { b.ping(2); fail("No exception thrown in ping(2)"); } catch (MyJavaException2 e)
+ { failif( ! java_director_exception_feature_Consts.PINGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+
+ try { b.pong(0); } catch (Exception e)
+ { fail("Exception should not have been thrown: " + e + " from pong(0)"); }
+ try { b.pong(1); fail("No exception thrown in pong(1)"); } catch (MyJavaException1 e)
+ { failif( ! java_director_exception_feature_Consts.PONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(2); fail("No exception thrown in pong(2)");} catch (MyJavaException2 e)
+ { failif( ! java_director_exception_feature_Consts.PONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(3); fail("No exception thrown in pong(3)");} catch (MyJavaUnexpected e)
+ { failif( ! java_director_exception_feature_Consts.PONGUNEXPECTED.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+ try { b.pong(4); fail("No exception thrown in pong(4)"); } catch (MyJavaUnexpected e)
+ { failif( ! java_director_exception_feature_Consts.TRANSLATED_NPE.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'"); }
+
+
+ try { b.genericpong(0); }
+ catch (Exception e) {
+ fail("Exception should not have been thrown: " + e + " from genericpong(0)");
+ }
+ try { b.genericpong(1); fail("No exception thrown in genericpong(1)"); }
+ catch (MyJavaException1 e) {
+ failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(2); fail("No exception thrown in genericpong(2)");}
+ catch (NewCheckedException e) {
+ failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(3); fail("No exception thrown in genericpong(3)");}
+ catch (NewUncheckedException e) {
+ failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP3.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+ try { b.genericpong(4); fail("No exception thrown in genericpong(4)");}
+ catch (RuntimeException e) {
+ failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RumtimeException");
+ failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP4.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
+ }
+
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexpected exception thrown or exception not mapped properly");
+ }
+
+ }
+}
View
209 Examples/test-suite/java_director_exception_feature.i
@@ -0,0 +1,209 @@
+%module(directors="1") java_director_exception_feature
+
+%include <std_except.i>
+
+%{
+#if defined(_MSC_VER)
+ #pragma warning(disable: 4290) // C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
+#endif
+
+#include <string>
+%}
+
+%include <std_string.i>
+
+// DEFINE exceptions in header section using std::runtime_error
+%{
+ #include <exception>
+ #include <iostream>
+
+ namespace MyNS {
+
+ struct Exception1 : public std::runtime_error {
+ Exception1(const std::string& what):runtime_error(what) {}
+ };
+ struct Exception2 : public std::runtime_error {
+ Exception2(const std::string& what):runtime_error(what) {}
+ };
+ struct Unexpected : public std::runtime_error {
+ Unexpected(const std::string& what):runtime_error(what) {}
+ };
+
+ }
+
+%}
+
+// Add an explicit handler for Foo::ping, mapping one java exception back to an 'int'
+%feature("director:except") MyNS::Foo::ping {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear(); // clear java exception since mapping to c++ exception
+ if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException1")) {
+ throw 1;
+ } else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException2")) {
+ std::string msg(Swig::JavaExceptionMessage(jenv,$error).message());
+ throw MyNS::Exception2(msg);
+ } else {
+ std::cerr << "Test failed, unexpected exception thrown: " <<
+ Swig::JavaExceptionMessage(jenv,$error).message() << std::endl;
+ throw std::runtime_error("unexpected exception in Foo::ping");
+ }
+ }
+}
+
+// Use default handler on Foo::pong, with directorthrows typemaps
+
+// directorthrows typemaps for java->c++ conversions
+%typemap(directorthrows) MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected %{
+ if (Swig::ExceptionMatches(jenv, $error, "$packagepath/$javaclassname")) {
+ std::string msg(Swig::JavaExceptionMessage(jenv,$error).message());
+ throw $1_type(msg);
+ }
+%}
+
+// Override the director:except feature so exception specification is not violated
+// (Cannot use built-in default of throw DirectorException)
+%feature("director:except") MyNS::Foo::pong %{
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ $directorthrowshandlers
+ throw ::MyNS::Unexpected(Swig::JavaExceptionMessage(jenv,$error).message());
+ }
+%}
+
+// TODO 'throws' typemap emitted by emit_action (emit.cxx) has no way
+// to get access to language specific special variables like
+// $javaclassname or $packagepath ("java_director_exception_feature" here)
+
+// throws typemaps for c++->java exception conversions
+%typemap(throws,throws="MyJavaException1") MyNS::Exception1 %{
+ jclass excpcls = jenv->FindClass("java_director_exception_feature/MyJavaException1");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+%typemap(throws,throws="MyJavaException1") int %{
+ jclass excpcls = jenv->FindClass("java_director_exception_feature/MyJavaException1");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, "Threw some integer");
+ }
+ return $null;
+%}
+
+%typemap(throws,throws="MyJavaException2") MyNS::Exception2 %{
+ jclass excpcls = jenv->FindClass("java_director_exception_feature/MyJavaException2");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+%typemap(throws,throws="MyJavaUnexpected") MyNS::Unexpected %{
+ jclass excpcls = jenv->FindClass("java_director_exception_feature/MyJavaUnexpected");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+// Use generic exception translation approach like python, ruby
+
+%feature("director:except") MyNS::Foo::genericpong {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ throw Swig::DirectorException(jenv,$error);
+ }
+}
+
+// %exception with throws attribute. Need throws attribute for checked exceptions
+%feature ("except",throws="Exception") MyNS::Foo::genericpong %{
+%}
+
+%feature ("except",throws="Exception") MyNS::Bar::genericpong %{
+ try { $action }
+ catch (Swig::DirectorException & direxcp) {
+ direxcp.raiseJavaException(jenv); // jenv always available in JNI code
+ return $null;
+ }
+%}
+
+
+
+%feature("director") Foo;
+
+// Rename exceptions on java side to make translation of exceptions more clear
+%rename(MyJavaException1,fullname=1) MyNS::Exception1;
+%rename(MyJavaException2,fullname=1) MyNS::Exception2;
+%rename(MyJavaUnexpected,fullname=1) MyNS::Unexpected;
+
+%typemap(javabase) ::MyNS::Exception1,::MyNS::Exception2,::MyNS::Unexpected "java.lang.Exception";
+%rename(getMessage) what(); // Rename all what() methods
+
+namespace MyNS {
+
+ struct Exception1 {
+ Exception1(const std::string& what);
+ const char * what();
+ };
+ struct Exception2 {
+ Exception2(const std::string& what);
+ const char * what();
+ };
+ struct Unexpected {
+ Unexpected(const std::string& what);
+ const char * what();
+ };
+
+}
+// In general it is better to use %catches instead of an exception specification on the method
+// since violating an exception specification calls terminate() preventing catch-all behavior
+// like throwing std::runtime_error. But an exception specification must be used if the
+// actual interface being wrapped does use them.
+%catches(MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected) MyNS::Foo::pong;
+%catches(MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected) MyNS::Bar::pong;
+
+%inline %{
+
+namespace MyNS {
+
+class Foo {
+public:
+ virtual ~Foo() {}
+ // ping java implementation throws a java Exception1 or an Exception2 if excp is 1 or 2.
+ // pong java implementation throws Exception1,Exception2,Unexpected,NullPointerException for 1,2,3,4
+ virtual std::string ping(int excp) throw(int,MyNS::Exception2) = 0;
+ virtual std::string pong(int excp) /* throws MyNS::Exception1 MyNS::Exception2 MyNS::Unexpected) */ = 0;
+ virtual std::string genericpong(int excp) /* unspecified throws - exception is always DirectorException in C++, translated back to whatever thrown in java */ = 0;
+};
+
+// Make a bar from a foo, so a call to Java Bar
+// goes Java Bar -> C++ Bar -> C++ Foo -> Java Foo Director
+
+class Bar {
+public:
+ Bar(Foo* d) { delegate=d; }
+ virtual std::string ping(int excp) throw(int,MyNS::Exception2)
+ {
+ return delegate->ping(excp);
+ }
+
+ virtual std::string pong(int excp) /* throws MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected */
+ {
+ return delegate->pong(excp);
+ }
+
+ virtual std::string genericpong(int excp)
+ {
+ return delegate->genericpong(excp);
+ }
+
+private:
+ Foo * delegate;
+};
+
+}
+%}
View
216 Examples/test-suite/java_director_exception_feature_nspace.i
@@ -0,0 +1,216 @@
+%module(directors="1") java_director_exception_feature_nspace
+
+%include <std_except.i>
+
+%nspace; // turn namespace feature on for everything.
+
+// When using namespaces with no -package, must put JNI classes into a namespace
+%pragma(java) jniclasspackage=%{MyNS_JNI%}
+%warnfilter(826);
+
+%{
+#if defined(_MSC_VER)
+ #pragma warning(disable: 4290) // C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
+#endif
+
+#include <string>
+%}
+
+%include <std_string.i>
+
+// DEFINE exceptions in header section using std::runtime_error
+%{
+ #include <exception>
+ #include <iostream>
+
+ namespace MyNS {
+
+ struct Exception1 : public std::runtime_error {
+ Exception1(const std::string& what):runtime_error(what) {}
+ };
+ struct Exception2 : public std::runtime_error {
+ Exception2(const std::string& what):runtime_error(what) {}
+ };
+ struct Unexpected : public std::runtime_error {
+ Unexpected(const std::string& what):runtime_error(what) {}
+ };
+
+ }
+
+%}
+
+// Add an explicit handler for Foo::ping, mapping one java exception back to an 'int'
+%feature("director:except") MyNS::Foo::ping {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear(); // clear java exception since mapping to c++ exception
+ if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyNS/MyJavaException1")) {
+ throw 1;
+ } else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyNS/MyJavaException2")) {
+ std::string msg(Swig::JavaExceptionMessage(jenv,$error).message());
+ throw MyNS::Exception2(msg);
+ } else {
+ std::cerr << "Test failed, unexpected exception thrown: " <<
+ Swig::JavaExceptionMessage(jenv,$error).message() << std::endl;
+ throw std::runtime_error("unexpected exception in Foo::ping");
+ }
+ }
+}
+
+// Use default handler on Foo::pong, with directorthrows typemaps
+
+// directorthrows typemaps for java->c++ conversions
+%typemap(directorthrows) MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected %{
+ if (Swig::ExceptionMatches(jenv, $error, "$packagepath/$javaclassname")) {
+ std::string msg(Swig::JavaExceptionMessage(jenv,$error).message());
+ throw $1_type(msg);
+ }
+%}
+
+// Override the director:except feature so exception specification is not violated
+// (Cannot use built-in default of throw DirectorException)
+%feature("director:except") MyNS::Foo::pong %{
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ $directorthrowshandlers
+ throw ::MyNS::Unexpected(Swig::JavaExceptionMessage(jenv,$error).message());
+ }
+%}
+
+// TODO 'throws' typemap emitted by emit_action (emit.cxx) has no way
+// to get access to language specific special variables like
+// $javaclassname or $packagepath ("java_director_exception_feature" here)
+
+// throws typemaps for c++->java exception conversions
+%typemap(throws,throws="MyNS.MyJavaException1") MyNS::Exception1 %{
+ jclass excpcls = jenv->FindClass("MyNS/MyJavaException1");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+%typemap(throws,throws="MyNS.MyJavaException1") int %{
+ jclass excpcls = jenv->FindClass("MyNS/MyJavaException1");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, "Threw some integer");
+ }
+ return $null;
+%}
+
+%typemap(throws,throws="MyNS.MyJavaException2") MyNS::Exception2 %{
+ jclass excpcls = jenv->FindClass("MyNS/MyJavaException2");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+
+%typemap(throws,throws="MyNS.MyJavaUnexpected") MyNS::Unexpected %{
+ jclass excpcls = jenv->FindClass("MyNS/MyJavaUnexpected");
+ if (excpcls) {
+ jenv->ThrowNew(excpcls, $1.what());
+ }
+ return $null;
+%}
+
+// Use generic exception translation approach like python, ruby
+
+%feature("director:except") MyNS::Foo::genericpong {
+ jthrowable $error = jenv->ExceptionOccurred();
+ if ($error) {
+ jenv->ExceptionClear();
+ throw Swig::DirectorException(jenv,$error);
+ }
+}
+
+// %exception with throws attribute. Need throws attribute for checked exceptions
+%feature ("except",throws="Exception") MyNS::Foo::genericpong %{
+%}
+
+%feature ("except",throws="Exception") MyNS::Bar::genericpong %{
+ try { $action }
+ catch (Swig::DirectorException & direxcp) {
+ direxcp.raiseJavaException(jenv); // jenv always available in JNI code
+ return $null;
+ }
+%}
+
+
+
+%feature("director") Foo;
+
+// Rename exceptions on java side to make translation of exceptions more clear
+%rename(MyJavaException1,fullname=1) MyNS::Exception1;
+%rename(MyJavaException2,fullname=1) MyNS::Exception2;
+%rename(MyJavaUnexpected,fullname=1) MyNS::Unexpected;
+
+%typemap(javabase) ::MyNS::Exception1,::MyNS::Exception2,::MyNS::Unexpected "java.lang.Exception";
+%rename(getMessage) what(); // Rename all what() methods
+
+namespace MyNS {
+
+ struct Exception1 {
+ Exception1(const std::string& what);
+ const char * what();
+ };
+ struct Exception2 {
+ Exception2(const std::string& what);
+ const char * what();
+ };
+ struct Unexpected {
+ Unexpected(const std::string& what);
+ const char * what();
+ };
+
+}
+// In general it is better to use %catches instead of an exception specification on the method
+// since violating an exception specification calls terminate() preventing catch-all behavior
+// like throwing std::runtime_error. But an exception specification must be used if the
+// actual interface being wrapped does use them.
+%catches(MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected) MyNS::Foo::pong;
+%catches(MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected) MyNS::Bar::pong;
+
+%inline %{
+
+namespace MyNS {
+
+class Foo {
+public:
+ virtual ~Foo() {}
+ // ping java implementation throws a java Exception1 or an Exception2 if excp is 1 or 2.
+ // pong java implementation throws Exception1,Exception2,Unexpected,NullPointerException for 1,2,3,4
+ virtual std::string ping(int excp) throw(int,MyNS::Exception2) = 0;
+ virtual std::string pong(int excp) /* throws MyNS::Exception1 MyNS::Exception2 MyNS::Unexpected) */ = 0;
+ virtual std::string genericpong(int excp) /* unspecified throws - exception is always DirectorException in C++, translated back to whatever thrown in java */ = 0;
+};
+
+// Make a bar from a foo, so a call to Java Bar
+// goes Java Bar -> C++ Bar -> C++ Foo -> Java Foo Director
+
+class Bar {
+public:
+ Bar(Foo* d) { delegate=d; }
+ virtual std::string ping(int excp) throw(int,MyNS::Exception2)
+ {
+ return delegate->ping(excp);
+ }
+
+ virtual std::string pong(int excp) /* throws MyNS::Exception1,MyNS::Exception2,MyNS::Unexpected */
+ {
+ return delegate->pong(excp);
+ }
+
+ virtual std::string genericpong(int excp)
+ {
+ return delegate->genericpong(excp);
+ }
+
+private:
+ Foo * delegate;
+};
+
+}
+%}
View
238 Lib/java/director.swg
@@ -193,6 +193,242 @@ namespace Swig {
};
}
-#endif /* __cplusplus */
+// Default exception handler for all director methods.
+// Can override this for specific methods
+// This just documents the default. It is not actually attached as a feature
+// for efficiency reasons (and is defined in java.cxx) It can be overridden
+// as a feature for specific methods or globally.
+
+//%feature("director:except") %{
+// jthrowable $error = jenv->ExceptionOccurred();
+// if ($error) {
+// jenv->ExceptionClear();
+// $directorthrowshandlers
+// throw Swig::DirectorException(jenv, $error);
+// }
+//%}
+
+// Define some utility methods and classes. Cannot use fragments since
+// these may be used by %features and in directorthrows typemaps
+
+#include <exception>
+
+// Simple holder for exception messages, allowing access to a c-style string
+namespace Swig {
+ struct JavaString {
+ JavaString(JNIEnv * jenv, jstring jstr):jenv_(jenv), jstr_(jstr), cstr_(0) {
+ if (jenv_ && jstr_) {
+ // Get the null-terminated c-string out, and hold it
+ cstr_ = (const char *) jenv_->GetStringUTFChars(jstr_, NULL);
+ }
+ }
+ ~JavaString() {
+ if (jenv_ && jstr_ && cstr_) {
+ jenv_->ReleaseStringUTFChars(jstr_, cstr_);
+ }
+ }
+ const char *cstr() {
+ return cstr_ ? cstr_ : "";
+ }
+
+ private:
+ JNIEnv * jenv_;
+ jstring jstr_;
+ const char * cstr_;
+
+ // non-copyable
+ JavaString(const JavaString &);
+ JavaString & operator=(const JavaString &);
+ };
+
+ struct JavaExceptionMessage {
+ JavaExceptionMessage(JNIEnv * jenv, jthrowable excp) :
+ jstrholder_(jenv,exceptionMsgJstr(jenv,excp)) {
+ }
+ ~JavaExceptionMessage() {
+ }
+ const char *message() {
+ return jstrholder_.cstr();
+ }
+
+ private:
+ JavaString jstrholder_;
+
+ // non-copyable
+ JavaExceptionMessage(const JavaExceptionMessage &);
+ JavaExceptionMessage & operator=(const JavaExceptionMessage &);
+
+ // Static method to initialize jstrholder_
+ static jstring exceptionMsgJstr(JNIEnv * jenv, jthrowable excp) {
+ jstring jmsg = NULL;
+ if (jenv && excp) {
+ jenv->ExceptionClear(); // Cannot invoke methods with pending exception
+ jclass thrwclz = jenv->GetObjectClass(excp);
+ if (thrwclz) {
+ // if no getMessage() or other exception, no msg available.
+ jmethodID getThrowableMsgMethodID =
+ jenv->GetMethodID(thrwclz, "getMessage", "()Ljava/lang/String;");
+ if (getThrowableMsgMethodID && !jenv->ExceptionCheck()) {
+ // if problem accessing exception message string, no msg available.
+ jmsg = (jstring)
+ jenv->CallObjectMethod(excp, getThrowableMsgMethodID);
+ }
+ }
+ if (jmsg == NULL && jenv->ExceptionCheck()) {
+ jenv->ExceptionClear();
+ }
+ }
+ return jmsg;
+ }
+ };
+
+
+ ////////////////////////////////////
+
+ bool ExceptionMatches(JNIEnv * jenv, jthrowable excp,
+ const char *clzname) {
+ jboolean matches = false;
+
+ if (excp && jenv && clzname) {
+ // Have to clear exceptions for correct behavior. Code around
+ // ExceptionMatches should restore pending exception if
+ // desired - already have throwable.
+ jenv->ExceptionClear();
+
+ jclass clz = jenv->FindClass(clzname);
+ if (clz && ! jenv->ExceptionCheck()) {
+ jclass classclz = jenv->GetObjectClass(clz);
+ jmethodID isInstanceMethodID =
+ jenv->GetMethodID(classclz, "isInstance", "(Ljava/lang/Object;)Z");
+ if (isInstanceMethodID) {
+ matches = (jboolean)
+ jenv->CallBooleanMethod(clz, isInstanceMethodID, excp);
+ }
+ }
+ // This may happen if user typemaps or director:except
+ // features call ExceptionMatches incorrectly, typically with
+ // an invalid clzname argument. Uncommenting the debug lines
+ // may help to diagnose.
+ // As is, this leaves the underlying case as a pending exception
+ // which may not be that clear (e.g. ClassNotFoundException)
+
+ // if (jenv->ExceptionCheck()) {
+ // JavaExceptionMessage jstrmsg(jenv,jenv->ExceptionOccurred());
+ // std::cerr << "Error: ExceptionMatches: class '" <<
+ // clzname << "' : " << jstrmsg.message() << std::endl;
+ // }
+ }
+ return matches;
+ }
+
+ // Provide the class name to allow reconstruction of the original exception
+ struct DirectorException : std::exception {
+
+ // Construct a DirectorException from a java throwable
+ DirectorException(JNIEnv* jenv,jthrowable excp) : classname_(0), msg_(0) {
+ jstring jstr_classname = NULL;
+ jmethodID mid_getName = NULL;
+ jclass thrwclz;
+ jclass clzclz;
+
+ if (excp) {
+ // Get the exception class, like Exception
+ thrwclz = jenv->GetObjectClass(excp);
+ if (thrwclz) {
+ // Get the java.lang.Class class
+ clzclz = jenv->GetObjectClass(thrwclz);
+ if (clzclz) {
+ mid_getName = jenv->GetMethodID(clzclz, "getName", "()Ljava/lang/String;");
+ if (mid_getName) {
+ // Get the excp class name
+ jstr_classname = (jstring)(jenv->CallObjectMethod(thrwclz, mid_getName));
+ }
+ }
+ }
+ }
+ // Copy strings, since no guarantee jenv will be active when handled
+ // If classname_ is 0, returned as "UnknownException"
+ if (jstr_classname) {
+ JavaString classname(jenv, jstr_classname);
+ classname_ = copypath(classname.cstr());
+ }
+ JavaExceptionMessage exceptionmsg(jenv, excp);
+ msg_ = copystr(exceptionmsg.message());
+ }
+
+ // Throw as a wrapped Runtime Error explicitly.
+ DirectorException(const char * msg) : classname_(0), msg_(0) {
+ classname_ = copypath("java/lang/RuntimeError");
+ msg_ = copystr(msg);
+ }
+
+ ~DirectorException() throw() {
+ delete[] classname_;
+ delete[] msg_;
+ }
+
+ // If there was problem finding classname, keep track of error
+ // On raiseJavaException will be mapped to a RuntimeException
+ const char* classname() const throw() {
+ return classname_ ? classname_ : "UnknownException";
+ }
+ const char* what() const throw() {
+ return msg_ ? msg_ : "";
+ }
+
+ // Python director code provides static raise() methods
+ // Omitted here: Seems less good than
+ // throw DirectorException(jenv, excp)
+ // from which compiler can infer a C++ exception is thrown.
+
+ // Reconstruct and raise the Java Exception that caused the DirectorException
+ void raiseJavaException(JNIEnv* jenv) {
+ if (jenv) {
+ jenv->ExceptionClear();
+
+ jmethodID strCtorID = 0;
+ jclass excpclz = jenv->FindClass(classname());
+
+ if (excpclz) {
+ strCtorID = jenv->GetMethodID(excpclz,"<init>","(Ljava/lang/String;)V");
+ }
+
+ if (strCtorID) {
+ // If exception has a string constructor, throw an instance
+ jenv->ThrowNew(excpclz, what());
+ } else {
+ // Else, throw a runtime
+ SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what() );
+ }
+ }
+ }
+
+ private:
+ const char * classname_;
+ const char * msg_;
+
+ static const char * copypath(const char * srcmsg) {
+ return copystr(srcmsg, 1);
+ }
+ static const char * copystr(const char * srcmsg, int pathrepl=0) {
+ char * target = 0;
+ if (srcmsg) {
+ int msglen = 1+strlen(srcmsg); //+1 for null terminator
+ target = new char[msglen];
+ strncpy(target, srcmsg, msglen);
+ }
+ // If pathrepl, replace any '.' with '/'
+ if (pathrepl) {
+ for(char *c=target; *c; ++c) {
+ if ('.' == *c) *c = '/';
+ }
+ }
+ return target;
+ }
+ };
+
+}
+
+#endif /* __cplusplus */
View
3  Lib/java/std_string.i
@@ -38,7 +38,8 @@ class string;
%typemap(directorout) string
%{ if(!$input) {
- SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null string");
+ if (!jenv->ExceptionCheck())
+ SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null string");
return $null;
}
const char *$1_pstr = (const char *)jenv->GetStringUTFChars($input, 0);
View
1  Source/Include/swigwarn.h
@@ -176,6 +176,7 @@
#define WARN_TYPEMAP_OUT_OPTIMAL_IGNORED 474
#define WARN_TYPEMAP_OUT_OPTIMAL_MULTIPLE 475
#define WARN_TYPEMAP_INITIALIZER_LIST 476
+#define WARN_TYPEMAP_DIRECTORTHROWS_UNDEF 477
/* -- Fragments -- */
#define WARN_FRAGMENT_NOT_FOUND 490
View
233 Source/Modules/java.cxx
@@ -742,15 +742,25 @@ class JAVA:public Language {
*----------------------------------------------------------------------*/
UpcallData *addUpcallMethod(String *imclass_method, String *class_method, String *imclass_desc, String *class_desc, String *decl) {
+ UpcallData *udata;
+ String *imclass_methodidx;
+ String *class_methodidx;
+ Hash *new_udata;
String *key = NewStringf("%s|%s", imclass_method, decl);
++curr_class_dmethod;
- String *imclass_methodidx = NewStringf("%d", n_dmethods);
- String *class_methodidx = NewStringf("%d", n_dmethods - first_class_dmethod);
+ /* Do we know about this director class already? */
+ if ((udata = Getattr(dmethods_table, key))) {
+ Delete(key);
+ return Getattr(udata, "methodoff");
+ }
+
+ imclass_methodidx = NewStringf("%d", n_dmethods);
+ class_methodidx = NewStringf("%d", n_dmethods - first_class_dmethod);
n_dmethods++;
- Hash *new_udata = NewHash();
+ new_udata = NewHash();
Append(dmethods_seq, new_udata);
Setattr(dmethods_table, key, new_udata);
@@ -2939,20 +2949,36 @@ class JAVA:public Language {
* pt - parameter type
* tm - typemap contents that might contain the special variable to be replaced
* jnidescriptor - if set, inner class names are separated with '$' otherwise a '.'
+ * p - a parameter type, that may hold a javapackage typemap. If passed,
+ * the $packagepath will be substituted, but call method below instead.
* Outputs:
* tm - typemap contents complete with the special variable substitution
* Return:
* substitution_performed - flag indicating if a substitution was performed
* ----------------------------------------------------------------------------- */
- bool substituteClassname(SwigType *pt, String *tm, bool jnidescriptor = false) {
+ bool substituteClassname(SwigType *pt, String *tm,
+ bool jnidescriptor = false, Parm * pkgpathparm = 0)
+ {
+ return substituteClassnameAndPackagePath( pt, tm, jnidescriptor, pkgpathparm);
+ }
+
+ /* -----------------------------------------------------------------------------
+ * substituteClassnameAndPackagePath()
+ * Used to canonicalize JNI descriptors, including code emitting director throws typemaps.
+ *
+ * Only usage always has jnidescriptor AND p set. Maybe collapse args.
+ * ----------------------------------------------------------------------------- */
+ bool substituteClassnameAndPackagePath(SwigType *pt, String *tm,
+ bool jnidescriptor, Parm * pkgpathparm)
+ {
bool substitution_performed = false;
SwigType *type = Copy(SwigType_typedef_resolve_all(pt));
SwigType *strippedtype = SwigType_strip_qualifiers(type);
if (Strstr(tm, "$javaclassname")) {
SwigType *classnametype = Copy(strippedtype);
- substituteClassnameSpecialVariable(classnametype, tm, "$javaclassname", jnidescriptor);
+ substituteClassnameSpecialVariable(classnametype, tm, "$javaclassname", jnidescriptor, pkgpathparm);
substitution_performed = true;
Delete(classnametype);
}
@@ -2960,7 +2986,7 @@ class JAVA:public Language {
SwigType *classnametype = Copy(strippedtype);
Delete(SwigType_pop(classnametype));
if (Len(classnametype) > 0) {
- substituteClassnameSpecialVariable(classnametype, tm, "$*javaclassname", jnidescriptor);
+ substituteClassnameSpecialVariable(classnametype, tm, "$*javaclassname", jnidescriptor, pkgpathparm);
substitution_performed = true;
}
Delete(classnametype);
@@ -2968,7 +2994,7 @@ class JAVA:public Language {
if (Strstr(tm, "$&javaclassname")) {
SwigType *classnametype = Copy(strippedtype);
SwigType_add_pointer(classnametype);
- substituteClassnameSpecialVariable(classnametype, tm, "$&javaclassname", jnidescriptor);
+ substituteClassnameSpecialVariable(classnametype, tm, "$&javaclassname", jnidescriptor, pkgpathparm);
substitution_performed = true;
Delete(classnametype);
}
@@ -2982,27 +3008,46 @@ class JAVA:public Language {
/* -----------------------------------------------------------------------------
* substituteClassnameSpecialVariable()
* ----------------------------------------------------------------------------- */
+ void substituteClassnameSpecialVariable(SwigType *classnametype, String *tm, const char *classnamespecialvariable, bool jnidescriptor, Parm * pkgpathparm) {
+ String * replacementname;
- void substituteClassnameSpecialVariable(SwigType *classnametype, String *tm, const char *classnamespecialvariable, bool jnidescriptor) {
if (SwigType_isenum(classnametype)) {
- String *enumname = getEnumName(classnametype, jnidescriptor);
- if (enumname)
- Replaceall(tm, classnamespecialvariable, enumname);
- else
- Replaceall(tm, classnamespecialvariable, NewStringf("int"));
+ replacementname = Copy(getEnumName(classnametype, jnidescriptor));
+ if (!replacementname) {
+ replacementname = NewString("int");
+ }
} else {
- String *classname = getProxyName(classnametype);
- if (classname) {
- Replaceall(tm, classnamespecialvariable, classname); // getProxyName() works for pointers to classes too
- } else { // use $descriptor if SWIG does not know anything about this type. Note that any typedefs are resolved.
- String *descriptor = NewStringf("SWIGTYPE%s", SwigType_manglestr(classnametype));
- Replaceall(tm, classnamespecialvariable, descriptor);
-
+ replacementname = Copy(getProxyName(classnametype));
+ if (! replacementname) {
+ // use $descriptor if SWIG does not know anything about this type. Note that any typedefs are resolved.
+ replacementname = NewStringf("SWIGTYPE%s", SwigType_manglestr(classnametype));
// Add to hash table so that the type wrapper classes can be created later
- Setattr(swig_types_hash, descriptor, classnametype);
- Delete(descriptor);
+ Setattr(swig_types_hash, replacementname, classnametype);
}
}
+
+ if (pkgpathparm) {
+ if (Strchr(replacementname, '.') != NULL) {
+ // nspace feature in use, indicated by dots (!?)
+ // if replacementname is in name space form, a.b.c, discard
+ // packagepath from any string like $packagepath/$javaclassname
+ // since IT WAS ADDED in getProxyName
+ // But $packagepath could still be used by itself in same string
+ // since this is used to emit general code blocks for director
+ // exceptions
+ // So do this to get rid of all combined forms before subsitituting
+ Replaceall(tm, "$packagepath/$javaclassname","$javaclassname");
+ Replaceall(tm, "$packagepath/$*javaclassname","$*javaclassname");
+ Replaceall(tm, "$packagepath/$&javaclassname","$&javaclassname");
+ }
+ substitutePackagePath(tm, pkgpathparm);
+ }
+ if (jnidescriptor) {
+ Replaceall(replacementname,".","/");
+ }
+ Replaceall(tm, classnamespecialvariable, replacementname);
+
+ Delete(replacementname);
}
/* -----------------------------------------------------------------------------
@@ -3497,11 +3542,32 @@ class JAVA:public Language {
// Delete(method_attr);
}
+ /** Replace $packagepath using the javapackage typemap
+ associated with passed parm or global package_path
+ if none. "$packagepath/" is replaced with "" if no
+ package path is set.
+ */
+ void substitutePackagePath(String *text, Parm *p) {
+ String *pkg_path= 0;
+
+ if (p)
+ pkg_path = Swig_typemap_lookup("javapackage", p, "", 0);
+ if (!pkg_path || Len(pkg_path) == 0)
+ pkg_path = package_path;
+
+ if (Len(pkg_path) > 0) {
+ Replaceall(text, "$packagepath", pkg_path);
+ } else {
+ Replaceall(text, "$packagepath/", empty_string);
+ Replaceall(text, "$packagepath", empty_string);
+ }
+ }
+
/* ---------------------------------------------------------------
* Canonicalize the JNI field descriptor
*
- * Replace the $javapackage and $javaclassname family of special
- * variables with the desired package and Java proxy name as
+ * Replace the $packagepath and $javaclassname family of
+ * variables with the desired package and Java descriptor as
* required in the JNI field descriptors.
*
* !!SFM!! If $packagepath occurs in the field descriptor, but
@@ -3511,27 +3577,11 @@ class JAVA:public Language {
* --------------------------------------------------------------- */
String *canonicalizeJNIDescriptor(String *descriptor_in, Parm *p) {
- String *pkg_path = Swig_typemap_lookup("javapackage", p, "", 0);
SwigType *type = Getattr(p, "type");
- if (!pkg_path || Len(pkg_path) == 0)
- pkg_path = package_path;
-
String *descriptor_out = Copy(descriptor_in);
-
- substituteClassname(type, descriptor_out, true);
-
- if (Len(pkg_path) > 0 && Strchr(descriptor_out, '.') == NULL) {
- Replaceall(descriptor_out, "$packagepath", pkg_path);
- } else {
- Replaceall(descriptor_out, "$packagepath/", empty_string);
- Replaceall(descriptor_out, "$packagepath", empty_string);
- }
-
- Replaceall(descriptor_out, ".", "/");
-
- if (pkg_path != package_path)
- Delete(pkg_path);
+ // This returns a JNI descriptor, in path format
+ substituteClassnameAndPackagePath(type, descriptor_out, true, p);
return descriptor_out;
}
@@ -3928,17 +3978,33 @@ class JAVA:public Language {
// Get any Java exception classes in the throws typemap
ParmList *throw_parm_list = NULL;
+ // May need to add throws to director classes if %catches defined
+ // Get any Java exception classes in the throws typemap
+ ParmList *catches_list = Getattr(n, "catchlist");
+ if (catches_list) {
+ Swig_typemap_attach_parms("throws", catches_list, 0);
+ Swig_typemap_attach_parms("directorthrows", catches_list, 0);
+ for (p = catches_list; p; p = nextSibling(p)) {
+ addThrows(n, "tmap:throws", p);
+ }
+ }
+
if ((throw_parm_list = Getattr(n, "throws")) || Getattr(n, "throw")) {
int gencomma = 0;
Append(w->def, " throw(");
Append(declaration, " throw(");
- if (throw_parm_list)
+ if (throw_parm_list) {
Swig_typemap_attach_parms("throws", throw_parm_list, 0);
+ Swig_typemap_attach_parms("directorthrows", throw_parm_list, 0);
+ }
for (p = throw_parm_list; p; p = nextSibling(p)) {
if (Getattr(p, "tmap:throws")) {
- addThrows(n, "tmap:throws", p);
+ // If %catches feature, it overrides specified throws().
+ if (! catches_list) {
+ addThrows(n, "tmap:throws", p);
+ }
if (gencomma++) {
Append(w->def, ", ");
@@ -4001,7 +4067,8 @@ class JAVA:public Language {
Printf(w->code, "jenv->%s(Swig::jclass_%s, Swig::director_methids[%s], %s);\n", methop, imclass_name, methid, jupcall_args);
- Printf(w->code, "if (jenv->ExceptionCheck() == JNI_TRUE) return $null;\n");
+ // Generate code to handle any java exception thrown by director delegation
+ directorExceptHandler(n, catches_list?catches_list:throw_parm_list, w, c_classname, name);
if (!is_void) {
String *jresult_str = NewString("jresult");
@@ -4042,7 +4109,8 @@ class JAVA:public Language {
/* Terminate wrapper code */
Printf(w->code, "} else {\n");
- Printf(w->code, "SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, \"null upcall object\");\n");
+ Printf(w->code, "SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, \"null upcall object in %s::%s \");\n",
+ SwigType_namestr(c_classname), SwigType_namestr(name));
Printf(w->code, "}\n");
Printf(w->code, "if (swigjobj) jenv->DeleteLocalRef(swigjobj);\n");
@@ -4099,6 +4167,79 @@ class JAVA:public Language {
}
/* ------------------------------------------------------------
+ * directorExcept handler: Emit code to map java exceptions
+ * back to C++ exceptions when feature("director:except") is appied
+ * to a method node
+ * ------------------------------------------------------------ */
+ void directorExceptHandler(Node *n, ParmList *throw_parm_list, Wrapper *w, String *c_classname, String *name) {
+ // After director java method call, allow code for director method exception to be added
+ // Look for director:exceptfeature
+ Parm *p;
+
+ // "Default" feature, so that feature is not applied to every node, per W. Fulton
+ static char const * DEFAULT_DIREXCP_FEATURE =
+ "jthrowable $error = jenv->ExceptionOccurred();\n"
+ "if ($error) {\n"
+ " jenv->ExceptionClear();\n"
+ " $directorthrowshandlers\n"
+ " throw Swig::DirectorException(jenv, $error);\n"
+ "}\n";
+
+ String * featdirexcp = Getattr(n, "feature:director:except");
+
+ if (!featdirexcp) {
+ featdirexcp = NewString(DEFAULT_DIREXCP_FEATURE);
+ } else {
+ featdirexcp = Copy(featdirexcp);
+ }
+ // Can explicitly disable director:except by setting to "" or "0"
+ if (0 != Len(featdirexcp) && 0 != Cmp(featdirexcp,"0")) {
+
+ // Replace any $packagepath with global -package package_path
+ substitutePackagePath(featdirexcp, 0);
+
+ // Replace $action with any defined typemap handlers (or nothing)
+ if (Strstr(featdirexcp, "$directorthrowshandlers")) {
+ String *directorthrowshandlers_code = NewString("");
+
+ for (p = throw_parm_list; p; p = nextSibling(p)) {
+ String *tmapdirthrows = Getattr(p, "tmap:directorthrows");
+ String * excptype = Getattr(p,"type");
+
+ if (!tmapdirthrows) {
+ Swig_warning(WARN_TYPEMAP_DIRECTORTHROWS_UNDEF, input_file, line_number,
+ "Feature director:except on %s::%s with $directorthrowshandlers requires directorthrows typemap for exception %s.\n",
+ SwigType_namestr(c_classname),SwigType_namestr(name), excptype);
+ } else {
+ // replace $packagepath
+ tmapdirthrows = Copy(tmapdirthrows);
+ substituteClassnameAndPackagePath(excptype, tmapdirthrows, true, p);
+
+ Printf(directorthrowshandlers_code,
+ "// Handle exception %s using directorthrows typemap\n"
+ "%s",
+ excptype, tmapdirthrows);
+ Delete(tmapdirthrows);
+ }
+ }
+ // Delete extra new line if no handlers.
+ while (Replaceall(featdirexcp, "$directorthrowshandlers ",
+ "$directorthrowshandlers")) {}
+ if (0 == Len(directorthrowshandlers_code))
+ Replaceall(featdirexcp, "$directorthrowshandlers\n", "");
+ else
+ Replaceall(featdirexcp, "$directorthrowshandlers", directorthrowshandlers_code);
+ Delete(directorthrowshandlers_code);
+ }
+
+ // Replace all occurrences of $error with common var name.
+ Replaceall(featdirexcp, "$error", "thrown_");
+ Printf(w->code, " %s\n", featdirexcp);
+ }
+ Delete(featdirexcp);
+ }
+
+ /* ------------------------------------------------------------
* directorPrefixArgs()
* ------------------------------------------------------------ */
Please sign in to comment.
Something went wrong with that request. Please try again.