Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate jcall to enable extension of JVM types #149

Merged
merged 14 commits into from
Mar 5, 2022

Conversation

ahnlabb
Copy link
Contributor

@ahnlabb ahnlabb commented Jun 21, 2021

The main purpose of this PR is to make it easier to extend JavaCall with types that e.g. need special cleanup, define a signature method, etc. (see #144).

Since the relevant logic to handle the core call through JNI was spread out, discrepancies had crept into the codebase:

isnull(obj) && error("Attempt to call method on Java NULL")

isnull(obj) && throw(JavaCallError("Attempt to call method on Java NULL"))

So:

julia> jcall(JavaObject{Symbol("java.util.HashMap")}(C_NULL), "toString", JString, ())
ERROR: Attempt to call method on Java NULL
Stacktrace:
...

julia> jcall(JavaObject{Symbol("java.util.HashMap")}(C_NULL), "isEmpty", jboolean, ())
ERROR: JavaCall.JavaCallError("Attempt to call method on Java NULL")
Stacktrace:
...

I believe this discrepancy arose because the central logic for setting up and "tearing down" a call (including error checking) is fragmented. For this reason I don't feel comfortable adding the functionality in #144 before this logic has been consolidated e.g. through this PR.

My goal in this PR is therefore to more or less only have one place in core.jl where we:

  • Convert arguments
  • Use GC.@preserve
  • Tear down temporary objects
  • Check errors
  • Convert result

for a Java method call.

@ahnlabb ahnlabb mentioned this pull request Jun 21, 2021
end

function jfield(obj::JavaObject, field::JField)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we drop the two argument version of jfield?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I'm expanding the test suite to test for this as well as some other things.

@ahnlabb ahnlabb force-pushed the consolidate-jcall branch from bd04093 to 3a8248e Compare July 20, 2021 13:39
@mkitti
Copy link
Member

mkitti commented Jul 20, 2021

Tests seems to be failing with the latest push

@ahnlabb
Copy link
Contributor Author

ahnlabb commented Jul 20, 2021

The expanded test suite from 7cc0fbf now captures the loss of the two-argument version of jfield. While working on the tests I noticed that exceptions are not thrown in julia unless a null pointer is returned, this is captured in the exceptions_1 test suite. Can you confirm that this is a bug @mkitti? If the errors are not checked after the initial call it they can surface at a later call which is not actually the call that threw the exception, you can reproduce it by the following (using the Test class from the test directory):

julia> JTest = @jimport(Test)
JavaObject{:Test}

julia> t=JTest(())
JavaObject{:Test}(JavaLocalRef(Ptr{Nothing} @0x000000000210a898))

julia> jcall((@jimport java.lang.Math), "floorDiv", jint, (jint, jint), 1, 0)
0

julia> jfield(t, "objectField", JObject)
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at java.lang.Math.floorDiv(Math.java:1052)
ERROR: JavaCall.JavaCallError("Error calling Java: java.lang.ArithmeticException: / by zero")
Stacktrace:
 [1] geterror(allow::Bool)
   @ JavaCall ~/projects/JavaCall.jl/src/core.jl:537
 [2] geterror
   @ ~/projects/JavaCall.jl/src/core.jl:522 [inlined]
 [3] _jfield(obj::JavaObject{:Test}, jfieldID::Ptr{Nothing}, fieldType::Type)
   @ JavaCall ~/projects/JavaCall.jl/src/core.jl:435
 [4] jfield(obj::JavaObject{:Test}, field::String, fieldType::Type)
   @ JavaCall ~/projects/JavaCall.jl/src/core.jl:398
 [5] top-level scope
   @ REPL[12]:1

Expanded test suite:

7cc0fbf (master with new test suite):

JavaCall                     |  212     4      4    220
  unsafe_strings_1           |    3                   3
  parameter_passing_1        |   13                  13
  static_method_call_1       |    3                   3
  static_method_call_async_1 |                    No tests
  instance_methods_1         |    3                   3
  exceptions_1               |          3      3      6
  fields_1                   |   20            1     21
    UInt8                    |    3                   3
    Int32                    |    3                   3
    JString                  |    3                   3
    JObject                  |    3                   3
  null_1                     |    9     1            10
  arrays_1                   |   14                  14
  dates_1                    |    6                   6
  map_conversion_1           |    1                   1
  array_list_conversion_1    |    1                   1
  inner_classes_1            |    4                   4
  sinx_1                     |    2                   2
  method_lists_1             |    9                   9
  double_free_1              |  100                 100
  array_conversions_1        |    1                   1
  iterator_conversions_1     |   11                  11
  roottask_and_env_1         |    4                   4
  jlocalframe                |    8                   8

In addition to the issue with exceptions mentioned above the new tests also caught a typo on master ("Bype"):

(:jbyte, :(JNI.GetByteField), :(JNI.GetStaticBypeField)) ,

I'll fix it in this PR. EDIT: Or rather it is already fixed because the consolidation of the logic avoids the repetition that caused this typo.

3a8248e (this PR)

Test Summary:                | Pass  Fail  Error  Total
JavaCall                     |  206     3     11    220
  unsafe_strings_1           |    3                   3
  parameter_passing_1        |   13                  13
  static_method_call_1       |    3                   3
  static_method_call_async_1 |                    No tests
  instance_methods_1         |    3                   3
  exceptions_1               |          3      3      6
  fields_1                   |   13            8     21
    UInt8                    |    1            2      3
    Int32                    |    1            2      3
    JString                  |    1            2      3
    JObject                  |    1            2      3
  null_1                     |   10                  10
  arrays_1                   |   14                  14
  dates_1                    |    6                   6
  map_conversion_1           |    1                   1
  array_list_conversion_1    |    1                   1
  inner_classes_1            |    4                   4
  sinx_1                     |    2                   2
  method_lists_1             |    9                   9
  double_free_1              |  100                 100
  array_conversions_1        |    1                   1
  iterator_conversions_1     |   11                  11
  roottask_and_env_1         |    4                   4
  jlocalframe                |    8                   8

@mkitti
Copy link
Member

mkitti commented Jul 20, 2021

I'm not able to investigate myself at the moment, but that certainly looks like a bug to me.

@ahnlabb ahnlabb force-pushed the consolidate-jcall branch from 3a8248e to 0e4a3ac Compare July 24, 2021 09:31
@mkitti
Copy link
Member

mkitti commented Jul 25, 2021

isnothing is not implemented in Julia 1.0. Just use === nothing instead.

@ahnlabb ahnlabb force-pushed the consolidate-jcall branch from 0e4a3ac to ef31d84 Compare July 26, 2021 08:38
@ahnlabb
Copy link
Contributor Author

ahnlabb commented Jul 26, 2021

Thank you @mkitti. It looks like the checks are passing now. It is important to note that this PR now includes some important changes to errors. As noted above:

While working on the tests I noticed that exceptions are not thrown in julia unless a null pointer is returned

Not only were the exceptions not thrown but they were not cleared meaning that a latter call returning a valid null-pointer would throw. When fixing this issue I noticed another related one. Null checks that did not properly use the exception checking mechanism of the JNI assumed a specific reason for the returned null even when several possibilities existed. The chosen reason was probably the most common one but this handling could sometimes obscure valuable information about the real error. This could happen when trying to get a constructor or find a class:

JNI.GetMethodID will according to the spec return a null pointer in the following situations:

NoSuchMethodError: if the specified method cannot be found.

ExceptionInInitializerError: if the class initializer fails due to an exception.

OutOfMemoryError: if the system runs out of memory.

However, on the current master, we throw: JavaCallError("No constructor for $T with signature $sig") when using JNI.GetMethodID to get the constructor for all situations without checking, clearing, or reporting the error.

Similarly:

JNI.FindClass will return a null pointer in these situations:

ClassFormatError: if the class data does not specify a valid class.

ClassCircularityError: if a class or interface would be its own superclass or superinterface.

NoClassDefFoundError: if no definition for a requested class or interface can be found.

OutOfMemoryError: if the system runs out of memory.

And we throw: JavaCallError("Class Not Found $jclass").

In this PR I instead handle all possible exceptions using geterror which provides the exception as reported by the JVM and clears the exception. For example, if we create a corrupted class file TestBad.class (e.g. by deleting som parts of Test.class:

master:

julia> JavaCall.jnew(Symbol("TestBad"))
ERROR: JavaCall.JavaCallError("Class Not Found TestBad")
Stacktrace:
 [1] _metaclass(class::Symbol)
   @ JavaCall JavaCall.jl/src/core.jl:501
[...]

julia> JavaCall.jnew(Symbol("TestNonexistentClassfile"))
ERROR: JavaCall.JavaCallError("Class Not Found TestNonexistentClassfile")
Stacktrace:
 [1] _metaclass(class::Symbol)
   @ JavaCall JavaCall.jl/src/core.jl:501
[...]

ahnlabb:consolidate-jcall:

julia> JavaCall.jnew(Symbol("TestBad"))
Exception in thread "main" java.lang.ClassFormatError: Unknown constant tag 68 in class file TestBad
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
ERROR: JavaCall.JavaCallError("Error calling Java: java.lang.ClassFormatError: Unknown constant tag 68 in class file TestBad")
Stacktrace:
 [1] geterror()
   @ JavaCall JavaCall.jl/src/core.jl:487
[...]

julia> JavaCall.jnew(Symbol("TestNonexistentClassfile"))
Exception in thread "main" java.lang.NoClassDefFoundError: TestNonexistentClassfile
Caused by: java.lang.ClassNotFoundException: TestNonexistentClassfile
        at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
ERROR: JavaCall.JavaCallError("Error calling Java: java.lang.NoClassDefFoundError: TestNonexistentClassfile")
Stacktrace:
 [1] geterror()
   @ JavaCall JavaCall.jl/src/core.jl:487
[...]

In summary. In addition to the previously discussed changes this PR does two new things:

  1. Fixes a bug where sometimes an exception was not thrown and/or not cleared.
  2. Removes to special cases of error handling in favor of the general and more correct error handling of geterror

Change 1 should be uncontroversial. While change 2 results in the correct error being shown it can in some cases result in a less "user-friendly" error. I think a lot could be done to make the errors more user-friendly and easier to interact with. A possible way forward would be to merge this PR and then open a new PR or issue where this could be discussed further, what do you think @mkitti?

Copy link
Member

@mkitti mkitti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have gone through this with a finer comb.

  1. Could you justify dropping the type annotations? I tend to be more specific about them in core packages such as this.
  2. checknull definitely seems to help simplify the code. It might be useful to have a second argument to add a note to the error if it is null. Perhaps this should be a macro so that we could elaborate on which JNI call created the error. Otherwise, as a function, consider adding an @inline annotation.

ptr
end

function geterror()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need not the allow argument anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The allow argument is no longer needed. We have moved the decision whether to throw when no error is present to the caller. Basically, where we had geterror(true) we now have checknull().

# Call static methods
function jcall(typ::Type{JavaObject{T}}, method::AbstractString, rettype::Type, argtypes::Tuple = (),
args... ) where T
function jcall(ref, method::AbstractString, rettype::Type, argtypes::Tuple = (), args...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ref be Any like this or perhaps should it be a Union{Type{JavaObject}, JavaObject}? Do we need an AbstractJavaObject?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer an AbstractJavaObject to a union. See the discussion in my general reply.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With AbstractJavaObject it might be Union{Type{JavaObject}, AbstractJavaObject}

# JMethod invoke
(m::JMethod)(obj, args...) = jcall(obj, m, args...)


function jfield(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) where T
function jfield(ref, field, fieldType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm again wondering about dropping the type annotations. Part of it is the lack of documentation here. Perhaps we should improve the documentation while we are making these changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree with the need for documentation. AbstractJavaObject is a possibility here as well.

GC.@preserve savedArgs begin
result = callmethod(Ptr(obj), jmethodId, Array{JNI.jvalue}(jvalue.(convertedArgs)))
function _jcall(obj::T, jmethodId::Ptr{Nothing}, rettype::$x,
argtypes::Tuple, args...; callmethod=$callmethod) where T <: $t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need callmethod as an argument here? If so, do we still need to validate it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to check for C_NULL as was done previously, C_NULL was used as a special value to be able to set a default, much like a keyword argument or default argument.

The callmethod kwarg is only used by jnew. I think this is ok, alternatively, we could break it up into two functions (manually generate functions with defaults like what julia generates for us now with kwargs). This could be more performant. There is if I recall correctly a slight performance penalty to functions with keyword arguments.


global const _jmc_cache = [ Dict{Symbol, JavaMetaClass}() ]

function _metaclass(class::Symbol)
jclass=javaclassname(class)
jclassptr = JNI.FindClass(jclass)
jclassptr == C_NULL && throw(JavaCallError("Class Not Found $jclass"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're losing the the specificity of the error message here. We should add a second argument to checknull for when we can provide a more descriptive error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree.

It is important to note that, as noted above, the error message can be wrong (or at the very least misleading). If we decide that the JavaCall.jl context of the call is more important than the JVM context then we can keep the error string with your checknull macro.

src/core.jl Outdated
Comment on lines 331 to 335
_jcallable(typ::Type{JavaObject{T}}) where T = metaclass(T)
function _jcallable(obj::JavaObject)
isnull(obj) && throw(JavaCallError("Attempt to call method on Java NULL"))
obj
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two forms of this function are quite distinct. The second does some validation. Documenting this would be useful.

For the second form, perhaps the two arg checknull would be useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should skip the null-check? I, agree with you on documentation.

When I kept that null-check from before it felt similar to the JVM behavior: A static method is always callable, an instance method is only callable if the instance is non-null. However, that's not really what's going on here. If we just skip the check we get:

Exception in thread "main" java.lang.NullPointerException
ERROR: JavaCall.JavaCallError("Error calling Java: java.lang.NullPointerException")
Stacktrace:
 [1] geterror()
   @ JavaCall JavaCall.jl/src/core.jl:487
 [2] _jcall(::JavaObject [...]

Which could be just as informative.

src/core.jl Outdated
# Call instance methods
function jcall(obj::JavaObject, method::AbstractString, rettype::Type, argtypes::Tuple = (), args... )
assertroottask_or_goodenv() && assertloaded()
function get_method_id(jnifun, ptr, method::AbstractString, rettype::Type, argtypes::Tuple)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know that jnifun is a Function and that ptr must be a Ptr? Is there a reason to keep this generic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is an empty tuple still a reasonable default for argtypes?

src/core.jl Outdated
Comment on lines 403 to 412
for (x, name) in [(:Type, "Object"),
(:(Type{jboolean}), "Boolean"),
(:(Type{jchar}), "Char" ),
(:(Type{jbyte}), "Byte" ),
(:(Type{jshort}), "Short" ),
(:(Type{jint}), "Int" ),
(:(Type{jlong}), "Long" ),
(:(Type{jfloat}), "Float" ),
(:(Type{jdouble}), "Double" ),
(:(Type{jvoid}), "Void" )]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify the first component to as before. For just plain type, use :(<:Any). Later you can use rettype::Type{$(x)} as before.

Make the second component, name a Symbol. :Object rather than "Object".

src/core.jl Outdated Show resolved Hide resolved
Comment on lines +426 to +429
GC.@preserve savedArgs begin
result = callmethod(Ptr(obj), jmethodId, Array{JNI.jvalue}(jvalue.(convertedArgs)))
end
cleanup_arg.(convertedArgs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A preexisting question is if we should just GC.@preserve the convertedArgs and let the Julia GC and JavaObject finalizer deal with the cleanup. The original issue I suppose is that there currently is no way for the Julia and Java garbage collectors to communicate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave this alone for now. I'll revisit it later or in another PR.

@mkitti
Copy link
Member

mkitti commented Jul 27, 2021

I think a lot could be done to make the errors more user-friendly and easier to interact with. A possible way forward would be to merge this PR and then open a new PR or issue where this could be discussed further.

Let's build some infrastructure here for this around the checknull function. An optional second argument would help us annotate the reason for the null. An additional @checknull macro might help us automatically capture the calling function, which we could then relay to checknull as an optional third argument.

Copy link
Member

@mkitti mkitti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's my suggestion for a @checknull macro and an expanded checknull function.

Here's a demonstration:

julia> @checknull JNI.NewStringUTF(Ptr{UInt8}(C_NULL)) "Issue creating a Java String"
ERROR: JavaCallError("JavaCall.JNI.NewStringUTF: Issue creating a Java String")

Later we can write specialized checknull functions:

function checknull(x, msg, y::typeof(JNI.GetMethodID))
    ...
end

src/core.jl Outdated Show resolved Hide resolved
@ahnlabb
Copy link
Contributor Author

ahnlabb commented Jul 27, 2021

Thank you @mkitti for the in-depth review. I may not have time to got through everything today but for the general comments:

Could you justify dropping the type annotations? I tend to be more specific about them in core packages such as this.

We can definitely put in some type annotations. I don't do it by default and prefer to be explicit about their purpose. For example:

function jcall(ref, method::AbstractString, rettype::Type, argtypes::Tuple = (), args...)
    assertroottask_or_goodenv() && assertloaded()
    jmethodId = checknull(get_method_id(ref, method, rettype, argtypes))
    _jcall(_jcallable(ref), jmethodId, rettype, argtypes, args...)
end

From a library maintainer/readability perspective, to me, this defines a very clear contract and I would actually prefer removing the AbstractString annotation. Calling jcall requires:

  • The JVM to be initialized
  • ref and method to be types that have a JavaCall.get_method_id method.
  • ref to be a type with a _jcallable method

If I want to make a new type (like native arrays) with a jcall method all I need is to implement _jcallable and get_method_id.
We quickly dispatch to more specialized functions, there is probably no performance benefit to annotating.
The more specialized methods yield errors that indicate that the contract has been broken.
For a core library like this I think it's important to enable other libraries to extend in ways we may not expect:

julia> jcall("abc.abc", "toUpperCase", JString)
ERROR: MethodError: no method matching get_method_id(::String, ::String, ::Type{JString}, ::Tuple{})
Closest candidates are:
  get_method_id(::Type{JavaObject{T}}, ::AbstractString, ::Type, ::Tuple) where T at /home/ja/projects/JavaCall.jl/src/core.jl:356
  get_method_id(::JavaObject, ::AbstractString, ::Type, ::Tuple) at /home/ja/projects/JavaCall.jl/src/core.jl:360
  get_method_id(::Any, ::Any, ::AbstractString, ::Type, ::Tuple) at /home/ja/projects/JavaCall.jl/src/core.jl:351
Stacktrace:
[...]

julia> JavaCall.get_method_id(str::String, args...) = JavaCall.get_method_id(JavaCall.JNI.GetMethodID, Ptr(JavaCall.metaclass(JString)), args...)

julia> JavaCall._jcallable(str::String) = JString(str)

julia> jcall("abc.abc", "toUpperCase", JString)
"ABC.ABC"

The disadvantage, as you indicate, is that we may lose some documentation from a library user's perspective. For example typing jcall(<TAB> in the REPL yields very limited information. I agree that expanding documentation is probably necessary and some functions (like get_method_id(jnifun, ptr, method::AbstractString, rettype::Type, argtypes::Tuple)) may benefit from annotation.

checknull definitely seems to help simplify the code

Here's my suggestion for a @checknull macro

Looks great to me, I considered creating a similar macro but was also thinking that we perhaps could exhaustively enumerate the JNI spec exceptions and provide Julia types for them so that users (perhaps mostly library authors) could match on them. Alternatively, we could figure out a generic way to present java exceptions, perhaps making a symbol from the Throwable class. Perhaps that wouldn't be very useful or too much work but giving this some thought so we don't lock into a rigid approach could be good. When handling an error inside a library do I care more that it happened during a specific part of the call to the constructor or that it was an OOM exception? Can we allow handling of the exception in a simple way using either part of this information? I think we can probably do better than just a JavaCallError wrapping a string but perhaps you're right in suggesting we start with such a solution in this PR.

@mkitti
Copy link
Member

mkitti commented Aug 4, 2021

About the error part, we probably should make a JavaError or JNIError that wraps a JavaObject which represents the underlying error. That would provide a mechanism to detect the error.

How are we doing on this pull request otherwise?

@mkitti
Copy link
Member

mkitti commented Aug 19, 2021

@ahnlabb would you mind I contributed some commits to this branch?

@ahnlabb
Copy link
Contributor Author

ahnlabb commented Aug 19, 2021

Hi @mkitti, I'm sorry that I've been unresponsive. I just went on vacation and needed to finalize some other things.

I understand that a lot is riding on this PR now and as I unfortunately won't have much time in front of a computer the coming two weeks I welcome contributions from your side.

I'll still be available for discussion!

@ahnlabb
Copy link
Contributor Author

ahnlabb commented Oct 13, 2021

@mkitti did you continue work here (anything not pushed to the branch)? Could I devote some time to this PR the coming weeks?

@mkitti
Copy link
Member

mkitti commented Oct 13, 2021

I have not made progress. Go ahead.

src/core.jl Outdated Show resolved Hide resolved
@mkitti
Copy link
Member

mkitti commented Mar 3, 2022

This is about where I want it now. It could use some more documentation.

@aviks , any thoughts about merging this?

@ahnlabb
Copy link
Contributor Author

ahnlabb commented Mar 3, 2022

@mkitti I've added the previously discussed simplification of the types as well as a small refactor that would make it easier to add something like this to geterror in the future:

function get_exception_object(jthrow)
    jclass = JNI.FindClass("java/lang/Class")
    _notnull_assert(jclass)

    thrown_class = JNI.GetObjectClass(jthrow)
    _notnull_assert(thrown_class)

    getname_method = JNI.GetMethodID(jclass, "getName", "()Ljava/lang/String;")
    _notnull_assert(getname_method)

    res = JNI.CallObjectMethodA(thrown_class, getname_method, Int[])
    _notnull_assert(res)

    return JavaObject{Symbol(unsafe_string(JString(res)))}(jthrow)
end

so that we can, as discussed, throw the exception as an object instead of a string. This would probably require ways to pick the desired behavior. I have thoughts but it would be a different PR.

@mkitti mkitti merged commit 2da3217 into JuliaInterop:master Mar 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants