diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 40f383f..20c1ab6 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -11,7 +11,7 @@ export JavaObject, JavaMetaClass, JNIVector, jint, jlong, jbyte, jboolean, jchar, jshort, jfloat, jdouble, jvoid, JObject, JClass, JMethod, JConstructor, JField, JString, JavaRef, JavaLocalRef, JavaGlobalRef, JavaNullRef, - @jimport, jcall, jfield, jlocalframe, isnull, + @jimport, @jcall, jcall, jfield, jlocalframe, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, listfields, gettype, narrow @@ -40,6 +40,7 @@ include("core.jl") include("convert.jl") include("reflect.jl") include("jniarray.jl") +include("jcall_macro.jl") Base.@deprecate_binding jnifunc JavaCall.JNI.jniref[] diff --git a/src/core.jl b/src/core.jl index d04121a..9e51814 100644 --- a/src/core.jl +++ b/src/core.jl @@ -428,6 +428,8 @@ function jfield(ref, field::AbstractString) _jfield(_jcallable(ref), jfieldID, fieldType) end +jfield(ref, field::Symbol) = jfield(ref, String(field)) + function get_field_id(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) where T @checknull JNI.GetStaticFieldID(Ptr(metaclass(T)), String(field), signature(fieldType)) end diff --git a/src/jcall_macro.jl b/src/jcall_macro.jl new file mode 100644 index 0000000..b5e4eff --- /dev/null +++ b/src/jcall_macro.jl @@ -0,0 +1,90 @@ +macro jcall(expr) + return jcall_macro_lower(jcall_macro_parse(expr)...) +end + +function jcall_macro_lower(func, rettype, types, args) + @debug "args: " func rettype types args + jtypes = Expr(:tuple, esc.(types)...) + jargs = Expr(:tuple, esc.(args)...) + jret = esc(rettype) + if func isa Expr + @debug "func" func.head func.args + obj = resolve_dots(func.args[2]) + f = string(func.args[1].value) + return :(jcall($(esc(obj)), $f, $jret, $jtypes, ($jargs)...)) + elseif func isa QuoteNode + return :($(esc(func.value))($jtypes, ($jargs)...)) + end +end + +function resolve_dots(obj) + if obj isa Expr && obj.head == :. + return :(jfield($(resolve_dots(obj.args[1])), string($(obj.args[2])))) + else + return obj + end +end + +# @jcall implementation, based on Base.@ccall +""" + jcall_macro_parse(expression) + +`jcall_macro_parse` is an implementation detail of `@jcall +it takes an expression like `:(System.out.println("Hello"::JString)::Nothing)` +returns: a tuple of `(function_name, return_type, arg_types, args)` +The above input outputs this: + (:(System.out.println), Nothing, [:JString], ["Hello]) +""" +function jcall_macro_parse(expr::Expr) + # setup and check for errors + if !Meta.isexpr(expr, :(::)) + throw(ArgumentError("@jcall needs a function signature with a return type")) + end + rettype = expr.args[2] + + call = expr.args[1] + if !Meta.isexpr(call, :call) + throw(ArgumentError("@jcall has to take a function call")) + end + + # get the function symbols + func = let f = call.args[1] + if Meta.isexpr(f, :.) + :(($(f.args[2]), $(f.args[1]))) + elseif Meta.isexpr(f, :$) + f + elseif f isa Symbol + QuoteNode(f) + else + throw(ArgumentError("@jcall function name must be a symbol or a `.` node (e.g. `System.out.println`)")) + end + end + + # detect varargs + varargs = nothing + argstart = 2 + callargs = call.args + if length(callargs) >= 2 && Meta.isexpr(callargs[2], :parameters) + argstart = 3 + varargs = callargs[2].args + end + + # collect args and types + args = [] + types = [] + + function pusharg!(arg) + if !Meta.isexpr(arg, :(::)) + throw(ArgumentError("args in @jcall need type annotations. '$arg' doesn't have one.")) + end + push!(args, arg.args[1]) + push!(types, arg.args[2]) + end + + for i in argstart:length(callargs) + pusharg!(callargs[i]) + end + + return func, rettype, types, args +end + diff --git a/test/jcall_macro.jl b/test/jcall_macro.jl new file mode 100644 index 0000000..44c3912 --- /dev/null +++ b/test/jcall_macro.jl @@ -0,0 +1,55 @@ +using Test +using JavaCall + +@testset "jcall macro" begin + JavaCall.isloaded() || JavaCall.init(["-Djava.class.path=$(@__DIR__)"]) + System = @jimport java.lang.System + version_from_macro = @jcall System.getProperty("java.version"::JString)::JString + version_from_func = jcall(System, "getProperty", JString, (JString,), "java.version") + @test version_from_macro == version_from_func + @test "bar" == @jcall System.getProperty("foo"::JString, "bar"::JString)::JString + @test 0x00 == @jcall System.out.checkError()::jboolean + rettype = jboolean + @test 0x00 == @jcall System.out.checkError()::rettype + jstr = JString + @test version_from_func == @jcall System.getProperty("java.version"::jstr)::jstr + + T = @jimport Test + @test 10 == @jcall T.testShort(10::jshort)::jshort + @test 10 == @jcall T.testInt(10::jint)::jint + @test 10 == @jcall T.testLong(10::jlong)::jlong + @test typemax(jint) == @jcall T.testInt(typemax(jint)::jint)::jint + @test typemax(jlong) == @jcall T.testLong(typemax(jlong)::jlong)::jlong + @test "Hello Java" == @jcall T.testString("Hello Java"::JString)::JString + @test Float64(10.02) == @jcall T.testDouble(10.02::jdouble)::jdouble + @test Float32(10.02) == @jcall T.testFloat(10.02::jfloat)::jfloat + @test floatmax(jdouble) == @jcall T.testDouble(floatmax(jdouble)::jdouble)::jdouble + @test floatmax(jfloat) == @jcall T.testFloat(floatmax(jfloat)::jfloat)::jfloat + c=JString(C_NULL) + @test isnull(c) + @test "" == @jcall T.testString(c::JString)::JString + a = rand(10^7) + @test [@jcall(T.testDoubleArray(a::Array{jdouble,1})::jdouble) + for i in 1:10][1] ≈ sum(a) + a = nothing + + jlm = @jimport "java.lang.Math" + @test 1.0 ≈ @jcall jlm.sin((pi/2)::jdouble)::jdouble + @test 1.0 ≈ @jcall jlm.min(1::jdouble, 2::jdouble)::jdouble + @test 1 == @jcall jlm.abs((-1)::jint)::jint + + @testset "jcall macro instance_methods_1" begin + jnu = @jimport java.net.URL + gurl = @jcall jnu("https://en.wikipedia.org"::JString)::jnu + @test "en.wikipedia.org"== @jcall gurl.getHost()::JString + jni = @jimport java.net.URI + guri = @jcall gurl.toURI()::jni + @test typeof(guri)==jni + + h=@jcall guri.hashCode()::jint + @test typeof(h)==jint + end + + jlist = @jimport java.util.ArrayList + @test 0x01 == @jcall jlist().add(JObject(C_NULL)::JObject)::jboolean +end diff --git a/test/runtests.jl b/test/runtests.jl index 3e67968..2234d60 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -438,6 +438,8 @@ end end end +include("jcall_macro.jl") + end # Test downstream dependencies