Add feature director:except for improved director exception handling in Java

Closes #91
This commit is contained in:
Marvin Greenberg 2013-10-22 20:31:14 +01:00 • committed by William S Fulton
commit 6736e74127
12 changed files with 1469 additions and 51 deletions

View file

@ -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 @@ However, if all director methods are expected to usually be overridden by Java s
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 file

@ -499,6 +499,7 @@ example.i(4) : Syntax error in input.
<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 file

@ -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 file

@ -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 file

@ -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 file

@ -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 file

@ -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 file

@ -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 file

@ -193,6 +193,242 @@ namespace Swig {
};
}
// 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 file

@ -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 file

@ -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 file

@ -742,15 +742,25 @@ public:
*----------------------------------------------------------------------*/
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 @@ public:
* 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 @@ public:
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 @@ public:
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 @@ public:
/* -----------------------------------------------------------------------------
* 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 @@ public:
// 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 @@ public:
* --------------------------------------------------------------- */
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 @@ public:
// 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 @@ public:
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 @@ public:
/* 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");
@ -4098,6 +4166,79 @@ public:
return status;
}
/* ------------------------------------------------------------
* 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()
* ------------------------------------------------------------ */