Skip to content

Commit 2b8c3f1

Browse files
authored
Add prototype jcall macro (#165)
* Add prototype jcall macro * Improve @jcall macro * Remove nreq from macro, restore 1.0 compat
1 parent d7f3136 commit 2b8c3f1

File tree

5 files changed

+151
-1
lines changed

5 files changed

+151
-1
lines changed

src/JavaCall.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export JavaObject, JavaMetaClass, JNIVector,
1111
jint, jlong, jbyte, jboolean, jchar, jshort, jfloat, jdouble, jvoid,
1212
JObject, JClass, JMethod, JConstructor, JField, JString,
1313
JavaRef, JavaLocalRef, JavaGlobalRef, JavaNullRef,
14-
@jimport, jcall, jfield, jlocalframe, isnull,
14+
@jimport, @jcall, jcall, jfield, jlocalframe, isnull,
1515
getname, getclass, listmethods, getreturntype, getparametertypes, classforname,
1616
listfields, gettype,
1717
narrow
@@ -40,6 +40,7 @@ include("core.jl")
4040
include("convert.jl")
4141
include("reflect.jl")
4242
include("jniarray.jl")
43+
include("jcall_macro.jl")
4344

4445
Base.@deprecate_binding jnifunc JavaCall.JNI.jniref[]
4546

src/core.jl

+2
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ function jfield(ref, field::AbstractString)
428428
_jfield(_jcallable(ref), jfieldID, fieldType)
429429
end
430430

431+
jfield(ref, field::Symbol) = jfield(ref, String(field))
432+
431433
function get_field_id(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) where T
432434
@checknull JNI.GetStaticFieldID(Ptr(metaclass(T)), String(field), signature(fieldType))
433435
end

src/jcall_macro.jl

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
macro jcall(expr)
2+
return jcall_macro_lower(jcall_macro_parse(expr)...)
3+
end
4+
5+
function jcall_macro_lower(func, rettype, types, args)
6+
@debug "args: " func rettype types args
7+
jtypes = Expr(:tuple, esc.(types)...)
8+
jargs = Expr(:tuple, esc.(args)...)
9+
jret = esc(rettype)
10+
if func isa Expr
11+
@debug "func" func.head func.args
12+
obj = resolve_dots(func.args[2])
13+
f = string(func.args[1].value)
14+
return :(jcall($(esc(obj)), $f, $jret, $jtypes, ($jargs)...))
15+
elseif func isa QuoteNode
16+
return :($(esc(func.value))($jtypes, ($jargs)...))
17+
end
18+
end
19+
20+
function resolve_dots(obj)
21+
if obj isa Expr && obj.head == :.
22+
return :(jfield($(resolve_dots(obj.args[1])), string($(obj.args[2]))))
23+
else
24+
return obj
25+
end
26+
end
27+
28+
# @jcall implementation, based on Base.@ccall
29+
"""
30+
jcall_macro_parse(expression)
31+
32+
`jcall_macro_parse` is an implementation detail of `@jcall
33+
it takes an expression like `:(System.out.println("Hello"::JString)::Nothing)`
34+
returns: a tuple of `(function_name, return_type, arg_types, args)`
35+
The above input outputs this:
36+
(:(System.out.println), Nothing, [:JString], ["Hello])
37+
"""
38+
function jcall_macro_parse(expr::Expr)
39+
# setup and check for errors
40+
if !Meta.isexpr(expr, :(::))
41+
throw(ArgumentError("@jcall needs a function signature with a return type"))
42+
end
43+
rettype = expr.args[2]
44+
45+
call = expr.args[1]
46+
if !Meta.isexpr(call, :call)
47+
throw(ArgumentError("@jcall has to take a function call"))
48+
end
49+
50+
# get the function symbols
51+
func = let f = call.args[1]
52+
if Meta.isexpr(f, :.)
53+
:(($(f.args[2]), $(f.args[1])))
54+
elseif Meta.isexpr(f, :$)
55+
f
56+
elseif f isa Symbol
57+
QuoteNode(f)
58+
else
59+
throw(ArgumentError("@jcall function name must be a symbol or a `.` node (e.g. `System.out.println`)"))
60+
end
61+
end
62+
63+
# detect varargs
64+
varargs = nothing
65+
argstart = 2
66+
callargs = call.args
67+
if length(callargs) >= 2 && Meta.isexpr(callargs[2], :parameters)
68+
argstart = 3
69+
varargs = callargs[2].args
70+
end
71+
72+
# collect args and types
73+
args = []
74+
types = []
75+
76+
function pusharg!(arg)
77+
if !Meta.isexpr(arg, :(::))
78+
throw(ArgumentError("args in @jcall need type annotations. '$arg' doesn't have one."))
79+
end
80+
push!(args, arg.args[1])
81+
push!(types, arg.args[2])
82+
end
83+
84+
for i in argstart:length(callargs)
85+
pusharg!(callargs[i])
86+
end
87+
88+
return func, rettype, types, args
89+
end
90+

test/jcall_macro.jl

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Test
2+
using JavaCall
3+
4+
@testset "jcall macro" begin
5+
JavaCall.isloaded() || JavaCall.init(["-Djava.class.path=$(@__DIR__)"])
6+
System = @jimport java.lang.System
7+
version_from_macro = @jcall System.getProperty("java.version"::JString)::JString
8+
version_from_func = jcall(System, "getProperty", JString, (JString,), "java.version")
9+
@test version_from_macro == version_from_func
10+
@test "bar" == @jcall System.getProperty("foo"::JString, "bar"::JString)::JString
11+
@test 0x00 == @jcall System.out.checkError()::jboolean
12+
rettype = jboolean
13+
@test 0x00 == @jcall System.out.checkError()::rettype
14+
jstr = JString
15+
@test version_from_func == @jcall System.getProperty("java.version"::jstr)::jstr
16+
17+
T = @jimport Test
18+
@test 10 == @jcall T.testShort(10::jshort)::jshort
19+
@test 10 == @jcall T.testInt(10::jint)::jint
20+
@test 10 == @jcall T.testLong(10::jlong)::jlong
21+
@test typemax(jint) == @jcall T.testInt(typemax(jint)::jint)::jint
22+
@test typemax(jlong) == @jcall T.testLong(typemax(jlong)::jlong)::jlong
23+
@test "Hello Java" == @jcall T.testString("Hello Java"::JString)::JString
24+
@test Float64(10.02) == @jcall T.testDouble(10.02::jdouble)::jdouble
25+
@test Float32(10.02) == @jcall T.testFloat(10.02::jfloat)::jfloat
26+
@test floatmax(jdouble) == @jcall T.testDouble(floatmax(jdouble)::jdouble)::jdouble
27+
@test floatmax(jfloat) == @jcall T.testFloat(floatmax(jfloat)::jfloat)::jfloat
28+
c=JString(C_NULL)
29+
@test isnull(c)
30+
@test "" == @jcall T.testString(c::JString)::JString
31+
a = rand(10^7)
32+
@test [@jcall(T.testDoubleArray(a::Array{jdouble,1})::jdouble)
33+
for i in 1:10][1] sum(a)
34+
a = nothing
35+
36+
jlm = @jimport "java.lang.Math"
37+
@test 1.0 @jcall jlm.sin((pi/2)::jdouble)::jdouble
38+
@test 1.0 @jcall jlm.min(1::jdouble, 2::jdouble)::jdouble
39+
@test 1 == @jcall jlm.abs((-1)::jint)::jint
40+
41+
@testset "jcall macro instance_methods_1" begin
42+
jnu = @jimport java.net.URL
43+
gurl = @jcall jnu("https://en.wikipedia.org"::JString)::jnu
44+
@test "en.wikipedia.org"== @jcall gurl.getHost()::JString
45+
jni = @jimport java.net.URI
46+
guri = @jcall gurl.toURI()::jni
47+
@test typeof(guri)==jni
48+
49+
h=@jcall guri.hashCode()::jint
50+
@test typeof(h)==jint
51+
end
52+
53+
jlist = @jimport java.util.ArrayList
54+
@test 0x01 == @jcall jlist().add(JObject(C_NULL)::JObject)::jboolean
55+
end

test/runtests.jl

+2
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ end
438438
end
439439
end
440440

441+
include("jcall_macro.jl")
442+
441443
end
442444

443445
# Test downstream dependencies

0 commit comments

Comments
 (0)