From 28fa72c0a9a9382184c685f15cab6b931e376adc Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 8 Sep 2018 15:28:12 +0300 Subject: [PATCH 01/43] allow initializing from running VM to allow call-in/call-out created init_current_vm function --- src/jvm.jl | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/jvm.jl b/src/jvm.jl index 03ab95c..26f1621 100644 --- a/src/jvm.jl +++ b/src/jvm.jl @@ -185,6 +185,61 @@ function init(opts) return end +""" + init_current_vm() + +Allow initialization from running VM. Uses the first VM it finds. + +# Example using JNA + +```java +package zot.julia; + +import com.sun.jna.Native; + +public class Julia { + static { + Native.register("julia"); + jl_init__threading(); + } + + public static double bubba = Math.random(); + + public static native void jl_init__threading(); + public static native void jl_eval_string(String code); + public static native void jl_atexit_hook(int status); + + public static void main(String args[]) { + System.out.println("test"); + jl_eval_string("println(\"test from Julia\")"); + jl_eval_string("using JavaCall"); + jl_eval_string("JavaCall.init_current_vm()"); + jl_eval_string("println(\"initialized VM\")"); + jl_eval_string("jlm = @jimport java.lang.Math"); + jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))"); + jl_eval_string("jl = @jimport zot.julia.Julia"); + System.out.println("Bubba should be " + bubba); + jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))"); + jl_eval_string("println(\"Done with tests\")"); + jl_atexit_hook(0); + } +} +``` +""" +function init_current_vm() + ppjvm = Array{Ptr{JavaVM}}(undef, 1) + ppenv = Array{Ptr{JNIEnv}}(undef, 1) + pnum = Array{Cint}(undef, 1) + ccall(Libdl.dlsym(libjvm, :JNI_GetCreatedJavaVMs), Cint, (Ptr{Ptr{JavaVM}}, Cint, Ptr{Cint}), ppjvm, 1, pnum) + global pjvm = ppjvm[1] + jvm = unsafe_load(pjvm) + global jvmfunc = unsafe_load(jvm.JNIInvokeInterface_) + ccall(jvmfunc.GetEnv, Cint, (Ptr{Nothing}, Ptr{Ptr{JNIEnv}}, Cint), pjvm, ppenv, JNI_VERSION_1_8) + global penv = ppenv[1] + jnienv = unsafe_load(penv) + global jnifunc = unsafe_load(jnienv.JNINativeInterface_) #The JNI Function table +end + function destroy() if (!isdefined(JavaCall, :penv) || penv == C_NULL) throw(JavaCallError("Called destroy without initialising Java VM")) From d5cab177da169ec3b23545f51b1226d515ea8e4b Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 8 Sep 2018 15:34:19 +0300 Subject: [PATCH 02/43] untabified example code --- src/jvm.jl | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/jvm.jl b/src/jvm.jl index 26f1621..73fecf6 100644 --- a/src/jvm.jl +++ b/src/jvm.jl @@ -198,31 +198,31 @@ package zot.julia; import com.sun.jna.Native; public class Julia { - static { - Native.register("julia"); - jl_init__threading(); - } - - public static double bubba = Math.random(); - - public static native void jl_init__threading(); - public static native void jl_eval_string(String code); - public static native void jl_atexit_hook(int status); - - public static void main(String args[]) { - System.out.println("test"); - jl_eval_string("println(\"test from Julia\")"); - jl_eval_string("using JavaCall"); - jl_eval_string("JavaCall.init_current_vm()"); - jl_eval_string("println(\"initialized VM\")"); - jl_eval_string("jlm = @jimport java.lang.Math"); - jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))"); - jl_eval_string("jl = @jimport zot.julia.Julia"); - System.out.println("Bubba should be " + bubba); - jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))"); - jl_eval_string("println(\"Done with tests\")"); - jl_atexit_hook(0); - } + static { + Native.register("julia"); + jl_init__threading(); + } + + public static double bubba = Math.random(); + + public static native void jl_init__threading(); + public static native void jl_eval_string(String code); + public static native void jl_atexit_hook(int status); + + public static void main(String args[]) { + System.out.println("test"); + jl_eval_string("println(\"test from Julia\")"); + jl_eval_string("using JavaCall"); + jl_eval_string("JavaCall.init_current_vm()"); + jl_eval_string("println(\"initialized VM\")"); + jl_eval_string("jlm = @jimport java.lang.Math"); + jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))"); + jl_eval_string("jl = @jimport zot.julia.Julia"); + System.out.println("Bubba should be " + bubba); + jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))"); + jl_eval_string("println(\"Done with tests\")"); + jl_atexit_hook(0); + } } ``` """ From 1bf2c9998f144eb091cb6285b73870a315334bbc Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 9 Sep 2018 13:06:23 +0300 Subject: [PATCH 03/43] Added usage section to doc/index.md --- doc/index.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/doc/index.md b/doc/index.md index 722f132..84718dd 100644 --- a/doc/index.md +++ b/doc/index.md @@ -52,6 +52,45 @@ julia> jcall(j_u_arrays, "binarySearch", jint, (Array{jint,1}, jint), [10,20,30, ``` +##To call from a running JVM + +Use JNI or JNA to initialize a Julia VM and call `JavaCall.init_current_vm()`. Here's an example +using JNA: + +```java +package zot.julia; + +import com.sun.jna.Native; + +public class Julia { + static { + Native.register("julia"); + jl_init__threading(); + } + + public static double bubba = Math.random(); + + public static native void jl_init__threading(); + public static native void jl_eval_string(String code); + public static native void jl_atexit_hook(int status); + + public static void main(String args[]) { + System.out.println("test"); + jl_eval_string("println(\"test from Julia\")"); + jl_eval_string("using JavaCall"); + jl_eval_string("JavaCall.init_current_vm()"); + jl_eval_string("println(\"initialized VM\")"); + jl_eval_string("jlm = @jimport java.lang.Math"); + jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))"); + jl_eval_string("jl = @jimport zot.julia.Julia"); + System.out.println("Bubba should be " + bubba); + jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))"); + jl_eval_string("println(\"Done with tests\")"); + jl_atexit_hook(0); + } +} +``` + ##Major TODOs and Caveats * Currently, only a low level interface is available, via `jcall`. As a result, this package is best suited for writing libraries that wrap around existing java packages. Writing user code direcly using this interface might be a bit tedious at present. A high level interface using reflection will eventually be built. From 0b63144138f6fbbc842486a1e58da7030207995a Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 9 Sep 2018 13:07:42 +0300 Subject: [PATCH 04/43] slight wording improvement --- doc/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/index.md b/doc/index.md index 84718dd..067e691 100644 --- a/doc/index.md +++ b/doc/index.md @@ -52,10 +52,9 @@ julia> jcall(j_u_arrays, "binarySearch", jint, (Array{jint,1}, jint), [10,20,30, ``` -##To call from a running JVM +##Usage from a running JVM -Use JNI or JNA to initialize a Julia VM and call `JavaCall.init_current_vm()`. Here's an example -using JNA: +Use JNI or JNA to initialize a Julia VM, then call `JavaCall.init_current_vm()`. Here's an example using JNA: ```java package zot.julia; From d2d6a3d3f0e0ec1704186914dfa7b9ca7f46096b Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Thu, 13 Sep 2018 12:16:12 +0300 Subject: [PATCH 05/43] checkpoint proxy work --- src/JavaCall.jl | 4 +- src/core.jl | 4 + src/proxy.jl | 306 ++++++++++++++++++++++++++++++++++++++ test/Test$TestInner.class | Bin 392 -> 392 bytes test/Test.class | Bin 2366 -> 2481 bytes test/Test.java | 7 + test/runtests.jl | 13 ++ 7 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/proxy.jl diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 64f04f5..446f1d5 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -4,7 +4,7 @@ export JavaObject, JavaMetaClass, JObject, JClass, JMethod, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, - narrow + narrow, JProxy # using Compat, Compat.Dates @@ -27,10 +27,12 @@ include("jvm.jl") include("core.jl") include("convert.jl") include("reflect.jl") +include("proxy.jl") function __init__() findjvm() global create = Libdl.dlsym(libjvm, :JNI_CreateJavaVM) + initProxy() end diff --git a/src/core.jl b/src/core.jl index d30bf55..49abed1 100644 --- a/src/core.jl +++ b/src/core.jl @@ -36,6 +36,8 @@ mutable struct JavaObject{T} #replace with: JavaObject{T}(argtypes::Tuple, args...) where T JavaObject{T}(argtypes::Tuple, args...) where {T} = jnew(T, argtypes, args...) + JavaObject{T}() where {T} = jnew(T, ()) + JavaObject(::Nothing) = new{Symbol("java.lang.Object")}(C_NULL) end JavaObject(T, ptr) = JavaObject{T}(ptr) @@ -84,6 +86,7 @@ const JMethod = JavaObject{Symbol("java.lang.reflect.Method")} const JThread = JavaObject{Symbol("java.lang.Thread")} const JClassLoader = JavaObject{Symbol("java.lang.ClassLoader")} const JString = JavaObject{Symbol("java.lang.String")} +const JNull = JavaObject(nothing) function JString(str::AbstractString) jstring = ccall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, String(str)) @@ -269,6 +272,7 @@ metaclass(::Type{JavaObject{T}}) where {T} = metaclass(T) metaclass(::JavaObject{T}) where {T} = metaclass(T) javaclassname(class::Symbol) = replace(string(class), "."=>"/") +javaclassname(class::AbstractString) = replace(class, "."=>"/") function geterror(allow=false) isexception = ccall(jnifunc.ExceptionCheck, jboolean, (Ptr{JNIEnv},), penv ) diff --git a/src/proxy.jl b/src/proxy.jl new file mode 100644 index 0000000..d8c8fa7 --- /dev/null +++ b/src/proxy.jl @@ -0,0 +1,306 @@ +const jvoid = Cvoid +const JField = JavaObject{Symbol("java.lang.reflect.Field")} + +struct JMethodInfo + name::String + returnType::Type + void::Bool + argTypes::Tuple + argClasses::Array{JavaObject} + id::Ptr{Nothing} + juliaType::Type + static::Bool + owner::JavaMetaClass +end + +struct JFieldInfo + field::JField + name::String + juliaType::Type + static::Bool + id + function JFieldInfo(field) + name = getname(field) + typ = JavaObject{Symbol(getname(jcall(field, "getType", JClass, ())))} + static = isStatic(field) + id = fieldId(name, typ, static, field) + new(field, name, typ, static, id) + end +end + +struct JMethodProxy + receiver + name + methods::Set{JMethodInfo} + static::Bool +end + +struct JClassInfo + class::JClass + fields::Dict{Symbol, JFieldInfo} + methods::Dict{Symbol, Set{JMethodInfo}} + JClassInfo(class) = new(class, fieldDict(class), methodDict(class)) +end + +struct JProxy + obj::JavaObject + info::JClassInfo + static::Bool + JProxy(::Type{JavaObject{C}}) where {C} = JProxy(string(C), false) + JProxy(::Type{JavaObject{C}}, static) where {C} = JProxy(string(C), static) + JProxy(::JavaMetaClass{C}) where {C} = JProxy(string(C), true) + JProxy(C::AbstractString) = JProxy(classforname(C), false) + JProxy(C::AbstractString, static) = JProxy(classforname(C), static) + JProxy(obj) = JProxy(obj, false) + JProxy(obj, static) = new(static ? JNull : narrow(obj), infoFor(static ? obj : getclass(obj)), static) +end + +struct JavaTypeInfo + signature::AbstractString + juliaType::Type + boxClass::AbstractString +end + +classes = Dict() +methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() +modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} +juliaConverters = Dict() +typeInfo = Dict([ + "int" => JavaTypeInfo("I", jint, "java.lang.Integer"), + "long" => JavaTypeInfo("J", jlong, "java.lang.Long"), + "byte" => JavaTypeInfo("B", jbyte, "java.lang.Byte"), + "boolean" => JavaTypeInfo("Z", jboolean, "java.lang.Boolean"), + "char" => JavaTypeInfo("C", jchar, "java.lang.Character"), + "short" => JavaTypeInfo("S", jshort, "java.lang.Short"), + "float" => JavaTypeInfo("F", jfloat, "java.lang.Float"), + "double" => JavaTypeInfo("D", jdouble, "java.lang.Double"), + "void" => JavaTypeInfo("V", jint, "java.lang.Void"), + "java.lang.String" => JavaTypeInfo("[java/lang/String;", JString, "") +]) + +function methodInfo(m::JMethod) + name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) + cls = jcall(m, "getDeclaringClass", JClass, ()) + methodKey = (getname(cls), name, getname.(argTypes)) + get!(methodCache, methodKey) do + methodId = getmethodid(m, cls, name, returnType, argTypes) + typeName = getname(returnType) + juliaType = juliaTypeFor(typeName) + owner = metaclass(getname(cls)) + JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, juliaType, isStatic(m), owner) + end +end + +isClass(obj::JavaObject) = false +#isClass(obj::JavaObject) = p((::JavaObject{C}) where {C} -> string(C))(narrow(obj)) + +function isStatic(meth::Union{JMethod,JField}) + global modifiers + + mods = jcall(meth, "getModifiers", jint, ()) + jcall(modifiers, "isStatic", jboolean, (jint,), mods) != 0 +end + +conv(func::Function, typ::String) = juliaConverters[typ] = func + +function initProxy() + conv("java.lang.String") do x; JProxy(x).toString(); end + conv("java.lang.Integer") do x; JProxy(x).intValue(); end + conv("java.lang.Long") do x; JProxy(x).longValue(); end +end + +metaclass(class::AbstractString) = metaclass(Symbol(class)) + +function getmethodid(meth::JMethod, cls::JClass, name::AbstractString, rettype::JClass, argtypes::Vector{JClass}) + sig = proxyMethodSignature(rettype, argtypes) + jclass = metaclass(getname(cls)) + result = ccall(isStatic(meth) ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + penv, jclass, name, sig) + if result == C_NULL + println("ERROR CALLING METHOD class: ", jclass, ", name: ", name, ", sig: ", sig, ", arg types: ", argtypes) + end + result==C_NULL && geterror() + result +end + +function fieldId(name, typ::Type{JavaObject{C}}, static, field) where {C} + cls = jcall(field, "getDeclaringClass", JClass, ()) + id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + penv, metaclass(getname(cls)), name, proxyClassSignature(string(C))) + id == C_NULL && geterror(true) + id +end + +function boxClass(cls) + info = get(typeInfo, cls, nothing) + info != nothing ? info.boxClass : cls +end + +function infoSignature(cls::AbstractString) + info = get(typeInfo, cls, nothing) + if info != nothing; info.signature; end +end + +function proxyClassSignature(cls::AbstractString) + sig = infoSignature(cls) + sig != nothing ? sig : proxyClassSignature(classforname(cls)) +end + +function proxyClassSignature(cls::JavaObject) + sig = [] + while jcall(cls, "isArray", jboolean, ()) != 0 + push!(sig, "[") + cls = jcall(cls, "getComponentType", JClass, ()) + end + clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) + push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") + join(sig, "") +end + +function proxyMethodSignature(rettype, argtypes) + s = IOBuffer() + write(s, "(") + for arg in argtypes + write(s, proxyClassSignature(arg)) + end + write(s, ")") + write(s, proxyClassSignature(rettype)) + String(take!(s)) +end + +juliaTypeFor(class::JavaObject) = juliaTypeFor(getname(class)) +function juliaTypeFor(name::AbstractString) + info = get(typeInfo, name, nothing) + info != nothing ? info.juliaType : JavaObject{Symbol(name)} +end + +infoFor(class::JClass) = haskey(classes, class) ? classes[class] : (classes[class] = JClassInfo(class)) + +getname(field::JField) = jcall(field, "getName", JString, ()) + +listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) +listfields(cls::Type{JavaObject{C}}) where C = jcall(classforname(string(C)), "getFields", Vector{JField}, ()) + +fieldDict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item = listfields(class)]) + +function methodDict(class::JClass) + d = Dict() + for method = listmethods(class) + s = get!(d, Symbol(getname(method))) do; Set(); end + push!(s, methodInfo(method)) + end + d +end + +fits(method::JMethodInfo, args::Tuple) = length(method.argTypes) == length(args) && all(canConvert.(method.argTypes, args)) + +canConvert(::Type{JavaObject{T}}, ::AbstractString) where {T} = true +canConvert(::Type{JavaObject{T}}, ::Real) where {T} = true +canConvert(::Type{JString}, ::AbstractString) = true +canConvert(::Type{T1}, ::T2) where {T1, T2 <: Real} = true +canConvert(::Type{jboolean}, ::Bool) = true +canConvert(::Type{jchar}, ::Char) = true +canConvert(x, y) = false +convert(::Type{JavaObject{T}}, n::Int32) where {T} = jnew(Symbol("java.lang.Integer"), (jint,), n) +convert(::Type{JObject}, n::Int64) = jnew(Symbol("java.lang.Long"), (jlong,), n) +convert(::Type{JavaObject{:int}}, n) = convert(jint, n) +convert(::Type{JavaObject{:long}}, n) = convert(jlong, n) +convert(::Type{JavaObject{:byte}}, n) = convert(jbyte, n) +convert(::Type{JavaObject{:boolean}}, n) = convert(jboolean, n) +convert(::Type{JavaObject{:char}}, n) = convert(jchar, n) +convert(::Type{JavaObject{:short}}, n) = convert(jshort, n) +convert(::Type{JavaObject{:float}}, n) = convert(jfloat, n) +convert(::Type{JavaObject{:double}}, n) = convert(jdouble, n) +convert(::Type{JavaObject{:void}}, n) = convert(jvoid, n) + +function (pxy::JMethodProxy)(args...) + targets = Set() + for m = pxy.methods + if fits(m, args) + push!(targets, m) + end + end + if !isempty(targets) + # Find the most specific method + meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) + convertedArgs = convert.(meth.argTypes, args) + result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.juliaType, meth.argTypes, convertedArgs...) + #println("METHOD: ", meth) + asJulia(result) + end +end + +function filterStatic(pxy::JMethodProxy, targets) + static = getfield(pxy, :static) + filter(target->target.static == static, targets) +end + +asJulia(obj) = obj +function asJulia(obj::JavaObject) + (get(juliaConverters, classname(obj), identity))(obj) +end + +classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Class), ()), "getName", JString, ()) + + +# Determine which method is more general using a fairly lame heuristic +function generality(p1::JMethodInfo, p2::JMethodInfo) + g = 0 + for i = 1:length(p1.argTypes) + c1, c2 = p1.argClasses[i], p2.argClasses[i] + g += generality(c1, c2) - generality(c2, c1) + end + g +end + +function generality(c1::JClass, c2::JClass) + p1, p2 = isPrimitive.((c1, c2)) + if !p1 && p2 || jcall(c1, "isAssignableFrom", jboolean, (@jimport(java.lang.Class),), c2) != 0 + 1 + else + 0 + end +end + +isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 + +function Base.getproperty(p::JProxy, name::Symbol) + obj = getfield(p, :obj) + info = getfield(p, :info) + meths = get(info.methods, name, nothing) + static = getfield(p, :static) + result = if meths != nothing + JMethodProxy(obj, name, meths, static) + else + field = info.fields[name] + #jfield(obj, name, info.fields[name].fieldType) + result = ccall(static ? jnifunc.GetStaticObjectField : jnifunc.GetObjectField, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj.ptr, field.id) + result == C_NULL && geterror() + asJulia(convert_result(field.juliaType, result)) + end + isa(result, JavaObject) ? JProxy(result) : result +end + +Base.show(io::IO, pxy::JProxy) = print(io, pxy.toString()) + +JavaObject(pxy::JProxy) = getfield(pxy, :obj) + +function systest() + println("\n\n\n") + init(split("-Djava.class.path=/home/bill/work/workspace-photon/Julia/bin:/home/bill/work/workspace-photon/Julia/dist:/home/bill/work/Dataswarm/Dataswarm-libraries/lib-common/jna-4.5.2.jar -Djna.library.path=/home/bill/work/workspace-photon/Julia/dist", r" +")) + JAL = @jimport java.util.ArrayList + println(JProxy(JavaCall.jnew(Symbol("java.lang.Integer"), (jint,), 3)).toString() == "3") + println(JProxy(convert(JObject, 3)).toString() == "3") + a = JProxy(JAL(())) + println(a.size() == 0) + a.add("one") + println(a.size() == 1) + println(a.toString() == "[one]") + removed = a.remove(0) + println(typeof(removed) == String) + println(removed == "one") +end diff --git a/test/Test$TestInner.class b/test/Test$TestInner.class index ea184fc79fb6bad5475a872b3146645043bd2003..25e98fe5512554915373139a6c139d251d0321a9 100644 GIT binary patch delta 17 YcmeBR?qKFP^>5cc1_lO`jT{Y(067Q+%>V!Z delta 17 YcmeBR?qKFP^>5cc1_lP>jT{Y(067B%%m4rY diff --git a/test/Test.class b/test/Test.class index 8d3fc150530a192d17845388ace2b17d32a28be5..767916c448319dc3e4d14b7bce12b6a8d3902430 100644 GIT binary patch delta 436 zcmX|-J5K^Z6ot?1z|OKEfoNhwOg2=AXd`O0p~ligLBR(CB0g3H#J3>6wK7}bZ?GY1 zp#_DVwp4Z|Hv9l<<6I)%NxpOD%$?lK)H`aOrT3qgI)ETN;JUzu zhE0K-4liPaye{V#2wQ}rhHcUvfn9+;f&JQRmazjXpfo*dCS3@d1qSZx2{Wkqzpc#| zw%mMp69FTaPNqf;fwB|Fs5o(gDB+ZFMmQ%_tTON77r3;lJj$A9pk-5l*Q^ zsBtz!wpDT<<^2RJR`+wMtEKt^O!^-RkYdD78;iJq H%P`g-w#G^2 diff --git a/test/Test.java b/test/Test.java index 0b69779..a06c06c 100644 --- a/test/Test.java +++ b/test/Test.java @@ -83,4 +83,11 @@ public String innerString() { } } + public int getInt() { + return integerField; + } + + public void setInt(int v) { + integerField = v; + } } diff --git a/test/runtests.jl b/test/runtests.jl index 1716bfe..e82fe00 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -234,6 +234,19 @@ end @test isa(narrow(o), JString) end +@testset "proxy_tests" begin + JAL = @jimport java.util.ArrayList + @test JProxy(JavaCall.jnew(Symbol("java.lang.Integer"), (jint,), 3)).toString() == "3" + @test JProxy(convert(JObject, 3)).toString() == "3" + a = JProxy(JAL(())) + @test a.size() == 0 + a.add("one") + @test a.size() == 1 + @test a.toString() == "[one]" + removed = a.remove(0) + @test typeof(removed) == String + @test removed == "one" +end # At the end, unload the JVM before exiting JavaCall.destroy() From c92d06d2c61d1591d6e9a86a03e04c7de5146a38 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Thu, 13 Sep 2018 13:57:22 +0300 Subject: [PATCH 06/43] cleanup and more conversions Adding new conversions to convert.jl --- src/convert.jl | 16 +++++++++++ src/proxy.jl | 73 ++++++++++++++++++------------------------------ test/runtests.jl | 2 +- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/convert.jl b/src/convert.jl index 31d8fe8..2f14488 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -1,5 +1,21 @@ convert(::Type{JString}, str::AbstractString) = JString(str) convert(::Type{JObject}, str::AbstractString) = convert(JObject, JString(str)) +convert(::Type{JavaObject{Symbol("java.lang.Double")}}, n::Real) = jnew(Symbol("java.lang.Double"), (jdouble,), Float64(n)) +convert(::Type{JavaObject{Symbol("java.lang.Float")}}, n::Real) = jnew(Symbol("java.lang.Float"), (jfloat,), Float32(n)) +convert(::Type{JavaObject{Symbol("java.lang.Long")}}, n::Real) = jnew(Symbol("java.lang.Long"), (jlong,), Int64(n)) +convert(::Type{JavaObject{Symbol("java.lang.Integer")}}, n::Real) = jnew(Symbol("java.lang.Integer"), (jint,), Int32(n)) +convert(::Type{JavaObject{Symbol("java.lang.Short")}}, n::Real) = jnew(Symbol("java.lang.Short"), (jshort,), Int16(n)) +convert(::Type{JavaObject{Symbol("java.lang.Byte")}}, n::Real) = jnew(Symbol("java.lang.Byte"), (jbyte,), Int8(n)) +convert(::Type{JavaObject{Symbol("java.lang.Character")}}, n::Real) = jnew(Symbol("java.lang.Character"), (jchar,), Char(n)) +convert(::Type{JavaObject{:int}}, n) = convert(jint, n) +convert(::Type{JavaObject{:long}}, n) = convert(jlong, n) +convert(::Type{JavaObject{:byte}}, n) = convert(jbyte, n) +convert(::Type{JavaObject{:boolean}}, n) = convert(jboolean, n) +convert(::Type{JavaObject{:char}}, n) = convert(jchar, n) +convert(::Type{JavaObject{:short}}, n) = convert(jshort, n) +convert(::Type{JavaObject{:float}}, n) = convert(jfloat, n) +convert(::Type{JavaObject{:double}}, n) = convert(jdouble, n) +convert(::Type{JavaObject{:void}}, n) = convert(jvoid, n) #Cast java object from S to T . Needed for polymorphic calls function convert(::Type{JavaObject{T}}, obj::JavaObject{S}) where {T,S} diff --git a/src/proxy.jl b/src/proxy.jl index d8c8fa7..c651482 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -58,7 +58,6 @@ end struct JavaTypeInfo signature::AbstractString juliaType::Type - boxClass::AbstractString end classes = Dict() @@ -66,16 +65,16 @@ methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} juliaConverters = Dict() typeInfo = Dict([ - "int" => JavaTypeInfo("I", jint, "java.lang.Integer"), - "long" => JavaTypeInfo("J", jlong, "java.lang.Long"), - "byte" => JavaTypeInfo("B", jbyte, "java.lang.Byte"), - "boolean" => JavaTypeInfo("Z", jboolean, "java.lang.Boolean"), - "char" => JavaTypeInfo("C", jchar, "java.lang.Character"), - "short" => JavaTypeInfo("S", jshort, "java.lang.Short"), - "float" => JavaTypeInfo("F", jfloat, "java.lang.Float"), - "double" => JavaTypeInfo("D", jdouble, "java.lang.Double"), - "void" => JavaTypeInfo("V", jint, "java.lang.Void"), - "java.lang.String" => JavaTypeInfo("[java/lang/String;", JString, "") + "int" => JavaTypeInfo("I", jint), + "long" => JavaTypeInfo("J", jlong), + "byte" => JavaTypeInfo("B", jbyte), + "boolean" => JavaTypeInfo("Z", jboolean), + "char" => JavaTypeInfo("C", jchar), + "short" => JavaTypeInfo("S", jshort), + "float" => JavaTypeInfo("F", jfloat), + "double" => JavaTypeInfo("D", jdouble), + "void" => JavaTypeInfo("V", jint), + "java.lang.String" => JavaTypeInfo("[java/lang/String;", JString) ]) function methodInfo(m::JMethod) @@ -133,11 +132,6 @@ function fieldId(name, typ::Type{JavaObject{C}}, static, field) where {C} id end -function boxClass(cls) - info = get(typeInfo, cls, nothing) - info != nothing ? info.boxClass : cls -end - function infoSignature(cls::AbstractString) info = get(typeInfo, cls, nothing) if info != nothing; info.signature; end @@ -183,12 +177,12 @@ getname(field::JField) = jcall(field, "getName", JString, ()) listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) listfields(cls::Type{JavaObject{C}}) where C = jcall(classforname(string(C)), "getFields", Vector{JField}, ()) -fieldDict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item = listfields(class)]) +fieldDict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) function methodDict(class::JClass) d = Dict() - for method = listmethods(class) - s = get!(d, Symbol(getname(method))) do; Set(); end + for method in listmethods(class) + s = get!(()->Set(), d, Symbol(getname(method))) push!(s, methodInfo(method)) end d @@ -196,51 +190,38 @@ end fits(method::JMethodInfo, args::Tuple) = length(method.argTypes) == length(args) && all(canConvert.(method.argTypes, args)) -canConvert(::Type{JavaObject{T}}, ::AbstractString) where {T} = true -canConvert(::Type{JavaObject{T}}, ::Real) where {T} = true +canConvert(::Type{JavaObject{Symbol("java.lang.Object")}}, ::Union{AbstractString, Real}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Double")}}, ::Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Float")}}, ::Union{Float32, Float16, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Long")}}, ::Union{Int64, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Integer")}}, ::Union{Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Short")}}, ::Union{Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Byte")}}, ::Union{Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Character")}}, ::Union{Int8, Char}) = true canConvert(::Type{JString}, ::AbstractString) = true -canConvert(::Type{T1}, ::T2) where {T1, T2 <: Real} = true +canConvert(::Type{<: Real}, ::T) where {T <: Real} = true canConvert(::Type{jboolean}, ::Bool) = true canConvert(::Type{jchar}, ::Char) = true canConvert(x, y) = false -convert(::Type{JavaObject{T}}, n::Int32) where {T} = jnew(Symbol("java.lang.Integer"), (jint,), n) -convert(::Type{JObject}, n::Int64) = jnew(Symbol("java.lang.Long"), (jlong,), n) -convert(::Type{JavaObject{:int}}, n) = convert(jint, n) -convert(::Type{JavaObject{:long}}, n) = convert(jlong, n) -convert(::Type{JavaObject{:byte}}, n) = convert(jbyte, n) -convert(::Type{JavaObject{:boolean}}, n) = convert(jboolean, n) -convert(::Type{JavaObject{:char}}, n) = convert(jchar, n) -convert(::Type{JavaObject{:short}}, n) = convert(jshort, n) -convert(::Type{JavaObject{:float}}, n) = convert(jfloat, n) -convert(::Type{JavaObject{:double}}, n) = convert(jdouble, n) -convert(::Type{JavaObject{:void}}, n) = convert(jvoid, n) function (pxy::JMethodProxy)(args...) - targets = Set() - for m = pxy.methods - if fits(m, args) - push!(targets, m) - end - end + targets = Set(m for m in pxy.methods if fits(m, args)) if !isempty(targets) # Find the most specific method meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) convertedArgs = convert.(meth.argTypes, args) result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.juliaType, meth.argTypes, convertedArgs...) - #println("METHOD: ", meth) - asJulia(result) + if !meth.void; asJulia(result); end end end function filterStatic(pxy::JMethodProxy, targets) static = getfield(pxy, :static) - filter(target->target.static == static, targets) + Set(target for target in targets if target.static == static) end asJulia(obj) = obj -function asJulia(obj::JavaObject) - (get(juliaConverters, classname(obj), identity))(obj) -end +asJulia(obj::JavaObject) = (get(juliaConverters, classname(obj), identity))(obj) classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Class), ()), "getName", JString, ()) @@ -248,7 +229,7 @@ classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Clas # Determine which method is more general using a fairly lame heuristic function generality(p1::JMethodInfo, p2::JMethodInfo) g = 0 - for i = 1:length(p1.argTypes) + for i in 1:length(p1.argTypes) c1, c2 = p1.argClasses[i], p2.argClasses[i] g += generality(c1, c2) - generality(c2, c1) end diff --git a/test/runtests.jl b/test/runtests.jl index e82fe00..6cf73bb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -237,7 +237,7 @@ end @testset "proxy_tests" begin JAL = @jimport java.util.ArrayList @test JProxy(JavaCall.jnew(Symbol("java.lang.Integer"), (jint,), 3)).toString() == "3" - @test JProxy(convert(JObject, 3)).toString() == "3" + @test JProxy(convert(@jimport(java.lang.Integer), 3)).toString() == "3" a = JProxy(JAL(())) @test a.size() == 0 a.add("one") From dc06735b10a86e367b8d810e593c365a1784dcbf Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Fri, 14 Sep 2018 13:31:53 +0300 Subject: [PATCH 07/43] JProxy for better call-in to Java --- src/JavaCall.jl | 1 - src/convert.jl | 2 + src/jvm.jl | 1 + src/proxy.jl | 182 +++++++++++++++++++++++++------------- test/Test$TestInner.class | Bin 392 -> 392 bytes test/Test.class | Bin 2481 -> 3087 bytes test/Test.java | 21 +++++ test/runtests.jl | 23 ++++- 8 files changed, 168 insertions(+), 62 deletions(-) diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 446f1d5..66e6deb 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -32,7 +32,6 @@ include("proxy.jl") function __init__() findjvm() global create = Libdl.dlsym(libjvm, :JNI_CreateJavaVM) - initProxy() end diff --git a/src/convert.jl b/src/convert.jl index 2f14488..d741116 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -1,3 +1,4 @@ +convert(::AbstractString, str::Type{JString}) = unsafe_string(str) convert(::Type{JString}, str::AbstractString) = JString(str) convert(::Type{JObject}, str::AbstractString) = convert(JObject, JString(str)) convert(::Type{JavaObject{Symbol("java.lang.Double")}}, n::Real) = jnew(Symbol("java.lang.Double"), (jdouble,), Float64(n)) @@ -43,6 +44,7 @@ isConvertible(T, S::Ptr{Nothing}) = (ccall(jnifunc.IsAssignableFrom, jboolean, metaclass(T)) == JNI_TRUE) unsafe_convert(::Type{Ptr{Nothing}}, cls::JavaMetaClass) = cls.ptr +unsafe_convert(::Type{Ptr{Nothing}}, cls::JavaObject{Symbol("java.lang.Class")}) = cls.ptr # Get the JNI/C type for a particular Java type function real_jtype(rettype) diff --git a/src/jvm.jl b/src/jvm.jl index 73fecf6..01a049d 100644 --- a/src/jvm.jl +++ b/src/jvm.jl @@ -182,6 +182,7 @@ function init(opts) jvm = unsafe_load(pjvm) global jvmfunc = unsafe_load(jvm.JNIInvokeInterface_) global jnifunc = unsafe_load(jnienv.JNINativeInterface_) #The JNI Function table + initProxy() return end diff --git a/src/proxy.jl b/src/proxy.jl index c651482..d992916 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -1,5 +1,8 @@ +import Base.== + const jvoid = Cvoid const JField = JavaObject{Symbol("java.lang.reflect.Field")} +genericFieldInfo = nothing struct JMethodInfo name::String @@ -11,20 +14,40 @@ struct JMethodInfo juliaType::Type static::Bool owner::JavaMetaClass + primitive::Bool +end + +struct JavaTypeInfo + setterFunc + signature::AbstractString + juliaType::Type + getter::Ptr{Nothing} + staticGetter::Ptr{Nothing} + setter::Ptr{Nothing} + staticSetter::Ptr{Nothing} end struct JFieldInfo field::JField name::String - juliaType::Type + info::JavaTypeInfo static::Bool id + juliaType::Type + fieldClass::JClass + fieldJType::Type + owner::JClass + primitive::Bool function JFieldInfo(field) name = getname(field) - typ = JavaObject{Symbol(getname(jcall(field, "getType", JClass, ())))} + fcl = jcall(field, "getType", JClass, ()) + fjt = JavaObject{Symbol(getname(fcl))} + typ = juliaTypeFor(getname(fcl)) static = isStatic(field) - id = fieldId(name, typ, static, field) - new(field, name, typ, static, id) + cls = jcall(field, "getDeclaringClass", JClass, ()) + id = fieldId(name, JavaObject{Symbol(getname(fcl))}, static, field, cls) + info = get(typeInfo, getname(fcl), genericFieldInfo) + new(field, name, info, static, id, typ, fcl, fjt, cls, isPrimitive(fcl)) end end @@ -46,36 +69,24 @@ struct JProxy obj::JavaObject info::JClassInfo static::Bool - JProxy(::Type{JavaObject{C}}) where {C} = JProxy(string(C), false) - JProxy(::Type{JavaObject{C}}, static) where {C} = JProxy(string(C), static) - JProxy(::JavaMetaClass{C}) where {C} = JProxy(string(C), true) - JProxy(C::AbstractString) = JProxy(classforname(C), false) - JProxy(C::AbstractString, static) = JProxy(classforname(C), static) - JProxy(obj) = JProxy(obj, false) - JProxy(obj, static) = new(static ? JNull : narrow(obj), infoFor(static ? obj : getclass(obj)), static) -end - -struct JavaTypeInfo - signature::AbstractString - juliaType::Type + JProxy(s::AbstractString) = JProxy(JString(s)) + JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}, true) + JProxy(::Type{JavaObject{C}}) where C = JProxy(JavaObject{C}, false) + JProxy(::Type{JavaObject{C}}, static) where C = JProxy(classforname(string(C)), static) + JProxy(obj::JavaObject) = JProxy(obj, false) + JProxy(obj::JavaObject, static) = new(static ? obj : narrow(obj), infoFor(static ? obj : getclass(obj)), static) end classes = Dict() methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} juliaConverters = Dict() -typeInfo = Dict([ - "int" => JavaTypeInfo("I", jint), - "long" => JavaTypeInfo("J", jlong), - "byte" => JavaTypeInfo("B", jbyte), - "boolean" => JavaTypeInfo("Z", jboolean), - "char" => JavaTypeInfo("C", jchar), - "short" => JavaTypeInfo("S", jshort), - "float" => JavaTypeInfo("F", jfloat), - "double" => JavaTypeInfo("D", jdouble), - "void" => JavaTypeInfo("V", jint), - "java.lang.String" => JavaTypeInfo("[java/lang/String;", JString) -]) +typeInfo = nothing + +pxyObj(p::JProxy) = getfield(p, :obj) +pxyPtr(p::JProxy) = pxyObj(p).ptr +pxyInfo(p::JProxy) = getfield(p, :info) +pxyStatic(p::JProxy) = getfield(p, :static) function methodInfo(m::JMethod) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) @@ -84,9 +95,10 @@ function methodInfo(m::JMethod) get!(methodCache, methodKey) do methodId = getmethodid(m, cls, name, returnType, argTypes) typeName = getname(returnType) + info = get(typeInfo, typeName, nothing) juliaType = juliaTypeFor(typeName) owner = metaclass(getname(cls)) - JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, juliaType, isStatic(m), owner) + JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, juliaType, isStatic(m), owner, info != nothing && length(info.signature) == 1) end end @@ -102,10 +114,35 @@ end conv(func::Function, typ::String) = juliaConverters[typ] = func +macro typeInf(sig, typ, object, Typ) + s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) + :(JavaTypeInfo($sig, $typ, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : typ) + ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : typ)), + penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) + end) +end + +==(j1::JProxy, j2::JProxy) = ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, pxyPtr(j1), pxyPtr(j2)) != 0 + function initProxy() - conv("java.lang.String") do x; JProxy(x).toString(); end + #conv("java.lang.String") do x; JProxy(x).toString(); end + conv("java.lang.String") do x; unsafe_string(x); end conv("java.lang.Integer") do x; JProxy(x).intValue(); end conv("java.lang.Long") do x; JProxy(x).longValue(); end + global typeInfo = Dict([ + "int" => @typeInf("I", jint, false, Int) + "long" => @typeInf("J", jlong, false, Long) + "byte" => @typeInf("B", jbyte, false, Byte) + "boolean" => @typeInf("Z", jboolean, false, Boolean) + "char" => @typeInf("C", jchar, false, Char) + "short" => @typeInf("S", jshort, false, Short) + "float" => @typeInf("F", jfloat, false, Float) + "double" => @typeInf("D", jdouble, false, Double) + "void" => @typeInf("V", jint, false, Object) + "java.lang.String" => @typeInf("Ljava/lang/String;", String, true, Object) + ]) + global genericFieldInfo = @typeInf("Ljava/lang/Object", Any, true, Object) end metaclass(class::AbstractString) = metaclass(Symbol(class)) @@ -123,8 +160,7 @@ function getmethodid(meth::JMethod, cls::JClass, name::AbstractString, rettype:: result end -function fieldId(name, typ::Type{JavaObject{C}}, static, field) where {C} - cls = jcall(field, "getDeclaringClass", JClass, ()) +function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(getname(cls)), name, proxyClassSignature(string(C))) @@ -202,7 +238,9 @@ canConvert(::Type{JString}, ::AbstractString) = true canConvert(::Type{<: Real}, ::T) where {T <: Real} = true canConvert(::Type{jboolean}, ::Bool) = true canConvert(::Type{jchar}, ::Char) = true +canConvert(::Type{<:Integer}, ::Ptr{Nothing}) = true canConvert(x, y) = false +convert(::Type{JObject}, pxy::JProxy) = JavaObject(pxy) function (pxy::JMethodProxy)(args...) targets = Set(m for m in pxy.methods if fits(m, args)) @@ -216,15 +254,31 @@ function (pxy::JMethodProxy)(args...) end function filterStatic(pxy::JMethodProxy, targets) - static = getfield(pxy, :static) + static = pxy.static Set(target for target in targets if target.static == static) end +convertPointers(typ, val) = isa(val, Ptr) ? convert(typ, val) : val + +isNull(obj::JavaObject) = isNull(obj.ptr) +isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 + asJulia(obj) = obj -asJulia(obj::JavaObject) = (get(juliaConverters, classname(obj), identity))(obj) +function asJulia(obj::JavaObject) + if isNull(obj) + obj + else + obj = narrow(obj) + (get(juliaConverters, classtypename(obj), identity))(obj) + end +end -classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Class), ()), "getName", JString, ()) +function asJulia(ptr::Ptr{Nothing}) + isNull(ptr) ? JNull : asJulia(JObject(ptr)) +end +classtypename(obj::JavaObject{T}) where T = string(T) +classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Class), ()), "getName", JString, ()) # Determine which method is more general using a fairly lame heuristic function generality(p1::JMethodInfo, p2::JMethodInfo) @@ -248,40 +302,48 @@ end isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 function Base.getproperty(p::JProxy, name::Symbol) - obj = getfield(p, :obj) - info = getfield(p, :info) + obj = pxyObj(p) + info = pxyInfo(p) meths = get(info.methods, name, nothing) - static = getfield(p, :static) + static = pxyStatic(p) result = if meths != nothing JMethodProxy(obj, name, meths, static) else field = info.fields[name] - #jfield(obj, name, info.fields[name].fieldType) - result = ccall(static ? jnifunc.GetStaticObjectField : jnifunc.GetObjectField, Ptr{Nothing}, + result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj.ptr, field.id) + penv, static ? getclass(obj) : obj.ptr, field.id) + result == C_NULL && geterror() + result = (field.primitive ? convert(field.juliaType, result) : result == C_NULL ? JNull : narrow(JavaObject(JObject, result))) + asJulia(result) + end + result != JNull && isa(result, JavaObject) ? JProxy(result) : result +end + +function Base.setproperty!(p::JProxy, name::Symbol, value) + obj = pxyObj(p) + info = pxyInfo(p) + meths = get(info.methods, name, nothing) + static = pxyStatic(p) + result = if meths != nothing + throw(JavaCallError("Attempt to set a method")) + else + if isa(value, JProxy); value = JavaObject(value); end + field = info.fields[name] + value = convert(field.primitive ? field.juliaType : field.fieldJType, value) + result = field.info.setterFunc(field, obj, value) result == C_NULL && geterror() - asJulia(convert_result(field.juliaType, result)) + value end isa(result, JavaObject) ? JProxy(result) : result end -Base.show(io::IO, pxy::JProxy) = print(io, pxy.toString()) - -JavaObject(pxy::JProxy) = getfield(pxy, :obj) - -function systest() - println("\n\n\n") - init(split("-Djava.class.path=/home/bill/work/workspace-photon/Julia/bin:/home/bill/work/workspace-photon/Julia/dist:/home/bill/work/Dataswarm/Dataswarm-libraries/lib-common/jna-4.5.2.jar -Djna.library.path=/home/bill/work/workspace-photon/Julia/dist", r" +")) - JAL = @jimport java.util.ArrayList - println(JProxy(JavaCall.jnew(Symbol("java.lang.Integer"), (jint,), 3)).toString() == "3") - println(JProxy(convert(JObject, 3)).toString() == "3") - a = JProxy(JAL(())) - println(a.size() == 0) - a.add("one") - println(a.size() == 1) - println(a.toString() == "[one]") - removed = a.remove(0) - println(typeof(removed) == String) - println(removed == "one") +function Base.show(io::IO, pxy::JProxy) + if pxyStatic(pxy) + print(io, "static class $(getname(JavaObject(pxy)))") + else + print(io, pxy.toString()) + end end + +JavaObject(pxy::JProxy) = pxyObj(pxy) diff --git a/test/Test$TestInner.class b/test/Test$TestInner.class index 25e98fe5512554915373139a6c139d251d0321a9..4f1b61f3d788396d9f6bf379436564d716c7e175 100644 GIT binary patch delta 19 acmeBR?qJ^F!pImn*_Ba)F?ezwqZ9x$*96Z1 delta 19 acmeBR?qJ^F!pImf*_Ba)F=%ogqZ9x$#{|s) diff --git a/test/Test.class b/test/Test.class index 767916c448319dc3e4d14b7bce12b6a8d3902430..1d7d8b0276cd59a9fc3f1ed96a8eb73165ea26c4 100644 GIT binary patch literal 3087 zcmb7FX?GK46n-Xq!eHB>Em9CmDJ4lMMHYclyEl-w6l@A5EXuSQ(_lIivmkZf_k9QV z-4E_ZLKXG9e)W^zJjegx@p&hcv{~dFC+FOE=HB}}_rCYu_s#GB{PZh;-T1;sp?k3l zh6gFJ-X%oZiwv?J7gpbwkykOvRD`hUDAo=dL& z--<7P>@puW9&z>mSycYT=zLuLe;2Kfy78C?kGt`N51XM2@uZKbJSD`_LOkQavtoVD zjpu#ngzmu$VtrAFmppjcgIC1st8TpJ#_MjpA;CMXWwK#i*U}PXa~)2qnT(dC-!2}6$y*jQl9VhSY<)dYqDAq!Z@T0)1vdMklyl|?<7OC}{W`7MuJ zHj(T;q-N$1t10fTK|~)+r`5&5Okv9s8jHSWtxARPjb513QrU!|XWV#`C0O2!Ng(nM zoZ^m5&cZHD!%pg)G?i?Hjg)N7!d5H))urSn5*Zd_Z80%(6DD35xD9V45!cmhF3m=C z*7oYHj?hP>V?j;m5}N%rG7$ZC#kE2U*?xA=m9ah@{;)86X)gAqyl$l#^Ab{YX{yya zi>6q9;a_|&^S3%7!g{hskIn(f>!23I?>eIY&&f^%v+HN=h{ z(vnGo1w1w#+9%^fd_>)hkrt=VCR)9wMnxsmSDr#1lY@p4n_Xn<)|kff?#lQWpU4=& zsEjd;%Lw5h4}=-rOkBnU4s$pviHCBDWK7%?PEi!Uj8E~I5T7%@ZLPc_0y6FdqdJ7W zEVE+yuM?JbA*@TM-3*d2x{v9ZTq?!GuEj?6IU~kt@#q&va2!n zrRF%DMq-Wzxd{?p8VkzM>h0_>2)Ao5KT18AH_pTid$KU0owc7BjLH;`3tTa589PFr9O{_NYJ4u*4r{cp{-qT zVmkwNV;y#2ieNzzA?-AdS+u+9BWSO8{p^m~8?k08>S#pMRMgpswNv9&8`#Zy?8Idzs?8sJ$j*BwF%)g)3V_Vt9%=7C(i)C`z*$0#}YbbUU63yvQCnE zfu8)ovHSuOa3Ks3LKRV^}KRW&Hwd^d;FjINvT=|^yedpY>=#PJY`VGJ-d=|%H%*UYP zRus>R^Mx2*#7j|_aV%g_h=LH75HAZ+6ymlDJBm9|yb?zbN>MCDhJXBAJYPhHe{a0} zc~U+qoXGHB4dveq%qKGZPs8e}SdQXV73Da3F)xG{XDSsTszTh2;x%#JQ?a7rbro+Y zh|O7ECF?j=S%D_cv6AU|mdC%gQ?ZIxIb&O;f&wM0V4GJd+fLEfB&P3}cg=y4=@bVH zX*jPSGH5$?Wk^9VmAhKLL;nYN=Knc5-)(eFsns<)8sX9pvm|2LG%3Ul0WtK~&2 zW6KekV=<438PaePr!)-UDRyC`$C#VV&Ro=R94Az~rQvP7!}?{No=N&1=_y-_g>yYB z?rV4#4>Y`o8$!G<#0NrrD8vH=f%#FE@{(05xy=09T;`01kMJ>dIkzlkIv{GiV~eXO z=-52loX?A{TUc0O&2~uR#(_LiWipwmWdm@v6(q+!d=Z;VilZ?qGAeHc%NWUd3AC^0`&U*jeu(39fQ&E1V2FlCD9gf0%zkU@MekH;FR}Y#6~%6I|KyPoN0gl(U8#sKM{+9$(Tkv2VAVGS z@b@Ks69@QdI?Qic0E(CdzA0h?cy{oO148*U{~ebeK&So)ir!Iph=865K1Ap{33-OH z4MEBfe-Xr?B8JmAOqW0Gi>;Gm`)Gk7I5_$nckAd#1A-?acP0-zhb`#3!YeGdaS*mqWdu!O%`}HR1 zK0@~s$_af8RIfqPzug2qNN69SV;J|L-7+Q{sjT@%q%K9j31nCa(a2%;M`X7r{42Ik z>A^%>qP>O=Lk}f7{kJuO1f78+2;(RN9>Z?V$VF-d#U;{aG3nEuqK}~675PQY2fL7% z&4;?sHJcB2AvrtMa)Faba<D<~XCfN Date: Fri, 14 Sep 2018 14:22:25 +0300 Subject: [PATCH 08/43] better type conversion from Java to Julia --- src/proxy.jl | 118 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 34 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index d992916..e80af12 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -1,26 +1,28 @@ import Base.== -const jvoid = Cvoid +# See documentation for JProxy for infomation + const JField = JavaObject{Symbol("java.lang.reflect.Field")} genericFieldInfo = nothing struct JMethodInfo name::String - returnType::Type + juliaType::Type void::Bool argTypes::Tuple argClasses::Array{JavaObject} id::Ptr{Nothing} - juliaType::Type static::Bool owner::JavaMetaClass primitive::Bool + convertType::Type end struct JavaTypeInfo setterFunc signature::AbstractString juliaType::Type + convertType::Type getter::Ptr{Nothing} staticGetter::Ptr{Nothing} setter::Ptr{Nothing} @@ -65,16 +67,65 @@ struct JClassInfo JClassInfo(class) = new(class, fieldDict(class), methodDict(class)) end +""" + JProxy(s::AbstractString) + JProxy(::JavaMetaClass) + JProxy(::Type{JavaObject}; static=false) + JProxy(obj::JavaObject; static=false) + +Create a proxy for a Java object that you can use like a Java object. Field and method syntax is like in Java. Primitive types and strings are converted to Julia objects on field accesses and method returns and converted back to Java types when sent as arguments to Java methods. + +*NOTE: Because of this, if you need to call Java methods on a string that you got from Java, you'll have to use `JProxy(str)` to convert the Julia string to a proxied Java string* + +To invoke static methods, set static to true. + +#Example +```jldoctest +julia> a=JProxy(@jimport(java.util.ArrayList)(())) +[] + +julia> a.size() +0 + +julia> a.add("hello") +true + +julia> a.get(0) +"hello" + +julia> a.isEmpty() +false + +julia> a.toString() +"[hello]" + +julia> b = a.clone() +[hello] + +julia> b.add("derp") +true + +julia> a == b +false + +julia> b == b +true + +julia> JProxy(@jimport(java.lang.System)).getName() +"java.lang.System" + +julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") +hello +``` +""" struct JProxy obj::JavaObject info::JClassInfo static::Bool JProxy(s::AbstractString) = JProxy(JString(s)) - JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}, true) - JProxy(::Type{JavaObject{C}}) where C = JProxy(JavaObject{C}, false) - JProxy(::Type{JavaObject{C}}, static) where C = JProxy(classforname(string(C)), static) - JProxy(obj::JavaObject) = JProxy(obj, false) - JProxy(obj::JavaObject, static) = new(static ? obj : narrow(obj), infoFor(static ? obj : getclass(obj)), static) + JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}; static=true) + JProxy(::Type{JavaObject{C}}; static=false) where C = JProxy(classforname(string(C)); static=static) + JProxy(obj::JavaObject; static=false) = new(static ? obj : narrow(obj), infoFor(static ? obj : getclass(obj)), static) end classes = Dict() @@ -88,6 +139,8 @@ pxyPtr(p::JProxy) = pxyObj(p).ptr pxyInfo(p::JProxy) = getfield(p, :info) pxyStatic(p::JProxy) = getfield(p, :static) +==(j1::JProxy, j2::JProxy) = Int64(ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, pxyPtr(j1), pxyPtr(j2))) != 0 + function methodInfo(m::JMethod) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) cls = jcall(m, "getDeclaringClass", JClass, ()) @@ -95,15 +148,14 @@ function methodInfo(m::JMethod) get!(methodCache, methodKey) do methodId = getmethodid(m, cls, name, returnType, argTypes) typeName = getname(returnType) - info = get(typeInfo, typeName, nothing) + info = get(typeInfo, typeName, genericFieldInfo) juliaType = juliaTypeFor(typeName) owner = metaclass(getname(cls)) - JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, juliaType, isStatic(m), owner, info != nothing && length(info.signature) == 1) + JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner, length(info.signature) == 1, info.convertType) end end isClass(obj::JavaObject) = false -#isClass(obj::JavaObject) = p((::JavaObject{C}) where {C} -> string(C))(narrow(obj)) function isStatic(meth::Union{JMethod,JField}) global modifiers @@ -114,35 +166,32 @@ end conv(func::Function, typ::String) = juliaConverters[typ] = func -macro typeInf(sig, typ, object, Typ) +macro typeInf(sig, typ, jtyp, object, Typ) s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) - :(JavaTypeInfo($sig, $typ, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : typ) + :(JavaTypeInfo($sig, $typ, $jtyp, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : typ) ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : typ)), penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) end) end -==(j1::JProxy, j2::JProxy) = ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, pxyPtr(j1), pxyPtr(j2)) != 0 - function initProxy() - #conv("java.lang.String") do x; JProxy(x).toString(); end conv("java.lang.String") do x; unsafe_string(x); end conv("java.lang.Integer") do x; JProxy(x).intValue(); end conv("java.lang.Long") do x; JProxy(x).longValue(); end global typeInfo = Dict([ - "int" => @typeInf("I", jint, false, Int) - "long" => @typeInf("J", jlong, false, Long) - "byte" => @typeInf("B", jbyte, false, Byte) - "boolean" => @typeInf("Z", jboolean, false, Boolean) - "char" => @typeInf("C", jchar, false, Char) - "short" => @typeInf("S", jshort, false, Short) - "float" => @typeInf("F", jfloat, false, Float) - "double" => @typeInf("D", jdouble, false, Double) - "void" => @typeInf("V", jint, false, Object) - "java.lang.String" => @typeInf("Ljava/lang/String;", String, true, Object) + "int" => @typeInf("I", jint, Int32, false, Int) + "long" => @typeInf("J", jlong, Int64, false, Long) + "byte" => @typeInf("B", jbyte, Int8, false, Byte) + "boolean" => @typeInf("Z", jboolean, Bool, false, Boolean) + "char" => @typeInf("C", jchar, Char, false, Char) + "short" => @typeInf("S", jshort, Int16, false, Short) + "float" => @typeInf("F", jfloat, Float32, false, Float) + "double" => @typeInf("D", jdouble, Float64, false, Double) + "void" => @typeInf("V", jint, Nothing, false, Object) + "java.lang.String" => @typeInf("Ljava/lang/String;", String, String, true, Object) ]) - global genericFieldInfo = @typeInf("Ljava/lang/Object", Any, true, Object) + global genericFieldInfo = @typeInf("Ljava/lang/Object", Any, JObject, true, Object) end metaclass(class::AbstractString) = metaclass(Symbol(class)) @@ -249,7 +298,7 @@ function (pxy::JMethodProxy)(args...) meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) convertedArgs = convert.(meth.argTypes, args) result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.juliaType, meth.argTypes, convertedArgs...) - if !meth.void; asJulia(result); end + if !meth.void; asJulia(meth.convertType, result); end end end @@ -263,18 +312,19 @@ convertPointers(typ, val) = isa(val, Ptr) ? convert(typ, val) : val isNull(obj::JavaObject) = isNull(obj.ptr) isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 -asJulia(obj) = obj -function asJulia(obj::JavaObject) +asJulia(t, obj) = obj +asJulia(::Type{Bool}, obj) = obj != 0 +function asJulia(x, obj::JavaObject) if isNull(obj) obj else obj = narrow(obj) - (get(juliaConverters, classtypename(obj), identity))(obj) + (get(juliaConverters, classtypename(obj), JProxy))(obj) end end -function asJulia(ptr::Ptr{Nothing}) - isNull(ptr) ? JNull : asJulia(JObject(ptr)) +function asJulia(x, ptr::Ptr{Nothing}) + isNull(ptr) ? JNull : asJulia(x, JObject(ptr)) end classtypename(obj::JavaObject{T}) where T = string(T) @@ -315,7 +365,7 @@ function Base.getproperty(p::JProxy, name::Symbol) penv, static ? getclass(obj) : obj.ptr, field.id) result == C_NULL && geterror() result = (field.primitive ? convert(field.juliaType, result) : result == C_NULL ? JNull : narrow(JavaObject(JObject, result))) - asJulia(result) + asJulia(field.juliaType, result) end result != JNull && isa(result, JavaObject) ? JProxy(result) : result end From 9f1154b405ed941cd5b045ebac349fe13344ba90 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Fri, 14 Sep 2018 14:25:21 +0300 Subject: [PATCH 09/43] fixing tests to conform to new JProxy "static" keyword --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index b04c205..5cd28f0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -263,8 +263,8 @@ end end @testset "proxy_meta" begin - @test(JProxy(@jimport(java.lang.Integer), true).MAX_VALUE == 2147483647) - @test(JProxy(@jimport(java.lang.Long), true).MAX_VALUE == 9223372036854775807) + @test(JProxy(@jimport(java.lang.Integer); static=true).MAX_VALUE == 2147483647) + @test(JProxy(@jimport(java.lang.Long); static=true).MAX_VALUE == 9223372036854775807) #This currently fails #@testprintln(JProxy(@jimport(java.lang.Double), true).MAX_VALUE == 1.7976931348623157e308) end From 6e60b351b756d404dfd25e4143ea3029812978ef Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Fri, 14 Sep 2018 14:35:46 +0300 Subject: [PATCH 10/43] adding documentation --- doc/index.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/doc/index.md b/doc/index.md index 067e691..287d34e 100644 --- a/doc/index.md +++ b/doc/index.md @@ -90,6 +90,57 @@ public class Julia { } ``` +##JProxy + +JProxy lets you use Java-like syntax to access fields and methods in Java. You can: + +* Get field values +* Set field values +* Call methods +* Create static proxies to access static members + +Primitive types and strings are converted to Julia objects on field accesses and method returns and converted back to Java types when sent as arguments to Java methods. + +*NOTE: Because of this, if you need to call Java methods on a string that you got from Java, you'll have to use `JProxy(str)` to convert the Julia string to a proxied Java string* + +### Examples +```jldoctest +julia> a=JProxy(@jimport(java.util.ArrayList)(())) +[] + +julia> a.size() +0 + +julia> a.add("hello") +true + +julia> a.get(0) +"hello" + +julia> a.isEmpty() +false + +julia> a.toString() +"[hello]" + +julia> b = a.clone() +[hello] + +julia> b.add("derp") +true + +julia> a == b +false + +julia> b == b +true + +julia> JProxy(@jimport(java.lang.System)).getName() +"java.lang.System" + +julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") +hello +``` ##Major TODOs and Caveats * Currently, only a low level interface is available, via `jcall`. As a result, this package is best suited for writing libraries that wrap around existing java packages. Writing user code direcly using this interface might be a bit tedious at present. A high level interface using reflection will eventually be built. From 6cd466de6dbf847d1325fa6c6cef66c28193d3ef Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Fri, 14 Sep 2018 15:10:09 +0300 Subject: [PATCH 11/43] associate Java null with Julia nothing --- src/convert.jl | 1 + src/core.jl | 2 +- src/proxy.jl | 12 +++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/convert.jl b/src/convert.jl index d741116..30d01be 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -17,6 +17,7 @@ convert(::Type{JavaObject{:short}}, n) = convert(jshort, n) convert(::Type{JavaObject{:float}}, n) = convert(jfloat, n) convert(::Type{JavaObject{:double}}, n) = convert(jdouble, n) convert(::Type{JavaObject{:void}}, n) = convert(jvoid, n) +convert(::Type{JavaObject{T}}, ::Nothing) where T = jnull #Cast java object from S to T . Needed for polymorphic calls function convert(::Type{JavaObject{T}}, obj::JavaObject{S}) where {T,S} diff --git a/src/core.jl b/src/core.jl index 49abed1..b41914d 100644 --- a/src/core.jl +++ b/src/core.jl @@ -86,7 +86,7 @@ const JMethod = JavaObject{Symbol("java.lang.reflect.Method")} const JThread = JavaObject{Symbol("java.lang.Thread")} const JClassLoader = JavaObject{Symbol("java.lang.ClassLoader")} const JString = JavaObject{Symbol("java.lang.String")} -const JNull = JavaObject(nothing) +const jnull = JavaObject(nothing) function JString(str::AbstractString) jstring = ccall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, String(str)) diff --git a/src/proxy.jl b/src/proxy.jl index e80af12..a5ab948 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -4,6 +4,7 @@ import Base.== const JField = JavaObject{Symbol("java.lang.reflect.Field")} genericFieldInfo = nothing +objectClass = nothing struct JMethodInfo name::String @@ -125,7 +126,7 @@ struct JProxy JProxy(s::AbstractString) = JProxy(JString(s)) JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}; static=true) JProxy(::Type{JavaObject{C}}; static=false) where C = JProxy(classforname(string(C)); static=static) - JProxy(obj::JavaObject; static=false) = new(static ? obj : narrow(obj), infoFor(static ? obj : getclass(obj)), static) + JProxy(obj::JavaObject; static=false) = new(static ? obj : isNull(obj) ? obj : narrow(obj), infoFor(static ? obj : isNull(obj) ? objectClass : getclass(obj)), static) end classes = Dict() @@ -192,6 +193,7 @@ function initProxy() "java.lang.String" => @typeInf("Ljava/lang/String;", String, String, true, Object) ]) global genericFieldInfo = @typeInf("Ljava/lang/Object", Any, JObject, true, Object) + global objectClass = classforname("java.lang.Object") end metaclass(class::AbstractString) = metaclass(Symbol(class)) @@ -316,7 +318,7 @@ asJulia(t, obj) = obj asJulia(::Type{Bool}, obj) = obj != 0 function asJulia(x, obj::JavaObject) if isNull(obj) - obj + nothing else obj = narrow(obj) (get(juliaConverters, classtypename(obj), JProxy))(obj) @@ -324,7 +326,7 @@ function asJulia(x, obj::JavaObject) end function asJulia(x, ptr::Ptr{Nothing}) - isNull(ptr) ? JNull : asJulia(x, JObject(ptr)) + isNull(ptr) ? jnull : asJulia(x, JObject(ptr)) end classtypename(obj::JavaObject{T}) where T = string(T) @@ -364,10 +366,10 @@ function Base.getproperty(p::JProxy, name::Symbol) (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, static ? getclass(obj) : obj.ptr, field.id) result == C_NULL && geterror() - result = (field.primitive ? convert(field.juliaType, result) : result == C_NULL ? JNull : narrow(JavaObject(JObject, result))) + result = (field.primitive ? convert(field.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) asJulia(field.juliaType, result) end - result != JNull && isa(result, JavaObject) ? JProxy(result) : result + result != jnull && isa(result, JavaObject) ? JProxy(result) : result end function Base.setproperty!(p::JProxy, name::Symbol, value) From 03089abf7087889f6e63db9c955e242aded08fc6 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Fri, 14 Sep 2018 15:18:10 +0300 Subject: [PATCH 12/43] more docu --- doc/index.md | 4 ++++ src/proxy.jl | 2 ++ 2 files changed, 6 insertions(+) diff --git a/doc/index.md b/doc/index.md index 287d34e..3745233 100644 --- a/doc/index.md +++ b/doc/index.md @@ -103,6 +103,10 @@ Primitive types and strings are converted to Julia objects on field accesses and *NOTE: Because of this, if you need to call Java methods on a string that you got from Java, you'll have to use `JProxy(str)` to convert the Julia string to a proxied Java string* +To invoke static methods, set static to true (see below). + +To get a JProxy's Java object, use `JavaObject(proxy)` + ### Examples ```jldoctest julia> a=JProxy(@jimport(java.util.ArrayList)(())) diff --git a/src/proxy.jl b/src/proxy.jl index a5ab948..03540f3 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -80,6 +80,8 @@ Create a proxy for a Java object that you can use like a Java object. Field and To invoke static methods, set static to true. +To get a JProxy's Java object, use `JavaObject(proxy)` + #Example ```jldoctest julia> a=JProxy(@jimport(java.util.ArrayList)(())) From 53a49c3baf23f463974d794ffbfc9e7e60cee641 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 15 Sep 2018 15:22:52 +0300 Subject: [PATCH 13/43] cleaning up types, removing redundant fields, etc. --- src/proxy.jl | 103 ++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 03540f3..78a0dff 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -6,57 +6,48 @@ const JField = JavaObject{Symbol("java.lang.reflect.Field")} genericFieldInfo = nothing objectClass = nothing -struct JMethodInfo - name::String - juliaType::Type - void::Bool - argTypes::Tuple - argClasses::Array{JavaObject} - id::Ptr{Nothing} - static::Bool - owner::JavaMetaClass - primitive::Bool - convertType::Type -end - struct JavaTypeInfo setterFunc + class::Type{JavaObject{T}} where T # narrowed JavaObject type signature::AbstractString - juliaType::Type - convertType::Type + juliaType::Type # the Julia representation of the Java type, like jboolean (which is a UInt8), for call-in + convertType::Type # the Julia type to convert results to, like Bool or String getter::Ptr{Nothing} staticGetter::Ptr{Nothing} setter::Ptr{Nothing} staticSetter::Ptr{Nothing} end +struct JMethodInfo + name::String + typeInfo::JavaTypeInfo + argTypes::Tuple + argClasses::Array{JavaObject} + id::Ptr{Nothing} + static::Bool + owner::JavaMetaClass +end + struct JFieldInfo field::JField - name::String info::JavaTypeInfo static::Bool - id - juliaType::Type - fieldClass::JClass - fieldJType::Type + id::Ptr{Nothing} owner::JClass primitive::Bool - function JFieldInfo(field) - name = getname(field) + function JFieldInfo(field::JField) fcl = jcall(field, "getType", JClass, ()) - fjt = JavaObject{Symbol(getname(fcl))} typ = juliaTypeFor(getname(fcl)) static = isStatic(field) cls = jcall(field, "getDeclaringClass", JClass, ()) - id = fieldId(name, JavaObject{Symbol(getname(fcl))}, static, field, cls) + id = fieldId(getname(field), JavaObject{Symbol(getname(fcl))}, static, field, cls) info = get(typeInfo, getname(fcl), genericFieldInfo) - new(field, name, info, static, id, typ, fcl, fjt, cls, isPrimitive(fcl)) + new(field, info, static, id, cls, isPrimitive(fcl)) end end struct JMethodProxy receiver - name methods::Set{JMethodInfo} static::Bool end @@ -152,12 +143,15 @@ function methodInfo(m::JMethod) methodId = getmethodid(m, cls, name, returnType, argTypes) typeName = getname(returnType) info = get(typeInfo, typeName, genericFieldInfo) - juliaType = juliaTypeFor(typeName) owner = metaclass(getname(cls)) - JMethodInfo(name, juliaType, typeName == "void", Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner, length(info.signature) == 1, info.convertType) + JMethodInfo(name, info, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) end end +isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing + +isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 + isClass(obj::JavaObject) = false function isStatic(meth::Union{JMethod,JField}) @@ -169,11 +163,22 @@ end conv(func::Function, typ::String) = juliaConverters[typ] = func -macro typeInf(sig, typ, jtyp, object, Typ) +macro typeInf(jclass, sig, jtyp, Typ, object) + _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, Typ, object) +end + +macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object) + if typeof(jclass) == String + jclass = Symbol(jclass) + end + _typeInf(jclass, ctyp, sig, jtyp, Typ, object) +end + +function _typeInf(jclass, ctyp, sig, jtyp, Typ, object) s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) - :(JavaTypeInfo($sig, $typ, $jtyp, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : typ) + :(JavaTypeInfo(JavaObject{Symbol($(string(jclass)))}, $sig, $ctyp, $jtyp, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : typ)), + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) end) end @@ -183,18 +188,18 @@ function initProxy() conv("java.lang.Integer") do x; JProxy(x).intValue(); end conv("java.lang.Long") do x; JProxy(x).longValue(); end global typeInfo = Dict([ - "int" => @typeInf("I", jint, Int32, false, Int) - "long" => @typeInf("J", jlong, Int64, false, Long) - "byte" => @typeInf("B", jbyte, Int8, false, Byte) - "boolean" => @typeInf("Z", jboolean, Bool, false, Boolean) - "char" => @typeInf("C", jchar, Char, false, Char) - "short" => @typeInf("S", jshort, Int16, false, Short) - "float" => @typeInf("F", jfloat, Float32, false, Float) - "double" => @typeInf("D", jdouble, Float64, false, Double) - "void" => @typeInf("V", jint, Nothing, false, Object) - "java.lang.String" => @typeInf("Ljava/lang/String;", String, String, true, Object) + "int" => @typeInf(int, "I", Int32, Int, false) + "long" => @typeInf(long, "J", Int64, Long, false) + "byte" => @typeInf(byte, "B", Int8, Byte, false) + "boolean" => @typeInf(boolean, "Z", Bool, Boolean, false) + "char" => @typeInf(char, "C", Char, Char, false) + "short" => @typeInf(short, "S", Int16, Short, false) + "float" => @typeInf(float, "F", Float32, Float, false) + "double" => @typeInf(double, "D", Float64, Double, false) + "void" => @vtypeInf(void, jint, "V", Nothing, Object, false) + "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true) ]) - global genericFieldInfo = @typeInf("Ljava/lang/Object", Any, JObject, true, Object) + global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true) global objectClass = classforname("java.lang.Object") end @@ -301,8 +306,8 @@ function (pxy::JMethodProxy)(args...) # Find the most specific method meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) convertedArgs = convert.(meth.argTypes, args) - result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.juliaType, meth.argTypes, convertedArgs...) - if !meth.void; asJulia(meth.convertType, result); end + result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.typeInfo.juliaType, meth.argTypes, convertedArgs...) + if !isVoid(meth); asJulia(meth.typeInfo.convertType, result); end end end @@ -353,23 +358,21 @@ function generality(c1::JClass, c2::JClass) end end -isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 - function Base.getproperty(p::JProxy, name::Symbol) obj = pxyObj(p) info = pxyInfo(p) meths = get(info.methods, name, nothing) static = pxyStatic(p) result = if meths != nothing - JMethodProxy(obj, name, meths, static) + JMethodProxy(obj, meths, static) else field = info.fields[name] result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, static ? getclass(obj) : obj.ptr, field.id) result == C_NULL && geterror() - result = (field.primitive ? convert(field.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) - asJulia(field.juliaType, result) + result = (field.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) + asJulia(field.info.juliaType, result) end result != jnull && isa(result, JavaObject) ? JProxy(result) : result end @@ -384,7 +387,7 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) else if isa(value, JProxy); value = JavaObject(value); end field = info.fields[name] - value = convert(field.primitive ? field.juliaType : field.fieldJType, value) + value = convert(field.primitive ? field.info.juliaType : field.info.class, value) result = field.info.setterFunc(field, obj, value) result == C_NULL && geterror() value From 1dd74b7a96f4b90d45b9172624594f225750938e Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 15 Sep 2018 15:27:47 +0300 Subject: [PATCH 14/43] cleaned up typeInf macro --- src/proxy.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 78a0dff..93d0229 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -163,8 +163,8 @@ end conv(func::Function, typ::String) = juliaConverters[typ] = func -macro typeInf(jclass, sig, jtyp, Typ, object) - _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, Typ, object) +macro typeInf(jclass, sig, jtyp) + _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false) end macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object) @@ -188,14 +188,14 @@ function initProxy() conv("java.lang.Integer") do x; JProxy(x).intValue(); end conv("java.lang.Long") do x; JProxy(x).longValue(); end global typeInfo = Dict([ - "int" => @typeInf(int, "I", Int32, Int, false) - "long" => @typeInf(long, "J", Int64, Long, false) - "byte" => @typeInf(byte, "B", Int8, Byte, false) - "boolean" => @typeInf(boolean, "Z", Bool, Boolean, false) - "char" => @typeInf(char, "C", Char, Char, false) - "short" => @typeInf(short, "S", Int16, Short, false) - "float" => @typeInf(float, "F", Float32, Float, false) - "double" => @typeInf(double, "D", Float64, Double, false) + "int" => @typeInf(int, "I", Int32) + "long" => @typeInf(long, "J", Int64) + "byte" => @typeInf(byte, "B", Int8) + "boolean" => @typeInf(boolean, "Z", Bool) + "char" => @typeInf(char, "C", Char) + "short" => @typeInf(short, "S", Int16) + "float" => @typeInf(float, "F", Float32) + "double" => @typeInf(double, "D", Float64) "void" => @vtypeInf(void, jint, "V", Nothing, Object, false) "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true) ]) From 459fc57122d9c4a17715af2add8251430f76f747 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 17 Sep 2018 17:39:33 +0300 Subject: [PATCH 15/43] fixing arg conversion bug, changing proxy to use generated methods array arg conversion did not work for empty arrays adding JConstructor --- src/convert.jl | 14 +- src/core.jl | 1 + src/proxy.jl | 485 ++++++++++++++++++++++++++++++++++--------------- src/reflect.jl | 2 +- 4 files changed, 353 insertions(+), 149 deletions(-) diff --git a/src/convert.jl b/src/convert.jl index 30d01be..630334a 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -106,9 +106,9 @@ end function convert_arg(argtype::Type{Array{T,1}}, arg) where T<:JavaObject carg = convert(argtype, arg) sz = length(carg) - init = carg[1] + init = sz > 0 ? carg[1].ptr : C_NULL arrayptr = ccall(jnifunc.NewObjectArray, Ptr{Nothing}, - (Ptr{JNIEnv}, jint, Ptr{Nothing}, Ptr{Nothing}), penv, sz, metaclass(T), init.ptr) + (Ptr{JNIEnv}, jint, Ptr{Nothing}, Ptr{Nothing}), penv, sz, metaclass(T), init) for i=2:sz ccall(jnifunc.SetObjectArrayElement, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}, jint, Ptr{Nothing}), penv, arrayptr, i-1, carg[i].ptr) @@ -283,14 +283,16 @@ function convert(::Type{@jimport(java.util.List)}, x::Vector, V::Type{JavaObject end # Convert a reference to a java.lang.String into a Julia string. Copies the underlying byte buffer -function unsafe_string(jstr::JString) #jstr must be a jstring obtained via a JNI call - if isnull(jstr); return ""; end #Return empty string to keep type stability. But this is questionable +unsafe_string(jstr::JString) = unsafe_string(jstr.ptr) #jstr must be a jstring obtained via a JNI call + +function unsafe_string(jstr::Ptr{Nothing}) #jstr must be a jstring obtained via a JNI call + if jstr == C_NULL; return ""; end #Return empty string to keep type stability. But this is questionable pIsCopy = Array{jboolean}(undef, 1) buf::Ptr{UInt8} = ccall(jnifunc.GetStringUTFChars, Ptr{UInt8}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{jboolean}), penv, jstr.ptr, pIsCopy) + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{jboolean}), penv, jstr, pIsCopy) s = unsafe_string(buf) ccall(jnifunc.ReleaseStringUTFChars, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}), penv, - jstr.ptr, buf) + jstr, buf) return s end diff --git a/src/core.jl b/src/core.jl index b41914d..21f8bc3 100644 --- a/src/core.jl +++ b/src/core.jl @@ -83,6 +83,7 @@ isnull(obj::JavaMetaClass) = obj.ptr == C_NULL const JClass = JavaObject{Symbol("java.lang.Class")} const JObject = JavaObject{Symbol("java.lang.Object")} const JMethod = JavaObject{Symbol("java.lang.reflect.Method")} +const JConstructor = JavaObject{Symbol("java.lang.reflect.Constructor")} const JThread = JavaObject{Symbol("java.lang.Thread")} const JClassLoader = JavaObject{Symbol("java.lang.ClassLoader")} const JString = JavaObject{Symbol("java.lang.String")} diff --git a/src/proxy.jl b/src/proxy.jl index 93d0229..30a2b45 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -1,10 +1,14 @@ +# TODO: box incoming primitives that are sent to object args, including Strings + import Base.== # See documentation for JProxy for infomation const JField = JavaObject{Symbol("java.lang.reflect.Field")} + genericFieldInfo = nothing objectClass = nothing +methodsById = Dict() struct JavaTypeInfo setterFunc @@ -12,13 +16,26 @@ struct JavaTypeInfo signature::AbstractString juliaType::Type # the Julia representation of the Java type, like jboolean (which is a UInt8), for call-in convertType::Type # the Julia type to convert results to, like Bool or String + primitive::Bool + accessorName::AbstractString + boxType::Type{JavaObject{T}} where T + boxClass::JClass + primClass::JClass getter::Ptr{Nothing} staticGetter::Ptr{Nothing} setter::Ptr{Nothing} staticSetter::Ptr{Nothing} + function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) + boxClass = classfortype(boxType) + primitive = length(signature) == 1 + primClass = primitive ? jfield(boxType, "TYPE", JClass) : objectClass + info = new(setterFunc, class, signature, juliaType, convertType, primitive, accessorName, boxType, boxClass, primClass, getter, staticGetter, setter, staticSetter) + info + end end struct JMethodInfo + uid::Int64 name::String typeInfo::JavaTypeInfo argTypes::Tuple @@ -34,7 +51,6 @@ struct JFieldInfo static::Bool id::Ptr{Nothing} owner::JClass - primitive::Bool function JFieldInfo(field::JField) fcl = jcall(field, "getType", JClass, ()) typ = juliaTypeFor(getname(fcl)) @@ -42,23 +58,52 @@ struct JFieldInfo cls = jcall(field, "getDeclaringClass", JClass, ()) id = fieldId(getname(field), JavaObject{Symbol(getname(fcl))}, static, field, cls) info = get(typeInfo, getname(fcl), genericFieldInfo) - new(field, info, static, id, cls, isPrimitive(fcl)) + new(field, info, static, id, cls) end end - -struct JMethodProxy - receiver - methods::Set{JMethodInfo} + +struct JMethodProxy{N, T} + obj static::Bool end struct JClassInfo class::JClass fields::Dict{Symbol, JFieldInfo} - methods::Dict{Symbol, Set{JMethodInfo}} - JClassInfo(class) = new(class, fieldDict(class), methodDict(class)) + methods::Set{Symbol} + classType::Type end +struct Boxing + info::JavaTypeInfo + boxType::Type + boxClass::JClass + primClass::JClass + boxer::Ptr{Nothing} + unboxer::Ptr{Nothing} + function Boxing(info) + boxer = methodInfo(getConstructor(info.boxType, info.primClass)).id + unboxer = methodInfo(getMethod(info.boxType, info.accessorName)).id + new(info, info.boxType, info.boxClass, info.primClass, boxer, unboxer) + end +end + +boxers = Dict() + +isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing + +juliaConverters = Dict() +classtypename(obj::JavaObject{T}) where T = string(T) + +abstract type java_lang end + +#types = Dict("java.lang.Object" => java_lang_Object) +types = Dict() + +typeNameFor(className::String) = Symbol(replace(replace(className, "_" => "__"), "." => "_")) + +typeNameString(className::String) = string(typeNameFor(className)) + """ JProxy(s::AbstractString) JProxy(::JavaMetaClass) @@ -112,48 +157,221 @@ julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") hello ``` """ -struct JProxy +struct JProxy{C <: java_lang} obj::JavaObject info::JClassInfo static::Bool JProxy(s::AbstractString) = JProxy(JString(s)) JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}; static=true) - JProxy(::Type{JavaObject{C}}; static=false) where C = JProxy(classforname(string(C)); static=static) - JProxy(obj::JavaObject; static=false) = new(static ? obj : isNull(obj) ? obj : narrow(obj), infoFor(static ? obj : isNull(obj) ? objectClass : getclass(obj)), static) + function JProxy(obj::JavaObject) where C + obj = narrow(obj) + info = infoFor(isNull(obj) ? objectClass : getclass(obj)) + new{types[javaType(obj)]}(isNull(obj) ? obj : narrow(obj), info, false) + end + function JProxy(::Type{JavaObject{C}}; static::Bool=false) where C + obj = classforname(string(C)) + info = infoFor(obj) + new{types[C]}(obj, info, true) + end end +const JLegalArg = Union{Number, String, JProxy} +const JPrimitive = Union{Bool, Char, Int8, Int16, Int32, Int64, Float32, Float64} +const JNumber = Union{Int8, Int16, Int32, Int64, Float32, Float64} +const JBoxTypes = Union{ + Type{JavaObject{Symbol("java.lang.Boolean")}}, + Type{JavaObject{Symbol("java.lang.Byte")}}, + Type{JavaObject{Symbol("java.lang.Character")}}, + Type{JavaObject{Symbol("java.lang.Short")}}, + Type{JavaObject{Symbol("java.lang.Integer")}}, + Type{JavaObject{Symbol("java.lang.Long")}}, + Type{JavaObject{Symbol("java.lang.Float")}}, + Type{JavaObject{Symbol("java.lang.Double")}} +} +const JBoxed = Union{ + JavaObject{Symbol("java.lang.Boolean")}, + JavaObject{Symbol("java.lang.Byte")}, + JavaObject{Symbol("java.lang.Character")}, + JavaObject{Symbol("java.lang.Short")}, + JavaObject{Symbol("java.lang.Integer")}, + JavaObject{Symbol("java.lang.Long")}, + JavaObject{Symbol("java.lang.Float")}, + JavaObject{Symbol("java.lang.Double")} +} + classes = Dict() methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} -juliaConverters = Dict() -typeInfo = nothing +typeInfo = Dict() + +struct GenInfo + code + typeCode + deps + classList + methodSets + GenInfo() = new([], [], Set(), [], Dict()) +end + +hasClass(gen, name::Symbol) = haskey(types, name) || haskey(gen.methodSets, string(name)) + +function genClass(class, gen::GenInfo) + push!(gen.classList, class) + name = getname(class) + sc = superclass(class) + if !isNull(sc) + supername = getname(sc) + !hasClass(gen, Symbol(supername)) && genClass(sc, gen) + push!(gen.typeCode, :(abstract type $(typeNameFor(name)) <: $(typeNameFor(supername)) end)) + else + push!(gen.typeCode, :(abstract type $(typeNameFor(name)) <: java_lang end)) + end + genMethods(class, gen) +end + +struct GenArgInfo + name + javaType + juliaType + function GenArgInfo(index, info, gen) + javaType = info.argTypes[index] + new(Symbol("a" * string(index)), javaType, argType(javaType, gen)) + end +end + +argType(t, gen) = t +argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String +argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = JLegalArg +argType(::Type{<: Number}, gen) = Number +function argType(::Type{JavaObject{T}}, gen) where T + cl = findfirst("[", string(T)) != nothing ? Symbol("java.lang.Object") : T + !hasClass(gen, cl) && push!(gen.deps, cl) + :(JProxy{<:$(typeNameFor(string(cl)))}) +end + +function argCode(arg::GenArgInfo) + argname = arg.name + if arg.juliaType == String + :(JString($argname)) + elseif arg.juliaType == JLegalArg + :(box($argname)) + elseif arg.juliaType == Number + :($(arg.javaType)($argname)) + else + argname + end +end + +function genMethods(class, gen) + gen.methodSets[getname(class)] = methods = Set() + for method in listmethods(class) + name = getname(method) + push!(methods, Symbol(name)) + info = methodInfo(method) + owner = string(javaType(info.owner)) + if isSame(class.ptr, info.owner.ptr) + args = (GenArgInfo(i, info, gen) for i in 1:length(info.argTypes)) + argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) + push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $(typeNameFor(owner))})($(argDecs...)) + $(genConvertResult(info.typeInfo.convertType, info, :(_jcall(getfield(pxy, :obj), methodsById[$(info.uid)].id, C_NULL, $(info.typeInfo.juliaType), ($(info.argTypes...),), $((argCode(arg) for arg in args)...))))) + end)) + end + end +end + +genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) +genConvertResult(toType::Type{String}, info, expr) = :(unsafe_string($expr)) +genConvertResult(toType::JBoxTypes, info, expr) = :(unbox($expr)) +function genConvertResult(toType, info, expr) + if isVoid(info) || info.typeInfo.primitive + expr + else + :(asJulia($toType, $expr)) + end +end + +function JClassInfo(class::JClass) + gen = GenInfo() + genClass(class, gen) + while !isempty(gen.deps) + cls = iterate(gen.deps)[1] + delete!(gen.deps, cls) + !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) + end + for cl in gen.classList + name = getname(cl) + push!(gen.typeCode, :(types[Symbol($name)] = $(typeNameFor(name)))) + end + println("\nEVALUATING...\n\n") + expr = :(begin $(gen.typeCode...); $(gen.code...); end) + println(expr) + eval(expr) + println("DONE EVALUATING") + for cl in gen.classList + n = getname(cl) + classes[n] = JClassInfo(cl, fieldDict(cl), gen.methodSets[n], types[Symbol(n)]) + end + classes[getname(class)] +end + +asJulia(t, obj) = obj +asJulia(::Type{Bool}, obj) = obj != 0 +asJulia(t, obj::JBoxed) = unbox(obj) +function asJulia(x, obj::JavaObject) + if isNull(obj) + nothing + else + (get(juliaConverters, classtypename(obj), JProxy))(obj) + end +end +function asJulia(x, ptr::Ptr{Nothing}) + if isNull(ptr) + jnull + else + asJulia(x, JavaObject{Symbol(getclassname(getclass(ptr)))}(ptr)) + end +end + +box(str::String) = str +box(pxy::JProxy) = pxyObj(pxy) +unbox(obj) = obj pxyObj(p::JProxy) = getfield(p, :obj) pxyPtr(p::JProxy) = pxyObj(p).ptr pxyInfo(p::JProxy) = getfield(p, :info) pxyStatic(p::JProxy) = getfield(p, :static) -==(j1::JProxy, j2::JProxy) = Int64(ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, pxyPtr(j1), pxyPtr(j2))) != 0 +==(j1::JProxy, j2::JProxy) = isSame(pxyPtr(j1), pxyPtr(j2)) -function methodInfo(m::JMethod) +isSame(j1::JavaObject, j2::JavaObject) = isSame(j1.ptr, j2.ptr) +isSame(j1::Ptr{Nothing}, j2::Ptr{Nothing}) = ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, j1, j2) != C_NULL + +getreturntype(c::JConstructor) = voidClass + +function getMethod(class::Type, name::AbstractString, argTypes...) + jcall(classfortype(class), "getMethod", JMethod, (JString, Vector{JClass}), name, collect(JClass, argTypes)) +end + +function getConstructor(class::Type, argTypes...) + jcall(classfortype(class), "getConstructor", JConstructor, (Vector{JClass},), collect(argTypes)) +end + +methodInfo(class::String, name::String, argTypeNames::Array) = methodCache[(class, name, argTypeNames)] +function methodInfo(m::Union{JMethod, JConstructor}) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) cls = jcall(m, "getDeclaringClass", JClass, ()) methodKey = (getname(cls), name, getname.(argTypes)) get!(methodCache, methodKey) do - methodId = getmethodid(m, cls, name, returnType, argTypes) + methodId = getmethodid(isStatic(m), cls, name, returnType, argTypes) typeName = getname(returnType) info = get(typeInfo, typeName, genericFieldInfo) owner = metaclass(getname(cls)) - JMethodInfo(name, info, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) + id = length(methodCache) + methodsById[id] = JMethodInfo(id, name, info, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) end end -isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing - -isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 - -isClass(obj::JavaObject) = false - +isStatic(meth::JConstructor) = false function isStatic(meth::Union{JMethod,JField}) global modifiers @@ -163,52 +381,100 @@ end conv(func::Function, typ::String) = juliaConverters[typ] = func -macro typeInf(jclass, sig, jtyp) - _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false) +macro typeInf(jclass, sig, jtyp, jBoxType) + _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false, string(jclass) * "Value", "java.lang." * string(jBoxType)) end -macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object) +macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object, jBoxType) if typeof(jclass) == String jclass = Symbol(jclass) end - _typeInf(jclass, ctyp, sig, jtyp, Typ, object) + _typeInf(jclass, ctyp, sig, jtyp, Typ, object, "", "java.lang." * string(jBoxType)) end -function _typeInf(jclass, ctyp, sig, jtyp, Typ, object) +function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) - :(JavaTypeInfo(JavaObject{Symbol($(string(jclass)))}, $sig, $ctyp, $jtyp, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) - ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), - penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) - end) + quote + begin + JavaTypeInfo(JavaObject{Symbol($(string(jclass)))}, $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) + ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), + penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) + end + end + end end function initProxy() + global objectClass = classforname("java.lang.Object") + global classClass = classforname("java.lang.Class") + global voidClass = jfield(JavaObject{Symbol("java.lang.Void")}, "TYPE", JClass) conv("java.lang.String") do x; unsafe_string(x); end conv("java.lang.Integer") do x; JProxy(x).intValue(); end conv("java.lang.Long") do x; JProxy(x).longValue(); end global typeInfo = Dict([ - "int" => @typeInf(int, "I", Int32) - "long" => @typeInf(long, "J", Int64) - "byte" => @typeInf(byte, "B", Int8) - "boolean" => @typeInf(boolean, "Z", Bool) - "char" => @typeInf(char, "C", Char) - "short" => @typeInf(short, "S", Int16) - "float" => @typeInf(float, "F", Float32) - "double" => @typeInf(double, "D", Float64) - "void" => @vtypeInf(void, jint, "V", Nothing, Object, false) - "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true) + "void" => @vtypeInf(void, jint, "V", Nothing, Object, false, Void) + "boolean" => @typeInf(boolean, "Z", Bool, Boolean) + "byte" => @typeInf(byte, "B", Int8, Byte) + "char" => @typeInf(char, "C", Char, Character) + "short" => @typeInf(short, "S", Int16, Short) + "int" => @typeInf(int, "I", Int32, Integer) + "float" => @typeInf(float, "F", Float32, Float) + "long" => @typeInf(long, "J", Int64, Long) + "double" => @typeInf(double, "D", Float64, Double) + "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object) ]) - global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true) - global objectClass = classforname("java.lang.Object") + global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true, Object) + global methodId_object_getClass = getmethodid(false, objectClass, "getClass", classforname("java.lang.Class"), Vector{JClass}()) + global methodId_class_getName = getmethodid(false, classClass, "getName", classforname("java.lang.String"), Vector{JClass}()) + for info in (t->typeInfo[string(t)]).(:(int, long, byte, boolean, char, short, float, double).args) + infoName = string(javaType(info.class)) + boxVar = Symbol(infoName * "Box") + box = boxers[infoName] = Boxing(info) + expr = quote + $boxVar = boxers[$infoName] + function convert(::Type{JavaObject{T}}, obj::$(info.convertType)) where T + box(obj) + end + function box(data::$(info.convertType)) + _jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $(box.boxType), ($(box.info.juliaType),), data) + end + function unbox(obj::$(info.boxType)) + $(if box.info.convertType == Bool + :(_jcall(obj, $boxVar.unboxer, C_NULL, $(box.info.juliaType), ()) != 0) + else + :(_jcall(obj, $boxVar.unboxer, C_NULL, $(box.info.juliaType), ())) + end) + end + end + println("BOX METHOD FOR ", box.boxType) + println(expr) + eval(expr) + end end metaclass(class::AbstractString) = metaclass(Symbol(class)) -function getmethodid(meth::JMethod, cls::JClass, name::AbstractString, rettype::JClass, argtypes::Vector{JClass}) +function getclass(obj::Ptr{Nothing}) + result = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, methodId_object_getClass, Array{Int64,1}()) + result == C_NULL && geterror() + result +end + +function getclassname(class::Ptr{Nothing}) + result = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, class, methodId_class_getName, Array{Int64,1}()) + result == C_NULL && geterror() + unsafe_string(result) +end + +function getmethodid(static::Bool, cls::JClass, name::AbstractString, rettype::JClass, argtypes::Vector{JClass}) sig = proxyMethodSignature(rettype, argtypes) jclass = metaclass(getname(cls)) - result = ccall(isStatic(meth) ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + result = ccall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, jclass, name, sig) if result == C_NULL @@ -236,15 +502,20 @@ function proxyClassSignature(cls::AbstractString) sig != nothing ? sig : proxyClassSignature(classforname(cls)) end -function proxyClassSignature(cls::JavaObject) - sig = [] - while jcall(cls, "isArray", jboolean, ()) != 0 - push!(sig, "[") - cls = jcall(cls, "getComponentType", JClass, ()) +function proxyClassSignature(cls::JClass) + info = get(typeInfo, getname(cls), nothing) + if info != nothing && info.primitive + info.signature + else + sig = [] + while jcall(cls, "isArray", jboolean, ()) != 0 + push!(sig, "[") + cls = jcall(cls, "getComponentType", JClass, ()) + end + clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) + push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") + join(sig, "") end - clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) - push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") - join(sig, "") end function proxyMethodSignature(rettype, argtypes) @@ -264,114 +535,43 @@ function juliaTypeFor(name::AbstractString) info != nothing ? info.juliaType : JavaObject{Symbol(name)} end -infoFor(class::JClass) = haskey(classes, class) ? classes[class] : (classes[class] = JClassInfo(class)) +function infoFor(class::JClass) + name = getname(class) + haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class) +end + +getname(thing::Union{JClass, JMethod, JField}) = jcall(thing, "getName", JString, ()) +getname(thing::JConstructor) = "" -getname(field::JField) = jcall(field, "getName", JString, ()) +classfortype(t::Type{JavaObject{T}}) where T = classforname(string(T)) listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) listfields(cls::Type{JavaObject{C}}) where C = jcall(classforname(string(C)), "getFields", Vector{JField}, ()) fieldDict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) -function methodDict(class::JClass) - d = Dict() - for method in listmethods(class) - s = get!(()->Set(), d, Symbol(getname(method))) - push!(s, methodInfo(method)) - end - d -end - -fits(method::JMethodInfo, args::Tuple) = length(method.argTypes) == length(args) && all(canConvert.(method.argTypes, args)) - -canConvert(::Type{JavaObject{Symbol("java.lang.Object")}}, ::Union{AbstractString, Real}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Double")}}, ::Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Float")}}, ::Union{Float32, Float16, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Long")}}, ::Union{Int64, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Integer")}}, ::Union{Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Short")}}, ::Union{Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Byte")}}, ::Union{Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Character")}}, ::Union{Int8, Char}) = true -canConvert(::Type{JString}, ::AbstractString) = true -canConvert(::Type{<: Real}, ::T) where {T <: Real} = true -canConvert(::Type{jboolean}, ::Bool) = true -canConvert(::Type{jchar}, ::Char) = true -canConvert(::Type{<:Integer}, ::Ptr{Nothing}) = true -canConvert(x, y) = false -convert(::Type{JObject}, pxy::JProxy) = JavaObject(pxy) - -function (pxy::JMethodProxy)(args...) - targets = Set(m for m in pxy.methods if fits(m, args)) - if !isempty(targets) - # Find the most specific method - meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) - convertedArgs = convert.(meth.argTypes, args) - result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.typeInfo.juliaType, meth.argTypes, convertedArgs...) - if !isVoid(meth); asJulia(meth.typeInfo.convertType, result); end - end -end - -function filterStatic(pxy::JMethodProxy, targets) - static = pxy.static - Set(target for target in targets if target.static == static) -end - -convertPointers(typ, val) = isa(val, Ptr) ? convert(typ, val) : val +javaType(::JavaObject{T}) where T = T +javaType(::Type{JavaObject{T}}) where T = T +javaType(::JavaMetaClass{T}) where T = T isNull(obj::JavaObject) = isNull(obj.ptr) isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 -asJulia(t, obj) = obj -asJulia(::Type{Bool}, obj) = obj != 0 -function asJulia(x, obj::JavaObject) - if isNull(obj) - nothing - else - obj = narrow(obj) - (get(juliaConverters, classtypename(obj), JProxy))(obj) - end -end - -function asJulia(x, ptr::Ptr{Nothing}) - isNull(ptr) ? jnull : asJulia(x, JObject(ptr)) -end - -classtypename(obj::JavaObject{T}) where T = string(T) -classname(obj::JavaObject) = jcall(jcall(obj,"getClass", @jimport(java.lang.Class), ()), "getName", JString, ()) - -# Determine which method is more general using a fairly lame heuristic -function generality(p1::JMethodInfo, p2::JMethodInfo) - g = 0 - for i in 1:length(p1.argTypes) - c1, c2 = p1.argClasses[i], p2.argClasses[i] - g += generality(c1, c2) - generality(c2, c1) - end - g -end - -function generality(c1::JClass, c2::JClass) - p1, p2 = isPrimitive.((c1, c2)) - if !p1 && p2 || jcall(c1, "isAssignableFrom", jboolean, (@jimport(java.lang.Class),), c2) != 0 - 1 - else - 0 - end -end +superclass(obj::JavaObject) = jcall(obj, "getSuperclass", @jimport(java.lang.Class), ()) function Base.getproperty(p::JProxy, name::Symbol) obj = pxyObj(p) info = pxyInfo(p) - meths = get(info.methods, name, nothing) static = pxyStatic(p) - result = if meths != nothing - JMethodProxy(obj, meths, static) + result = if name in info.methods + JMethodProxy{name, types[javaType(obj)]}(obj, static) else field = info.fields[name] result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, static ? getclass(obj) : obj.ptr, field.id) result == C_NULL && geterror() - result = (field.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) + result = (field.info.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) asJulia(field.info.juliaType, result) end result != jnull && isa(result, JavaObject) ? JProxy(result) : result @@ -387,7 +587,7 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) else if isa(value, JProxy); value = JavaObject(value); end field = info.fields[name] - value = convert(field.primitive ? field.info.juliaType : field.info.class, value) + value = convert(field.info.primitive ? field.info.juliaType : field.info.class, value) result = field.info.setterFunc(field, obj, value) result == C_NULL && geterror() value @@ -400,6 +600,7 @@ function Base.show(io::IO, pxy::JProxy) print(io, "static class $(getname(JavaObject(pxy)))") else print(io, pxy.toString()) + #print(io, Java.toString(pxy)) end end diff --git a/src/reflect.jl b/src/reflect.jl index 8fd9f0c..d697975 100644 --- a/src/reflect.jl +++ b/src/reflect.jl @@ -135,7 +135,7 @@ Returns the parameter types of the java method ### Returns Vector the parametertypes """ -function getparametertypes(method::JMethod) +function getparametertypes(method::Union{JMethod,JConstructor}) jcall(method, "getParameterTypes", Vector{JClass}, ()) end From fed1446213c1f5adaad2fc853d309c739bcad1aa Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 23 Sep 2018 19:11:51 +0300 Subject: [PATCH 16/43] checkpoint --- src/JavaCall.jl | 1 + src/collections.jl | 20 ++ src/proxy.jl | 447 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 381 insertions(+), 87 deletions(-) create mode 100644 src/collections.jl diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 66e6deb..9b2c50c 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -28,6 +28,7 @@ include("core.jl") include("convert.jl") include("reflect.jl") include("proxy.jl") +include("collections.jl") function __init__() findjvm() diff --git a/src/collections.jl b/src/collections.jl new file mode 100644 index 0000000..8072b13 --- /dev/null +++ b/src/collections.jl @@ -0,0 +1,20 @@ +function Base.iterate(col::JProxy{<:java_util_AbstractCollection}) + i = col.iterator() + nextGetter(col, i)() +end +Base.iterate(col::JProxy{<:java_util_AbstractCollection}, state) = state() +Base.IteratorSize(::JProxy{<:java_util_AbstractCollection}) = Base.HasLength() +Base.length(col::JProxy{<:java_util_AbstractCollection}) = col.size() + +function nextGetter(col::JProxy{<:java_util_AbstractCollection}, iter) + let pending = true, value # memoize value + function () + if pending + pending = false + value = iter.hasNext() ? (iter.next(), nextGetter(col, iter)) : nothing + else + value + end + end + end +end diff --git a/src/proxy.jl b/src/proxy.jl index 30a2b45..b146e30 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -6,9 +6,12 @@ import Base.== const JField = JavaObject{Symbol("java.lang.reflect.Field")} -genericFieldInfo = nothing -objectClass = nothing +global genericFieldInfo +global objectClass +global sigTypes methodsById = Dict() +genned = Set() +const emptyset = Set() struct JavaTypeInfo setterFunc @@ -36,10 +39,12 @@ end struct JMethodInfo uid::Int64 - name::String + name::AbstractString typeInfo::JavaTypeInfo + returnType::Type # kluge this until we get generate typeInfo properly for new types + returnClass::JClass argTypes::Tuple - argClasses::Array{JavaObject} + argClasses::Array{JClass} id::Ptr{Nothing} static::Bool owner::JavaMetaClass @@ -53,24 +58,26 @@ struct JFieldInfo owner::JClass function JFieldInfo(field::JField) fcl = jcall(field, "getType", JClass, ()) - typ = juliaTypeFor(getname(fcl)) + typ = juliaTypeFor(legalClassName(fcl)) static = isStatic(field) cls = jcall(field, "getDeclaringClass", JClass, ()) - id = fieldId(getname(field), JavaObject{Symbol(getname(fcl))}, static, field, cls) - info = get(typeInfo, getname(fcl), genericFieldInfo) + id = fieldId(getname(field), JavaObject{Symbol(legalClassName(fcl))}, static, field, cls) + info = get(typeInfo, legalClassName(fcl), genericFieldInfo) new(field, info, static, id, cls) end end struct JMethodProxy{N, T} obj + methods::Set static::Bool end struct JClassInfo + parent::Union{Nothing, JClassInfo} class::JClass fields::Dict{Symbol, JFieldInfo} - methods::Set{Symbol} + methods::Dict{Symbol, Set{JMethodInfo}} classType::Type end @@ -95,14 +102,15 @@ isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing juliaConverters = Dict() classtypename(obj::JavaObject{T}) where T = string(T) -abstract type java_lang end +abstract type java_lang_Object end +abstract type java_util_AbstractCollection <: java_lang_Object end #types = Dict("java.lang.Object" => java_lang_Object) -types = Dict() - -typeNameFor(className::String) = Symbol(replace(replace(className, "_" => "__"), "." => "_")) - -typeNameString(className::String) = string(typeNameFor(className)) +types = Dict([ + Symbol("java.lang.Object") => java_lang_Object, + Symbol("java.util.AbstractCollection") => java_util_AbstractCollection, + Symbol("java.lang.String") => String, +]) """ JProxy(s::AbstractString) @@ -157,25 +165,50 @@ julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") hello ``` """ -struct JProxy{C <: java_lang} +struct JProxy{T<:Union{<:java_lang_Object, Array{<:java_lang_Object}}} obj::JavaObject info::JClassInfo static::Bool - JProxy(s::AbstractString) = JProxy(JString(s)) - JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}; static=true) - function JProxy(obj::JavaObject) where C + JProxy(s::AbstractString; deep=false) = JProxy(JString(s), deep=deep) + JProxy(::JavaMetaClass{C}; deep=false) where C = JProxy(JavaObject{C}, static=true, deep=deep) + function JProxy(obj::JavaObject; deep=false) where C obj = narrow(obj) - info = infoFor(isNull(obj) ? objectClass : getclass(obj)) - new{types[javaType(obj)]}(isNull(obj) ? obj : narrow(obj), info, false) + aType, dim = arrayInfo(string(javaType(obj))) + if dim != 0 + t = typeFor(Symbol(aType)) + info = infoFor(objectClass, deep=deep) + new{Array{typeFor(Symbol(aType)), length(dims)}}(JObject(obj.ptr), info, false) + else + info = infoFor(isNull(obj) ? objectClass : getclass(obj), deep=deep) + new{types[javaType(obj)]}(obj, info, false) + end end - function JProxy(::Type{JavaObject{C}}; static::Bool=false) where C + function JProxy(::Type{JavaObject{C}}; static=false, deep=false) where C obj = classforname(string(C)) - info = infoFor(obj) - new{types[C]}(obj, info, true) + info = infoFor(obj, deep=deep) + new{typeFor(C)}(obj, info, true) + end +end + +struct GenInfo + code + typeCode + deps + classList + methodSets + fielddicts + GenInfo() = new([], [], Set(), [], Dict(), Dict()) +end + +function arrayInfo(str) + if (m = match(r"^(\[+)L(.*);", str)) != nothing + m.captures[2], length(m.captures[1]) + else + nothing, 0 end end -const JLegalArg = Union{Number, String, JProxy} +const JLegalArg = Union{Number, String, JProxy, Array{Number}, Array{String}, Array{JProxy}} const JPrimitive = Union{Bool, Char, Int8, Int16, Int32, Int64, Float32, Float64} const JNumber = Union{Int8, Int16, Int32, Int64, Float32, Float64} const JBoxTypes = Union{ @@ -204,27 +237,86 @@ methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} typeInfo = Dict() -struct GenInfo - code - typeCode - deps - classList - methodSets - GenInfo() = new([], [], Set(), [], Dict()) +gettype(class::Symbol) = get(types, class, java_lang_Object) + +gettypeinfo(class::Symbol) = gettypeinfo(string(class)) +gettypeinfo(class::AbstractString) = get(typeInfo, class, genericFieldInfo) + +hasClass(name::AbstractString) = hasClass(Symbol(name)) +hasClass(name::Symbol) = name in genned +hasClass(gen, name::AbstractString) = hasClass(gen, Symbol(name)) +hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodSets, string(name)) + +function makeType(name::AbstractString, supername::Symbol, gen) + if !haskey(types, Symbol(name)) && !haskey(gen.methodSets, name) + typeName = typeNameFor(name) + push!(gen.typeCode, + :(abstract type $typeName <: $supername end), +# :(types[Symbol($name)] = $typeName) + ) + end +end + +function gen(name::AbstractString, classType::Type) + gen(Symbol(name), classType) +end +function gen(name::Symbol, classType::Type) + if !(name in genned) + types[name] = classType + gen(classforname(string(name))) + end +end +function gen(class::JClass; deep=false) + gen = GenInfo() + genClass(class, gen) + if deep + while !isempty(gen.deps) + cls = pop!(gen.deps) + !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) + end + else + while !isempty(gen.deps) + cls = pop!(gen.deps) + !hasClass(gen, cls) && genType(classforname(string(cls)), gen) + end + end + #println("\nEVALUATING...\n\n") + expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) + println(expr) + eval(expr) + for cl in gen.classList + n = legalClassName(cl) + classes[n] = JClassInfo(cl, gen.fielddicts[n], gen.methodSets[n], types[Symbol(n)]) + end end -hasClass(gen, name::Symbol) = haskey(types, name) || haskey(gen.methodSets, string(name)) +function genType(class, gen::GenInfo) + name = getname(class) + sc = superclass(class) + push!(genned, Symbol(legalClassName(class))) + if !isNull(sc) + supertype = typeNameFor(sc) + cType = componentType(supertype) + makeType(name, cType, gen) + else + makeType(name, :java_lang_Object, gen) + end +end function genClass(class, gen::GenInfo) + gen.fielddicts[legalClassName(class)] = fielddict(class) push!(gen.classList, class) name = getname(class) sc = superclass(class) + #println("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") + push!(genned, Symbol(legalClassName(class))) if !isNull(sc) - supername = getname(sc) - !hasClass(gen, Symbol(supername)) && genClass(sc, gen) - push!(gen.typeCode, :(abstract type $(typeNameFor(name)) <: $(typeNameFor(supername)) end)) + supertype = typeNameFor(sc) + cType = componentType(supertype) + !hasClass(gen, cType) && genClass(sc, gen) + makeType(name, cType, gen) else - push!(gen.typeCode, :(abstract type $(typeNameFor(name)) <: java_lang end)) + makeType(name, :java_lang_Object, gen) end genMethods(class, gen) end @@ -243,10 +335,56 @@ argType(t, gen) = t argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = JLegalArg argType(::Type{<: Number}, gen) = Number -function argType(::Type{JavaObject{T}}, gen) where T - cl = findfirst("[", string(T)) != nothing ? Symbol("java.lang.Object") : T - !hasClass(gen, cl) && push!(gen.deps, cl) - :(JProxy{<:$(typeNameFor(string(cl)))}) +argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen))}) + +legalClassName(cls::JavaObject) = legalClassName(getname(cls)) +legalClassName(cls::Symbol) = legalClassName(string(cls)) +function legalClassName(name::AbstractString) + if (m = match(r"((\[])+)$", name)) != nothing + dimensions = Integer(length(m.captures[1]) / 2) + "$(repeat('[', dimensions))L$(name[1:end-dimensions * 2]);" + else + name + end +end + +componentType(e::Expr) = e.args[2] +componentType(sym::Symbol) = sym + +typeNameFor(class::JClass) = typeNameFor(legalClassName(class)) +typeNameFor(className::Symbol) = typeNameFor(string(className)) +function typeNameFor(className::AbstractString) + if className == "java.lang.String" + String + elseif length(className) == 1 + sigTypes[className].convertType + else + n = replace(className, "_" => "___") + n = replace(className, "\$" => "_s_") + n = replace(n, "." => "_") + aType, dims = arrayInfo(n) + if dims != 0 + :(Array{$(typeNameFor(aType)), $(length(dims))}) + else + t = get(typeInfo, n, genericFieldInfo) + if t.primitive + t.juliaType + else + Symbol(n) + end + end + end +end +typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) +function typeNameFor(T::AbstractString, gen::GenInfo) + aType, dims = arrayInfo(T) + c = dims != 0 ? aType : T + csym = Symbol(c) + if (dims == 0 || length(c) > 1) && !(csym in gen.deps) && !hasClass(gen, csym) && !get(typeInfo, c, genericFieldInfo).primitive + #println("GEN CLASS: $c") + push!(gen.deps, csym) + end + typeNameFor(T) end function argCode(arg::GenArgInfo) @@ -262,21 +400,55 @@ function argCode(arg::GenArgInfo) end end +function fieldEntry((name, fld)) + fieldType = JavaObject{Symbol(legalClassName(fld.owner))} + name => :(jfield($(fld.info.class), $(string(name)), $fieldType)) +end + function genMethods(class, gen) - gen.methodSets[getname(class)] = methods = Set() - for method in listmethods(class) - name = getname(method) - push!(methods, Symbol(name)) + #push!(genned, Symbol(typeNameFor(legalClassName(class)))) + methodList = listmethods(class) + classname = legalClassName(class) + gen.methodSets[classname] = methods = Set() + typeName = typeNameFor(classname, gen) + classVar = Symbol("class_" * string(typeName)) + fieldsVar = Symbol("fields_" * string(typeName)) + methodsVar = Symbol("staticMethods_" * string(typeName)) + push!(gen.code, :($classVar = classforname($classname))) + push!(gen.code, :($fieldsVar = Dict([$([fieldEntry(f) for f in gen.fielddicts[classname]]...)]))) + push!(gen.code, :($methodsVar = Set($([getname(m) for m in methodList if isStatic(m)])))) + push!(gen.code, :(function Base.getproperty(p::JProxy{<: $typeName}, name::Symbol) + if (f = get($fieldsVar, name, nothing)) != nothing + getField(p, name, f) + else + JMethodProxy{name, $typeName}(pxyObj(p), emptyset, name in $methodsVar) + end + end)) + for method in methodList + name = Symbol(getname(method)) + push!(methods, name) info = methodInfo(method) - owner = string(javaType(info.owner)) + owner = javaType(info.owner) if isSame(class.ptr, info.owner.ptr) args = (GenArgInfo(i, info, gen) for i in 1:length(info.argTypes)) argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) - push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $(typeNameFor(owner))})($(argDecs...)) - $(genConvertResult(info.typeInfo.convertType, info, :(_jcall(getfield(pxy, :obj), methodsById[$(info.uid)].id, C_NULL, $(info.typeInfo.juliaType), ($(info.argTypes...),), $((argCode(arg) for arg in args)...))))) + methodIdName = Symbol("method_" * string(typeName) * "__" * string(name)) + push!(gen.code, :($methodIdName = getmethodid($(isStatic(method)), $classVar, $(string(name)), $(legalClassName(info.returnClass)), $(legalClassName.(info.argClasses))))) + push!(gen.code, :(function (pxy::JMethodProxy{Symbol($(string(name))), <: $typeName})($(argDecs...))::$(genReturnType(info, gen)) + $(genConvertResult(info.typeInfo.convertType, info, :(_jcall(getfield(pxy, :obj), $methodIdName, C_NULL, $(info.typeInfo.juliaType), ($(info.argTypes...),), $((argCode(arg) for arg in args)...))))) end)) end end + push!(gen.code, :(push!(genned, Symbol($(legalClassName(class)))))) +end + +function genReturnType(methodInfo, gen) + t = methodInfo.typeInfo.convertType + if methodInfo.typeInfo.primitive || t <: String || t == Nothing + t + else + :(JProxy{<:$(typeNameFor(javaType(methodInfo.returnType), gen))}) + end end genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) @@ -290,28 +462,87 @@ function genConvertResult(toType, info, expr) end end -function JClassInfo(class::JClass) - gen = GenInfo() - genClass(class, gen) - while !isempty(gen.deps) - cls = iterate(gen.deps)[1] - delete!(gen.deps, cls) - !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) - end - for cl in gen.classList - name = getname(cl) - push!(gen.typeCode, :(types[Symbol($name)] = $(typeNameFor(name)))) - end - println("\nEVALUATING...\n\n") - expr = :(begin $(gen.typeCode...); $(gen.code...); end) - println(expr) - eval(expr) - println("DONE EVALUATING") - for cl in gen.classList - n = getname(cl) - classes[n] = JClassInfo(cl, fieldDict(cl), gen.methodSets[n], types[Symbol(n)]) +isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 + +#function JClassInfo(class::JClass; deep=false) +# if isArray(class) +# infoFor(objectClass) +# else +# gen = GenInfo() +# genClass(class, gen) +# if deep +# while !isempty(gen.deps) +# cls = pop!(gen.deps) +# !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) +# end +# else +# while !isempty(gen.deps) +# cls = pop!(gen.deps) +# !hasClass(gen, cls) && genType(classforname(string(cls)), gen) +# end +# end +# #println("\nEVALUATING...\n\n") +# expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) +# println(expr) +# eval(expr) +# #for e in expr.args +# # println(e) +# # eval(e) +# #end +# #println("DONE EVALUATING") +# for cl in gen.classList +# n = legalClassName(cl) +# classes[n] = JClassInfo(cl, gen.fielddicts[n], gen.methodSets[n], types[Symbol(n)]) +# end +# classes[legalClassName(class)] +# end +#end + +function JClassInfo(class::JClass; gen=false, deep=false) + if isArray(class) + infoFor(objectClass) + else + n = legalClassName(class) + sc = superclass(class) + info = classes[n] = JClassInfo(isNull(sc) ? nothing : infoFor(sc), class, fielddict(class), methoddict(class), gettype(Symbol(n))) + if gen + genInfo = GenInfo() + genClass(class, genInfo) + if deep + while !isempty(genInfo.deps) + cls = pop!(genInfo.deps) + !hasClass(genInfo, cls) && genClass(classforname(string(cls)), genInfo) + end + else + while !isempty(genInfo.deps) + cls = pop!(genInfo.deps) + !hasClass(genInfo, cls) && genType(classforname(string(cls)), genInfo) + end + end + #println("\nEVALUATING...\n\n") + expr = :(begin $(genInfo.typeCode...); $(genInfo.code...); $(genClasses(getname.(genInfo.classList))...); end) + for e in expr.args + println(e) + # eval(e) + end + #println(expr) + eval(expr) + #println("DONE EVALUATING") + for cl in genInfo.classList + n = legalClassName(cl) + classes[n] = JClassInfo(cl, genInfo.fielddicts[n], genInfo.methodSets[n], types[Symbol(n)]) + end + classes[legalClassName(class)] + end + info end - classes[getname(class)] +end + +genClasses(classNames) = (:(gen($name, $(Symbol(typeNameFor(name))))) for name in classNames) + +function typeFor(sym::Symbol) + aType, dims = arrayInfo(string(sym)) + dims != 0 ? Array{types[Symbol(aType)], length(dims)} : types[sym] end asJulia(t, obj) = obj @@ -332,8 +563,10 @@ function asJulia(x, ptr::Ptr{Nothing}) end end -box(str::String) = str +box(str::AbstractString) = str box(pxy::JProxy) = pxyObj(pxy) +#function box(array::Array{T,N}) +#end unbox(obj) = obj pxyObj(p::JProxy) = getfield(p, :obj) @@ -356,18 +589,18 @@ function getConstructor(class::Type, argTypes...) jcall(classfortype(class), "getConstructor", JConstructor, (Vector{JClass},), collect(argTypes)) end -methodInfo(class::String, name::String, argTypeNames::Array) = methodCache[(class, name, argTypeNames)] +methodInfo(class::AbstractString, name::AbstractString, argTypeNames::Array) = methodCache[(class, name, argTypeNames)] function methodInfo(m::Union{JMethod, JConstructor}) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) cls = jcall(m, "getDeclaringClass", JClass, ()) - methodKey = (getname(cls), name, getname.(argTypes)) + methodKey = (legalClassName(cls), name, legalClassName.(argTypes)) get!(methodCache, methodKey) do methodId = getmethodid(isStatic(m), cls, name, returnType, argTypes) - typeName = getname(returnType) + typeName = legalClassName(returnType) info = get(typeInfo, typeName, genericFieldInfo) - owner = metaclass(getname(cls)) + owner = metaclass(legalClassName(cls)) id = length(methodCache) - methodsById[id] = JMethodInfo(id, name, info, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) + methodsById[id] = JMethodInfo(id, name, info, juliaTypeFor(returnType), returnType, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) end end @@ -379,7 +612,7 @@ function isStatic(meth::Union{JMethod,JField}) jcall(modifiers, "isStatic", jboolean, (jint,), mods) != 0 end -conv(func::Function, typ::String) = juliaConverters[typ] = func +conv(func::Function, typ::AbstractString) = juliaConverters[typ] = func macro typeInf(jclass, sig, jtyp, jBoxType) _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false, string(jclass) * "Value", "java.lang." * string(jBoxType)) @@ -424,6 +657,8 @@ function initProxy() "double" => @typeInf(double, "D", Float64, Double) "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object) ]) + classes["java.lang.String"] = JClassInfo(nothing, classforname("java.lang.String"), Dict(), Dict(), types[Symbol("java.lang.String")]) + global sigTypes = Dict([inf.signature => inf for (key, inf) in typeInfo if inf.primitive]) global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true, Object) global methodId_object_getClass = getmethodid(false, objectClass, "getClass", classforname("java.lang.Class"), Vector{JClass}()) global methodId_class_getName = getmethodid(false, classClass, "getName", classforname("java.lang.String"), Vector{JClass}()) @@ -447,8 +682,8 @@ function initProxy() end) end end - println("BOX METHOD FOR ", box.boxType) - println(expr) + #println("BOX METHOD FOR ", box.boxType) + #println(expr) eval(expr) end end @@ -471,9 +706,12 @@ function getclassname(class::Ptr{Nothing}) unsafe_string(result) end +function getmethodid(static::Bool, cls::JClass, name::AbstractString, rettype::AbstractString, argtypes::Vector{<:AbstractString}) + getmethodid(static, cls, name, classforlegalname(rettype), collect(JClass, classforlegalname.(argtypes))) +end function getmethodid(static::Bool, cls::JClass, name::AbstractString, rettype::JClass, argtypes::Vector{JClass}) sig = proxyMethodSignature(rettype, argtypes) - jclass = metaclass(getname(cls)) + jclass = metaclass(legalClassName(cls)) result = ccall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, jclass, name, sig) @@ -487,7 +725,7 @@ end function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - penv, metaclass(getname(cls)), name, proxyClassSignature(string(C))) + penv, metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) id == C_NULL && geterror(true) id end @@ -529,26 +767,40 @@ function proxyMethodSignature(rettype, argtypes) String(take!(s)) end -juliaTypeFor(class::JavaObject) = juliaTypeFor(getname(class)) +juliaTypeFor(class::JavaObject) = juliaTypeFor(legalClassName(class)) function juliaTypeFor(name::AbstractString) info = get(typeInfo, name, nothing) info != nothing ? info.juliaType : JavaObject{Symbol(name)} end -function infoFor(class::JClass) - name = getname(class) - haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class) +function infoFor(class::JClass; deep=false) + name = legalClassName(class) + haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class, deep=deep) end getname(thing::Union{JClass, JMethod, JField}) = jcall(thing, "getName", JString, ()) getname(thing::JConstructor) = "" +classforlegalname(n::AbstractString) = (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) + classfortype(t::Type{JavaObject{T}}) where T = classforname(string(T)) +listfields(cls::AbstractString) = listfields(classforname(cls)) +listfields(cls::Type{JavaObject{C}}) where C = listfields(classforname(string(C))) listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) -listfields(cls::Type{JavaObject{C}}) where C = jcall(classforname(string(C)), "getFields", Vector{JField}, ()) -fieldDict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) +fielddict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) + +function methoddict(class) + d = Dict() + for method in listmethods(class) + s = get!(d, Symbol(getname(method))) do + Set() + end + push!(s, methodInfo(method)) + end + d +end javaType(::JavaObject{T}) where T = T javaType(::Type{JavaObject{T}}) where T = T @@ -559,12 +811,22 @@ isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 superclass(obj::JavaObject) = jcall(obj, "getSuperclass", @jimport(java.lang.Class), ()) +function getField(p::JProxy, name::Symbol, field::JFieldInfo) + result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), + penv, pxyStatic(p) ? getclass(obj) : pxyObj(p).ptr, field.id) + result == C_NULL && geterror() + result = (field.info.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) + asJulia(field.info.juliaType, result) +end + function Base.getproperty(p::JProxy, name::Symbol) obj = pxyObj(p) info = pxyInfo(p) static = pxyStatic(p) result = if name in info.methods - JMethodProxy{name, types[javaType(obj)]}(obj, static) + println("MAKING METHOD PROXY") + JMethodProxy{name, gettype(javaType(obj))}(obj, info.methods[name], static) else field = info.fields[name] result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, @@ -595,9 +857,20 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) isa(result, JavaObject) ? JProxy(result) : result end +function (pxy::JMethodProxy)(args...) + targets = Set(m for m in pxy.methods if fits(m, args)) + if !isempty(targets) + # Find the most specific method + meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) + convertedArgs = convert.(meth.argTypes, args) + result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.typeInfo.juliaType, meth.argTypes, convertedArgs...) + if !isVoid(meth); asJulia(meth.typeInfo.convertType, result); end + end +end + function Base.show(io::IO, pxy::JProxy) if pxyStatic(pxy) - print(io, "static class $(getname(JavaObject(pxy)))") + print(io, "static class $(legalClassName(JavaObject(pxy)))") else print(io, pxy.toString()) #print(io, Java.toString(pxy)) From f1ea8af4ad223a205fb621084afc35ba865cfeb0 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 13 Oct 2018 13:55:31 +0300 Subject: [PATCH 17/43] checkpoint --- src/collections.jl | 4 + src/core.jl | 9 +- src/proxy.jl | 1138 +++++++++++++++++++++++++++++--------------- 3 files changed, 756 insertions(+), 395 deletions(-) diff --git a/src/collections.jl b/src/collections.jl index 8072b13..487b833 100644 --- a/src/collections.jl +++ b/src/collections.jl @@ -1,3 +1,7 @@ +function Base.iterate(col::JProxy{<:java_lang}) + cl = getclass(pxyObj(col)) + info = infoFor(cl) +end function Base.iterate(col::JProxy{<:java_util_AbstractCollection}) i = col.iterator() nextGetter(col, i)() diff --git a/src/core.jl b/src/core.jl index 21f8bc3..11065ea 100644 --- a/src/core.jl +++ b/src/core.jl @@ -29,7 +29,7 @@ mutable struct JavaObject{T} #This below is ugly. Once we stop supporting 0.5, this can be replaced by # function JavaObject{T}(ptr) where T function JavaObject{T}(ptr) where T - j = new{T}(ptr) + j = new{T}(newglobalref(ptr)) finalizer(deleteref, j) return j end @@ -40,13 +40,18 @@ mutable struct JavaObject{T} JavaObject(::Nothing) = new{Symbol("java.lang.Object")}(C_NULL) end +newglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + +deleteglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + JavaObject(T, ptr) = JavaObject{T}(ptr) function deleteref(x::JavaObject) if x.ptr == C_NULL; return; end if (penv==C_NULL); return; end #ccall(:jl_,Nothing,(Any,),x) - ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, x.ptr) + #ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, x.ptr) + deleteglobalref(x.ptr) x.ptr=C_NULL #Safety in case this function is called direcly, rather than at finalize return end diff --git a/src/proxy.jl b/src/proxy.jl index b146e30..d51cfc8 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -1,21 +1,73 @@ -# TODO: box incoming primitives that are sent to object args, including Strings +# See documentation for JProxy for infomation import Base.== -# See documentation for JProxy for infomation +abstract type java_lang end -const JField = JavaObject{Symbol("java.lang.reflect.Field")} +classnamefor(t::Type{<:java_lang}) = classnamefor(nameof(t)) +classnamefor(s::Symbol) = classnamefor(string(s)) +function classnamefor(s::AbstractString) + s = replace(s, "___" => "_") + s = replace(s, "_s_" => "\$") + replace(s, "_" => ".") +end -global genericFieldInfo -global objectClass -global sigTypes -methodsById = Dict() -genned = Set() -const emptyset = Set() +function _defjtype(a, b) + symA = Symbol(a) + eval(quote + abstract type $symA <: $b end + types[Symbol($(classnamefor(a)))] = $symA + end) +end + +macro defjtype(expr) + :(_defjtype($(string(expr.args[1])), $(expr.args[2]))) +end + +const types = Dict() + +@defjtype java_lang_Object <: java_lang +@defjtype java_util_AbstractCollection <: java_lang_Object +@defjtype java_lang_Number <: java_lang_Object +@defjtype java_lang_Double <: java_lang_Number +@defjtype java_lang_Float <: java_lang_Number +@defjtype java_lang_Long <: java_lang_Number +@defjtype java_lang_Integer <: java_lang_Number +@defjtype java_lang_Short <: java_lang_Number +@defjtype java_lang_Byte <: java_lang_Number +@defjtype java_lang_Character <: java_lang_Object +@defjtype java_lang_Boolean <: java_lang_Object + +# types +const modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} +const JField = JavaObject{Symbol("java.lang.reflect.Field")} +const JPrimitive = Union{Bool, Char, UInt8, Int8, UInt16, Int16, Int32, Int64, Float32, Float64} +const JNumber = Union{Int8, Int16, Int32, Int64, Float32, Float64} +const JBoxTypes = Union{ + java_lang_Double, + java_lang_Float, + java_lang_Long, + java_lang_Integer, + java_lang_Short, + java_lang_Byte, + java_lang_Character, + java_lang_Boolean +} +const JBoxed = Union{ + JavaObject{Symbol("java.lang.Boolean")}, + JavaObject{Symbol("java.lang.Byte")}, + JavaObject{Symbol("java.lang.Character")}, + JavaObject{Symbol("java.lang.Short")}, + JavaObject{Symbol("java.lang.Integer")}, + JavaObject{Symbol("java.lang.Long")}, + JavaObject{Symbol("java.lang.Float")}, + JavaObject{Symbol("java.lang.Double")} +} struct JavaTypeInfo setterFunc - class::Type{JavaObject{T}} where T # narrowed JavaObject type + #class::Type{JavaObject{T}} where T # narrowed JavaObject type + classname::Symbol # legal classname as a symbol signature::AbstractString juliaType::Type # the Julia representation of the Java type, like jboolean (which is a UInt8), for call-in convertType::Type # the Julia type to convert results to, like Bool or String @@ -28,59 +80,56 @@ struct JavaTypeInfo staticGetter::Ptr{Nothing} setter::Ptr{Nothing} staticSetter::Ptr{Nothing} - function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) - boxClass = classfortype(boxType) - primitive = length(signature) == 1 - primClass = primitive ? jfield(boxType, "TYPE", JClass) : objectClass - info = new(setterFunc, class, signature, juliaType, convertType, primitive, accessorName, boxType, boxClass, primClass, getter, staticGetter, setter, staticSetter) - info - end end -struct JMethodInfo - uid::Int64 - name::AbstractString - typeInfo::JavaTypeInfo - returnType::Type # kluge this until we get generate typeInfo properly for new types - returnClass::JClass - argTypes::Tuple - argClasses::Array{JClass} - id::Ptr{Nothing} - static::Bool - owner::JavaMetaClass +struct JReadonlyField + get +end + +struct JReadWriteField + get + set end struct JFieldInfo field::JField - info::JavaTypeInfo + typeInfo::JavaTypeInfo static::Bool id::Ptr{Nothing} owner::JClass - function JFieldInfo(field::JField) - fcl = jcall(field, "getType", JClass, ()) - typ = juliaTypeFor(legalClassName(fcl)) - static = isStatic(field) - cls = jcall(field, "getDeclaringClass", JClass, ()) - id = fieldId(getname(field), JavaObject{Symbol(legalClassName(fcl))}, static, field, cls) - info = get(typeInfo, legalClassName(fcl), genericFieldInfo) - new(field, info, static, id, cls) - end end -struct JMethodProxy{N, T} - obj - methods::Set +struct JMethodInfo + name::AbstractString + typeInfo::JavaTypeInfo + returnType::Symbol # kluge this until we get generate typeInfo properly for new types + returnClass::JClass + argTypes::Tuple + argClasses::Array{JClass} + id::Ptr{Nothing} static::Bool + owner::JavaMetaClass + dynArgTypes::Tuple end struct JClassInfo parent::Union{Nothing, JClassInfo} class::JClass - fields::Dict{Symbol, JFieldInfo} + fields::Dict{Symbol, Union{JFieldInfo, JReadonlyField}} methods::Dict{Symbol, Set{JMethodInfo}} classType::Type end +struct JMethodProxy{N, T} + pxy # hold onto this so long-held method proxies don't have dead ptr references + obj::Ptr{Nothing} + methods::Set + static::Bool + function JMethodProxy(N::Symbol, T::Type, pxy, methods) + new{N, T}(pxy, pxyptr(pxy), methods, pxystatic(pxy)) + end +end + struct Boxing info::JavaTypeInfo boxType::Type @@ -88,30 +137,8 @@ struct Boxing primClass::JClass boxer::Ptr{Nothing} unboxer::Ptr{Nothing} - function Boxing(info) - boxer = methodInfo(getConstructor(info.boxType, info.primClass)).id - unboxer = methodInfo(getMethod(info.boxType, info.accessorName)).id - new(info, info.boxType, info.boxClass, info.primClass, boxer, unboxer) - end end -boxers = Dict() - -isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing - -juliaConverters = Dict() -classtypename(obj::JavaObject{T}) where T = string(T) - -abstract type java_lang_Object end -abstract type java_util_AbstractCollection <: java_lang_Object end - -#types = Dict("java.lang.Object" => java_lang_Object) -types = Dict([ - Symbol("java.lang.Object") => java_lang_Object, - Symbol("java.util.AbstractCollection") => java_util_AbstractCollection, - Symbol("java.lang.String") => String, -]) - """ JProxy(s::AbstractString) JProxy(::JavaMetaClass) @@ -165,28 +192,13 @@ julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") hello ``` """ -struct JProxy{T<:Union{<:java_lang_Object, Array{<:java_lang_Object}}} - obj::JavaObject +# mutable because it can have a finalizer +mutable struct JProxy{T<:Union{<:java_lang, Array{<:java_lang}, <:AbstractString, <:Number}, C} + ptr::Ptr{Nothing} info::JClassInfo static::Bool - JProxy(s::AbstractString; deep=false) = JProxy(JString(s), deep=deep) - JProxy(::JavaMetaClass{C}; deep=false) where C = JProxy(JavaObject{C}, static=true, deep=deep) - function JProxy(obj::JavaObject; deep=false) where C - obj = narrow(obj) - aType, dim = arrayInfo(string(javaType(obj))) - if dim != 0 - t = typeFor(Symbol(aType)) - info = infoFor(objectClass, deep=deep) - new{Array{typeFor(Symbol(aType)), length(dims)}}(JObject(obj.ptr), info, false) - else - info = infoFor(isNull(obj) ? objectClass : getclass(obj), deep=deep) - new{types[javaType(obj)]}(obj, info, false) - end - end - function JProxy(::Type{JavaObject{C}}; static=false, deep=false) where C - obj = classforname(string(C)) - info = infoFor(obj, deep=deep) - new{typeFor(C)}(obj, info, true) + function JProxy{T, C}(ptr::Ptr{Nothing}, info, static) where {T, C} + finalizer(finalizeproxy, new{T, C}(globalref(ptr), info, static)) end end @@ -195,49 +207,202 @@ struct GenInfo typeCode deps classList - methodSets + methodDicts fielddicts - GenInfo() = new([], [], Set(), [], Dict(), Dict()) end -function arrayInfo(str) - if (m = match(r"^(\[+)L(.*);", str)) != nothing +struct GenArgInfo + name::Symbol + javaType::Type + #juliaType::Union{Type,Expr} + juliaType + spec +end + +const JLegalArg = Union{Number, String, JProxy, Array{Number}, Array{String}, Array{JProxy}} + +const methodsById = Dict() +const genned = Set() +const emptyset = Set() +const classes = Dict() +const methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() +const typeInfo = Dict{AbstractString, JavaTypeInfo}() +const boxers = Dict() +const juliaConverters = Dict() +global jnicalls = Dict() +const defaultjnicall = (instance=:CallObjectMethod,static=:CallStaticObjectMethod) + +#function classnamefor(t::Type{<:java_lang}) +# s = replace(string(nameof(t)), "___" => "_") +# s = replace(s, "_s_" => "\$") +# Symbol(replace(s, "_" => ".")) +#end +# +#const types = Dict([classnamefor(t) => t for t in [ +# java_lang_Object +# java_util_AbstractCollection +# java_lang_Number +# java_lang_Double +# java_lang_Float +# java_lang_Long +# java_lang_Integer +# java_lang_Short +# java_lang_Byte +# java_lang_Character +# java_lang_Boolean +#]]) + +#const types = Dict([ +# Symbol("java.lang.Object") => java_lang_Object, +# Symbol("java.util.AbstractCollection") => java_util_AbstractCollection, +# Symbol("java.lang.String") => String, +#]) +const dynamicTypeCache = Dict() + +global genericFieldInfo +global objectClass +global sigTypes + +macro jnicall(func, rettype, types, args...) + quote + result = ccall($(esc(func)), $(esc(rettype)), + (Ptr{JNIEnv}, $(esc.(types.args)...)), + penv, $(esc.(args)...)) + result == C_NULL && geterror() + result + end +end + +macro message(obj, rettype, methodid, args...) + func = get(jnicalls, rettype, defaultjnicall).instance + #println("INSTANCE FUNC: ", func) + flush(stdout) + quote + result = ccall(jnifunc.$func, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), + penv, $(esc(obj)), $(esc(methodid)), $(esc.(args)...)) + result == C_NULL && geterror() + result + end +end + +macro staticmessage(rettype, methodid, args...) + func = get(jnicalls, rettype, defaultjnicall).static + #println("STATIC FUNC: ", func) + flush(stdout) + expr = quote + result = ccall(jnifunc.$func, $(esc(rettype)), + (Ptr{JNIEnv}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), + penv, $(esc(methodid)), $(esc.(args)...)) + result == C_NULL && geterror() + result + end + #println("STATIC CALL: ", expr) + #if func == defaultjnicall.static && true + # expr + #else + # :(objectClass) + #end +end + +globalref(ptr::Ptr{Nothing}) = @jnicall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{Nothing},), ptr) + +function arrayinfo(str) + if (m = match(r"^(\[+)(.)$", str)) != nothing + signatureClassFor(m.captures[2]), length(m.captures[1]) + elseif (m = match(r"^(\[+)L(.*);", str)) != nothing m.captures[2], length(m.captures[1]) else nothing, 0 end end -const JLegalArg = Union{Number, String, JProxy, Array{Number}, Array{String}, Array{JProxy}} -const JPrimitive = Union{Bool, Char, Int8, Int16, Int32, Int64, Float32, Float64} -const JNumber = Union{Int8, Int16, Int32, Int64, Float32, Float64} -const JBoxTypes = Union{ - Type{JavaObject{Symbol("java.lang.Boolean")}}, - Type{JavaObject{Symbol("java.lang.Byte")}}, - Type{JavaObject{Symbol("java.lang.Character")}}, - Type{JavaObject{Symbol("java.lang.Short")}}, - Type{JavaObject{Symbol("java.lang.Integer")}}, - Type{JavaObject{Symbol("java.lang.Long")}}, - Type{JavaObject{Symbol("java.lang.Float")}}, - Type{JavaObject{Symbol("java.lang.Double")}} -} -const JBoxed = Union{ - JavaObject{Symbol("java.lang.Boolean")}, - JavaObject{Symbol("java.lang.Byte")}, - JavaObject{Symbol("java.lang.Character")}, - JavaObject{Symbol("java.lang.Short")}, - JavaObject{Symbol("java.lang.Integer")}, - JavaObject{Symbol("java.lang.Long")}, - JavaObject{Symbol("java.lang.Float")}, - JavaObject{Symbol("java.lang.Double")} -} +function finalizeproxy(pxy::JProxy) + ptr = pxyptr(pxy) + if ptr == C_NULL || penv == C_NULL; return; end + #ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + @jnicall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{Nothing},), ptr) + setfield!(pxy, :ptr, C_NULL) #Safety in case this function is called direcly, rather than at finalize +end + +signatureClassFor(name) = length(name) == 1 ? sigTypes[name].classname : name + +isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing -classes = Dict() -methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() -modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} -typeInfo = Dict() +classtypename(ptr::Ptr{Nothing}) = typeNameFor(getclassname(getclass(ptr))) +classtypename(obj::JavaObject{T}) where T = string(T) + +# To access static members, use types or metaclasses +# like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` +# or JProxy(JString).valueOf(1) +JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}) +function JProxy(::Type{JavaObject{C}}) where C + c = Symbol(legalClassName(string(C))) + obj = classforname(string(c)) + info = infoFor(obj) + JProxy{typeFor(c), c}(obj.ptr, info, true) +end +# Proxies on classes are on the class objects, they don't get you static members +# To access static members, use types or metaclasses +# like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` +JProxy(s::AbstractString) = JProxy(JString(s)) +JProxy{T, C}(ptr::Ptr{Nothing}) where {T, C} = JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) +function JProxy(ptr::Ptr{Nothing}) + if ptr == C_NULL + cls = objectClass + n = "java.lang.Object" + else + cls = JClass(getclass(ptr)) + n = legalClassName(getname(cls)) + end + c = Symbol(n) + #println("JPROXY INFO FOR ", n, ", ", getname(cls)) + info = infoFor(cls) + aType, dim = arrayinfo(n) + if dim != 0 + t = typeFor(Symbol(aType)) + JProxy{Array{typeFor(Symbol(aType)), dim}, c}(ptr, info, false) + else + JProxy{typeFor(c), c}(ptr, info, false) + end +end +function JProxy(obj::JavaObject) + cls = isNull(obj) ? objectClass : getclass(obj) + n = legalClassName(getname(cls)) + c = Symbol(n) + info = infoFor(cls) + aType, dim = arrayinfo(n) + if dim != 0 + t = typeFor(Symbol(aType)) + JProxy{Array{typeFor(Symbol(aType)), dim}, c}(JObject(obj.ptr), info, false) + else + JProxy{typeFor(c), c}(obj.ptr, info, false) + end +end + +function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) + boxClass = classfortype(boxType) + primitive = length(signature) == 1 + primClass = primitive ? jfield(boxType, "TYPE", JClass) : objectClass + info = JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, primitive, accessorName, boxType, boxClass, primClass, getter, staticGetter, setter, staticSetter) + info +end + +function JFieldInfo(field::JField) + fcl = jcall(field, "getType", JClass, ()) + typ = juliaTypeFor(legalClassName(fcl)) + static = isStatic(field) + cls = jcall(field, "getDeclaringClass", JClass, ()) + id = fieldId(getname(field), JavaObject{Symbol(legalClassName(fcl))}, static, field, cls) + info = get(typeInfo, legalClassName(fcl), genericFieldInfo) + JFieldInfo(field, info, static, id, cls) +end -gettype(class::Symbol) = get(types, class, java_lang_Object) +function Boxing(info) + boxer = methodInfo(getConstructor(info.boxType, info.primClass)).id + unboxer = methodInfo(getMethod(info.boxType, info.accessorName)).id + Boxing(info, info.boxType, info.boxClass, info.primClass, boxer, unboxer) +end gettypeinfo(class::Symbol) = gettypeinfo(string(class)) gettypeinfo(class::AbstractString) = get(typeInfo, class, genericFieldInfo) @@ -245,10 +410,10 @@ gettypeinfo(class::AbstractString) = get(typeInfo, class, genericFieldInfo) hasClass(name::AbstractString) = hasClass(Symbol(name)) hasClass(name::Symbol) = name in genned hasClass(gen, name::AbstractString) = hasClass(gen, Symbol(name)) -hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodSets, string(name)) +hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodDicts, string(name)) function makeType(name::AbstractString, supername::Symbol, gen) - if !haskey(types, Symbol(name)) && !haskey(gen.methodSets, name) + if string(name) != "String" && !haskey(types, Symbol(name)) && !haskey(gen.methodDicts, name) typeName = typeNameFor(name) push!(gen.typeCode, :(abstract type $typeName <: $supername end), @@ -257,19 +422,24 @@ function makeType(name::AbstractString, supername::Symbol, gen) end end -function gen(name::AbstractString, classType::Type) - gen(Symbol(name), classType) +function registerclass(name::AbstractString, classType::Type) + registerclass(Symbol(name), classType) end -function gen(name::Symbol, classType::Type) - if !(name in genned) +function registerclass(name::Symbol, classType::Type) + if !haskey(types, name) types[name] = classType - gen(classforname(string(name))) end + infoFor(classforname(string(name))) end -function gen(class::JClass; deep=false) + +gen(name::Symbol; genmode=:none, print=false, eval=true) = _gen(classforname(string(name)), genmode, print, eval) +gen(name::AbstractString; genmode=:none, print=false, eval=true) = _gen(classforname(name), genmode, print, eval) +gen(class::JClass; genmode=:none, print=false, eval=true) = _gen(class, genmode, eval) +function _gen(class::JClass, genmode, print, evalResult) + n = legalClassName(class) gen = GenInfo() genClass(class, gen) - if deep + if genmode == :deep while !isempty(gen.deps) cls = pop!(gen.deps) !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) @@ -280,14 +450,13 @@ function gen(class::JClass; deep=false) !hasClass(gen, cls) && genType(classforname(string(cls)), gen) end end - #println("\nEVALUATING...\n\n") expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) - println(expr) - eval(expr) - for cl in gen.classList - n = legalClassName(cl) - classes[n] = JClassInfo(cl, gen.fielddicts[n], gen.methodSets[n], types[Symbol(n)]) + if print + for e in expr.args + println(e) + end end + evalResult && eval(expr) end function genType(class, gen::GenInfo) @@ -295,54 +464,73 @@ function genType(class, gen::GenInfo) sc = superclass(class) push!(genned, Symbol(legalClassName(class))) if !isNull(sc) + if !(Symbol(legalClassName(sc)) in genned) + genType(getcomponentclass(sc), gen) + end supertype = typeNameFor(sc) cType = componentType(supertype) makeType(name, cType, gen) else - makeType(name, :java_lang_Object, gen) + makeType(name, :java_lang, gen) end end -function genClass(class, gen::GenInfo) - gen.fielddicts[legalClassName(class)] = fielddict(class) - push!(gen.classList, class) +genClass(class::JClass, gen::GenInfo) = genClass(class, gen, infoFor(class)) +function genClass(class::JClass, gen::GenInfo, info::JClassInfo) name = getname(class) - sc = superclass(class) - #println("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") - push!(genned, Symbol(legalClassName(class))) - if !isNull(sc) - supertype = typeNameFor(sc) - cType = componentType(supertype) - !hasClass(gen, cType) && genClass(sc, gen) - makeType(name, cType, gen) - else - makeType(name, :java_lang_Object, gen) + if !(Symbol(name) in genned) + gen.fielddicts[legalClassName(class)] = fielddict(class) + push!(gen.classList, class) + sc = superclass(class) + #println("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") + push!(genned, Symbol(legalClassName(class))) + if !isNull(sc) + supertype = typeNameFor(sc) + cType = componentType(supertype) + !hasClass(gen, cType) && genClass(sc, gen) + makeType(name, cType, gen) + else + makeType(name, :java_lang, gen) + end + genMethods(class, gen, info) end - genMethods(class, gen) end -struct GenArgInfo - name - javaType - juliaType - function GenArgInfo(index, info, gen) - javaType = info.argTypes[index] - new(Symbol("a" * string(index)), javaType, argType(javaType, gen)) - end +GenInfo() = GenInfo([], [], Set(), [], Dict(), Dict()) + +function GenArgInfo(index, info::JMethodInfo, gen::GenInfo) + javaType = info.argTypes[index] + GenArgInfo(Symbol("a" * string(index)), javaType, argType(javaType, gen), argSpec(javaType, gen)) end argType(t, gen) = t argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String -argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = JLegalArg +#argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = JLegalArg +argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JLegalArg argType(::Type{<: Number}, gen) = Number -argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen))}) +argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) +argSpec(t, gen) = t +argSpec(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String +argSpec(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JObject +argSpec(::Type{<: Number}, gen) = Number +argSpec(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) +argSpec(arg::GenArgInfo) = arg.spec + +#legalClassName(cls::Ptr{Nothing}) = legalClassName(getclassname(getclass(cls))) +legalClassName(pxy::JProxy) = legalClassName(getclassname(pxystatic(pxy) ? pxyptr(pxy) : getclass(pxyptr(pxy)))) legalClassName(cls::JavaObject) = legalClassName(getname(cls)) legalClassName(cls::Symbol) = legalClassName(string(cls)) function legalClassName(name::AbstractString) - if (m = match(r"((\[])+)$", name)) != nothing - dimensions = Integer(length(m.captures[1]) / 2) - "$(repeat('[', dimensions))L$(name[1:end-dimensions * 2]);" + if (m = match(r"^(.*)((\[])+)$", name)) != nothing + dimensions = Integer(length(m.captures[2]) / 2) + info = get(typeInfo, m.captures[1], nothing) + base = if info != nothing && info.primitive + info.signature + else + "L$(m.captures[1]);" + end + "$(repeat('[', dimensions))$base" else name end @@ -351,6 +539,16 @@ end componentType(e::Expr) = e.args[2] componentType(sym::Symbol) = sym +typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) +function typeNameFor(T::AbstractString, gen::GenInfo) + aType, dims = arrayinfo(T) + c = dims != 0 ? aType : T + csym = Symbol(c) + if (dims == 0 || length(c) > 1) && !(csym in gen.deps) && !hasClass(gen, csym) && !get(typeInfo, c, genericFieldInfo).primitive + push!(gen.deps, csym) + end + typeNameFor(T) +end typeNameFor(class::JClass) = typeNameFor(legalClassName(class)) typeNameFor(className::Symbol) = typeNameFor(string(className)) function typeNameFor(className::AbstractString) @@ -362,7 +560,7 @@ function typeNameFor(className::AbstractString) n = replace(className, "_" => "___") n = replace(className, "\$" => "_s_") n = replace(n, "." => "_") - aType, dims = arrayInfo(n) + aType, dims = arrayinfo(n) if dims != 0 :(Array{$(typeNameFor(aType)), $(length(dims))}) else @@ -375,22 +573,16 @@ function typeNameFor(className::AbstractString) end end end -typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) -function typeNameFor(T::AbstractString, gen::GenInfo) - aType, dims = arrayInfo(T) - c = dims != 0 ? aType : T - csym = Symbol(c) - if (dims == 0 || length(c) > 1) && !(csym in gen.deps) && !hasClass(gen, csym) && !get(typeInfo, c, genericFieldInfo).primitive - #println("GEN CLASS: $c") - push!(gen.deps, csym) - end - typeNameFor(T) + +macro jp(s) + :(JProxy{$(s), Symbol($(classnamefor(s)))}) end function argCode(arg::GenArgInfo) argname = arg.name if arg.juliaType == String - :(JString($argname)) + #:(JString($argname)) + argname elseif arg.juliaType == JLegalArg :(box($argname)) elseif arg.juliaType == Number @@ -402,41 +594,46 @@ end function fieldEntry((name, fld)) fieldType = JavaObject{Symbol(legalClassName(fld.owner))} - name => :(jfield($(fld.info.class), $(string(name)), $fieldType)) + name => :(jfield($(fld.typeInfo.class), $(string(name)), $fieldType)) end -function genMethods(class, gen) +function genMethods(class, gen, info) #push!(genned, Symbol(typeNameFor(legalClassName(class)))) methodList = listmethods(class) classname = legalClassName(class) - gen.methodSets[classname] = methods = Set() + gen.methodDicts[classname] = methods = Dict() typeName = typeNameFor(classname, gen) classVar = Symbol("class_" * string(typeName)) fieldsVar = Symbol("fields_" * string(typeName)) methodsVar = Symbol("staticMethods_" * string(typeName)) push!(gen.code, :($classVar = classforname($classname))) push!(gen.code, :($fieldsVar = Dict([$([fieldEntry(f) for f in gen.fielddicts[classname]]...)]))) - push!(gen.code, :($methodsVar = Set($([getname(m) for m in methodList if isStatic(m)])))) - push!(gen.code, :(function Base.getproperty(p::JProxy{<: $typeName}, name::Symbol) - if (f = get($fieldsVar, name, nothing)) != nothing + push!(gen.code, :($methodsVar = Set($([string(n) for (n, m) in info.methods if any(x->x.static, m)])))) + push!(gen.code, :(function Base.getproperty(p::JProxy{T, C}, name::Symbol) where {T <: $typeName, C} + if (f = get($fieldsVar, name, nothing)) != nothing getField(p, name, f) else - JMethodProxy{name, $typeName}(pxyObj(p), emptyset, name in $methodsVar) + JMethodProxy(name, $typeName, p, emptyset) end end)) - for method in methodList - name = Symbol(getname(method)) - push!(methods, name) - info = methodInfo(method) - owner = javaType(info.owner) - if isSame(class.ptr, info.owner.ptr) - args = (GenArgInfo(i, info, gen) for i in 1:length(info.argTypes)) - argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) - methodIdName = Symbol("method_" * string(typeName) * "__" * string(name)) - push!(gen.code, :($methodIdName = getmethodid($(isStatic(method)), $classVar, $(string(name)), $(legalClassName(info.returnClass)), $(legalClassName.(info.argClasses))))) - push!(gen.code, :(function (pxy::JMethodProxy{Symbol($(string(name))), <: $typeName})($(argDecs...))::$(genReturnType(info, gen)) - $(genConvertResult(info.typeInfo.convertType, info, :(_jcall(getfield(pxy, :obj), $methodIdName, C_NULL, $(info.typeInfo.juliaType), ($(info.argTypes...),), $((argCode(arg) for arg in args)...))))) - end)) + for nameSym in sort(collect(keys(info.methods))) + name = string(nameSym) + multiple = length(info.methods[nameSym]) > 1 + symId = 0 + for minfo in info.methods[nameSym] + owner = javaType(minfo.owner) + if isSame(class.ptr, minfo.owner.ptr) + symId += 1 + args = (GenArgInfo(i, minfo, gen) for i in 1:length(minfo.argTypes)) + argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) + methodIdName = Symbol("method_" * string(typeName) * "__" * name * (multiple ? string(symId) : "")) + callinfo = jnicalls[minfo.typeInfo.classname] + push!(gen.code, :($methodIdName = getmethodid($(minfo.static), $classVar, $name, $(legalClassName(minfo.returnClass)), $(legalClassName.(minfo.argClasses))))) + push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $typeName})($(argDecs...))::$(genReturnType(minfo, gen)) + println($("Generated method $name$(multiple ? "(" * string(symId) * ")" : "")")) + $(genConvertResult(minfo.typeInfo.convertType, minfo, :(call(pxy.obj, $methodIdName, $(static ? callinfo.static : callinfo.instance), $(minfo.typeInfo.juliaType), ($(argSpec.(args)...),), $((argCode(arg) for arg in args)...))))) + end)) + end end end push!(gen.code, :(push!(genned, Symbol($(legalClassName(class)))))) @@ -447,13 +644,15 @@ function genReturnType(methodInfo, gen) if methodInfo.typeInfo.primitive || t <: String || t == Nothing t else - :(JProxy{<:$(typeNameFor(javaType(methodInfo.returnType), gen))}) + :(JProxy{<:$(typeNameFor(methodInfo.returnType, gen))}) end end + genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) genConvertResult(toType::Type{String}, info, expr) = :(unsafe_string($expr)) -genConvertResult(toType::JBoxTypes, info, expr) = :(unbox($expr)) +#genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($expr)) +genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($(toType.parameters[1]), $expr)) function genConvertResult(toType, info, expr) if isVoid(info) || info.typeInfo.primitive expr @@ -464,120 +663,70 @@ end isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 -#function JClassInfo(class::JClass; deep=false) -# if isArray(class) -# infoFor(objectClass) -# else -# gen = GenInfo() -# genClass(class, gen) -# if deep -# while !isempty(gen.deps) -# cls = pop!(gen.deps) -# !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) -# end -# else -# while !isempty(gen.deps) -# cls = pop!(gen.deps) -# !hasClass(gen, cls) && genType(classforname(string(cls)), gen) -# end -# end -# #println("\nEVALUATING...\n\n") -# expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) -# println(expr) -# eval(expr) -# #for e in expr.args -# # println(e) -# # eval(e) -# #end -# #println("DONE EVALUATING") -# for cl in gen.classList -# n = legalClassName(cl) -# classes[n] = JClassInfo(cl, gen.fielddicts[n], gen.methodSets[n], types[Symbol(n)]) -# end -# classes[legalClassName(class)] -# end -#end - -function JClassInfo(class::JClass; gen=false, deep=false) - if isArray(class) - infoFor(objectClass) - else - n = legalClassName(class) - sc = superclass(class) - info = classes[n] = JClassInfo(isNull(sc) ? nothing : infoFor(sc), class, fielddict(class), methoddict(class), gettype(Symbol(n))) - if gen - genInfo = GenInfo() - genClass(class, genInfo) - if deep - while !isempty(genInfo.deps) - cls = pop!(genInfo.deps) - !hasClass(genInfo, cls) && genClass(classforname(string(cls)), genInfo) - end - else - while !isempty(genInfo.deps) - cls = pop!(genInfo.deps) - !hasClass(genInfo, cls) && genType(classforname(string(cls)), genInfo) - end - end - #println("\nEVALUATING...\n\n") - expr = :(begin $(genInfo.typeCode...); $(genInfo.code...); $(genClasses(getname.(genInfo.classList))...); end) - for e in expr.args - println(e) - # eval(e) - end - #println(expr) - eval(expr) - #println("DONE EVALUATING") - for cl in genInfo.classList - n = legalClassName(cl) - classes[n] = JClassInfo(cl, genInfo.fielddicts[n], genInfo.methodSets[n], types[Symbol(n)]) - end - classes[legalClassName(class)] +function JClassInfo(class::JClass) + n = Symbol(legalClassName(class)) + sc = superclass(class) + parentinfo = !isNull(sc) ? infoFor(sc) : nothing + tname = typeNameFor(string(n)) + #println("JCLASS INFO FOR ", n) + jtype = get!(types, n) do + if tname != String + #println("DEFINING ", repr(tname), quote + # abstract type $tname <: JavaCall.$(isNull(sc) ? :java_lang : typeNameFor(Symbol(legalClassName(sc)))) end + # $tname + #end) + JavaCall.eval(quote + abstract type $tname <: JavaCall.$(isNull(sc) ? :java_lang : typeNameFor(Symbol(legalClassName(sc)))) end + $tname + end) end - info end + classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) end -genClasses(classNames) = (:(gen($name, $(Symbol(typeNameFor(name))))) for name in classNames) +genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) function typeFor(sym::Symbol) - aType, dims = arrayInfo(string(sym)) - dims != 0 ? Array{types[Symbol(aType)], length(dims)} : types[sym] + aType, dims = arrayinfo(string(sym)) + dims != 0 ? Array{get(types, Symbol(aType), java_lang), length(dims)} : get(types, sym, java_lang) end asJulia(t, obj) = obj asJulia(::Type{Bool}, obj) = obj != 0 asJulia(t, obj::JBoxed) = unbox(obj) -function asJulia(x, obj::JavaObject) - if isNull(obj) - nothing - else - (get(juliaConverters, classtypename(obj), JProxy))(obj) - end -end function asJulia(x, ptr::Ptr{Nothing}) - if isNull(ptr) + if ptr == C_NULL jnull else - asJulia(x, JavaObject{Symbol(getclassname(getclass(ptr)))}(ptr)) + unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ptr))))}, ptr) end end box(str::AbstractString) = str -box(pxy::JProxy) = pxyObj(pxy) +box(pxy::JProxy) = ptrObj(pxy) #function box(array::Array{T,N}) #end unbox(obj) = obj +unbox(::Type{T}, obj) where T = obj +function unbox(::Type{JavaObject{T}}, obj::Ptr{Nothing}) where T + if obj == C_NULL + nothing + else + #println("UNBOXING ", T) + (get(juliaConverters, string(T)) do + (x)-> JProxy(x) + end)(obj) + end +end -pxyObj(p::JProxy) = getfield(p, :obj) -pxyPtr(p::JProxy) = pxyObj(p).ptr -pxyInfo(p::JProxy) = getfield(p, :info) -pxyStatic(p::JProxy) = getfield(p, :static) +pxyptr(p::JProxy) = getfield(p, :ptr) +pxyinfo(p::JProxy) = getfield(p, :info) +pxystatic(p::JProxy) = getfield(p, :static) -==(j1::JProxy, j2::JProxy) = isSame(pxyPtr(j1), pxyPtr(j2)) +==(j1::JProxy, j2::JProxy) = isSame(pxyptr(j1), pxyptr(j2)) isSame(j1::JavaObject, j2::JavaObject) = isSame(j1.ptr, j2.ptr) -isSame(j1::Ptr{Nothing}, j2::Ptr{Nothing}) = ccall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, j1, j2) != C_NULL +isSame(j1::Ptr{Nothing}, j2::Ptr{Nothing}) = @jnicall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}), j1, j2) != C_NULL getreturntype(c::JConstructor) = voidClass @@ -595,15 +744,17 @@ function methodInfo(m::Union{JMethod, JConstructor}) cls = jcall(m, "getDeclaringClass", JClass, ()) methodKey = (legalClassName(cls), name, legalClassName.(argTypes)) get!(methodCache, methodKey) do - methodId = getmethodid(isStatic(m), cls, name, returnType, argTypes) + methodId = getmethodid(isStatic(m), legalClassName(cls), name, legalClassName(returnType), legalClassName.(argTypes)) typeName = legalClassName(returnType) info = get(typeInfo, typeName, genericFieldInfo) owner = metaclass(legalClassName(cls)) - id = length(methodCache) - methodsById[id] = JMethodInfo(id, name, info, juliaTypeFor(returnType), returnType, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner) + methodsById[length(methodsById)] = JMethodInfo(name, info, Symbol(typeName), returnType, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner, get(jnicalls, typeName, Tuple(filterDynArgType.(juliaTypeFor.(argTypes))))) end end +filterDynArgType(::Type{<:AbstractString}) = JavaObject{Symbol("java.lang.String")} +filterDynArgType(t) = t + isStatic(meth::JConstructor) = false function isStatic(meth::Union{JMethod,JField}) global modifiers @@ -629,41 +780,51 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) quote begin - JavaTypeInfo(JavaObject{Symbol($(string(jclass)))}, $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) - ccall(field.static ? field.info.staticSetter : field.info.setter, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), - penv, (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) + JavaTypeInfo(Symbol($(string(jclass))), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) + @jnicall(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), + (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) end end end end function initProxy() + push!(jnicalls, + :boolean => (static=:CallStaticBooleanMethodA, instance=:CallBooleanMethodA), + :byte => (static=:CallStaticByteMethodA, instance=:CallByteMethodA), + :char => (static=:CallStaticCharMethodA, instance=:CallCharMethodA), + :short => (static=:CallStaticShortMethodA, instance=:CallShortMethodA), + :int => (static=:CallStaticIntMethodA, instance=:CallIntMethodA), + :long => (static=:CallStaticLongMethodA, instance=:CallLongMethodA), + :float => (static=:CallStaticFloatMethodA, instance=:CallFloatMethodA), + :double => (static=:CallStaticDoubleMethodA, instance=:CallDoubleMethodA), + ) global objectClass = classforname("java.lang.Object") global classClass = classforname("java.lang.Class") global voidClass = jfield(JavaObject{Symbol("java.lang.Void")}, "TYPE", JClass) + global methodid_getmethod = getmethodid("java.lang.Class", "getMethod", "java.lang.reflect.Method", "java.lang.String", "[Ljava.lang.Class;") conv("java.lang.String") do x; unsafe_string(x); end - conv("java.lang.Integer") do x; JProxy(x).intValue(); end - conv("java.lang.Long") do x; JProxy(x).longValue(); end - global typeInfo = Dict([ - "void" => @vtypeInf(void, jint, "V", Nothing, Object, false, Void) - "boolean" => @typeInf(boolean, "Z", Bool, Boolean) - "byte" => @typeInf(byte, "B", Int8, Byte) - "char" => @typeInf(char, "C", Char, Character) - "short" => @typeInf(short, "S", Int16, Short) - "int" => @typeInf(int, "I", Int32, Integer) - "float" => @typeInf(float, "F", Float32, Float) - "long" => @typeInf(long, "J", Int64, Long) - "double" => @typeInf(double, "D", Float64, Double) - "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object) - ]) - classes["java.lang.String"] = JClassInfo(nothing, classforname("java.lang.String"), Dict(), Dict(), types[Symbol("java.lang.String")]) + conv("java.lang.Integer") do x; @jp(java_lang_Integer)(x).intValue(); end + conv("java.lang.Long") do x; @jp(java_lang_Long)(x).longValue(); end + push!(typeInfo, + "void" => @vtypeInf(void, jint, "V", Nothing, Object, false, Void), + "boolean" => @typeInf(boolean, "Z", Bool, Boolean), + "byte" => @typeInf(byte, "B", Int8, Byte), + "char" => @typeInf(char, "C", Char, Character), + "short" => @typeInf(short, "S", Int16, Short), + "int" => @typeInf(int, "I", Int32, Integer), + "float" => @typeInf(float, "F", Float32, Float), + "long" => @typeInf(long, "J", Int64, Long), + "double" => @typeInf(double, "D", Float64, Double), + "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object), + ) global sigTypes = Dict([inf.signature => inf for (key, inf) in typeInfo if inf.primitive]) global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true, Object) - global methodId_object_getClass = getmethodid(false, objectClass, "getClass", classforname("java.lang.Class"), Vector{JClass}()) - global methodId_class_getName = getmethodid(false, classClass, "getName", classforname("java.lang.String"), Vector{JClass}()) + global methodId_object_getClass = getmethodid("java.lang.Object", "getClass", "java.lang.Class") + global methodId_class_getName = getmethodid("java.lang.Class", "getName", "java.lang.String") for info in (t->typeInfo[string(t)]).(:(int, long, byte, boolean, char, short, float, double).args) - infoName = string(javaType(info.class)) + infoName = string(info.classname) boxVar = Symbol(infoName * "Box") box = boxers[infoName] = Boxing(info) expr = quote @@ -674,6 +835,20 @@ function initProxy() function box(data::$(info.convertType)) _jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $(box.boxType), ($(box.info.juliaType),), data) end + function unbox(::Type{$(info.boxType)}, ptr::Ptr{Nothing}) + $(if box.info.convertType == Bool + :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ()) != 0) + else + :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ())) + end) + end + function unbox(::Type{$(types[javaType(info.boxType)])}, ptr::Ptr{Nothing}) + $(if box.info.convertType == Bool + :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ()) != 0) + else + :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ())) + end) + end function unbox(obj::$(info.boxType)) $(if box.info.convertType == Bool :(_jcall(obj, $boxVar.unboxer, C_NULL, $(box.info.juliaType), ()) != 0) @@ -690,44 +865,37 @@ end metaclass(class::AbstractString) = metaclass(Symbol(class)) -function getclass(obj::Ptr{Nothing}) - result = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, methodId_object_getClass, Array{Int64,1}()) - result == C_NULL && geterror() - result -end +getclass(obj::Ptr{Nothing}) = @message(obj, Object, methodId_object_getClass) -function getclassname(class::Ptr{Nothing}) - result = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, class, methodId_class_getName, Array{Int64,1}()) - result == C_NULL && geterror() - unsafe_string(result) -end +getclassname(class::Ptr{Nothing}) = unsafe_string(@message(class, Object, methodId_class_getName)) -function getmethodid(static::Bool, cls::JClass, name::AbstractString, rettype::AbstractString, argtypes::Vector{<:AbstractString}) +function getmethodid(cls::AbstractString, name, rettype::AbstractString, argtypes::AbstractString...) + getmethodid(false, cls, name, rettype, collect(argtypes)) +end +function getmethodid(static, cls::JClass, name, rettype::AbstractString, argtypes::Vector{<:AbstractString}) getmethodid(static, cls, name, classforlegalname(rettype), collect(JClass, classforlegalname.(argtypes))) end -function getmethodid(static::Bool, cls::JClass, name::AbstractString, rettype::JClass, argtypes::Vector{JClass}) +getmethodid(static, cls::JClass, name, rettype, argtypes) = getmethodid(static, legalClassName(cls), name, rettype, argtypes) +function getmethodid(static::Bool, clsname::AbstractString, name::AbstractString, rettype::AbstractString, argtypes::Vector{<:Union{JClass, AbstractString}}) sig = proxyMethodSignature(rettype, argtypes) - jclass = metaclass(legalClassName(cls)) - result = ccall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - penv, jclass, name, sig) - if result == C_NULL - println("ERROR CALLING METHOD class: ", jclass, ", name: ", name, ", sig: ", sig, ", arg types: ", argtypes) - end - result==C_NULL && geterror() - result + jclass = metaclass(clsname) + #println(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + # (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + # jclass, name, sig)) + @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + jclass, name, sig) end function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} - id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - penv, metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) - id == C_NULL && geterror(true) - id + #id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, + # (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + # penv, metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) + #id == C_NULL && geterror(true) + #id + @jnicall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) end function infoSignature(cls::AbstractString) @@ -735,25 +903,41 @@ function infoSignature(cls::AbstractString) if info != nothing; info.signature; end end -function proxyClassSignature(cls::AbstractString) - sig = infoSignature(cls) - sig != nothing ? sig : proxyClassSignature(classforname(cls)) -end - -function proxyClassSignature(cls::JClass) - info = get(typeInfo, getname(cls), nothing) +#function proxyClassSignature(cls::AbstractString) +# sig = infoSignature(cls) +# sig != nothing ? sig : proxyClassSignature(classforname(cls)) +#end +#function proxyClassSignature(cls::JClass) +# info = get(typeInfo, getname(cls), nothing) +# if info != nothing && info.primitive +# info.signature +# else +# sig = [] +# while jcall(cls, "isArray", jboolean, ()) != 0 +# push!(sig, "[") +# cls = jcall(cls, "getComponentType", JClass, ()) +# end +# clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) +# push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") +# join(sig, "") +# end +#end +proxyClassSignature(cls::JClass) = proxyClassSignature(legalClassName(cls)) +function proxyClassSignature(clsname::AbstractString) + info = get(typeInfo, clsname, nothing) if info != nothing && info.primitive info.signature else - sig = [] - while jcall(cls, "isArray", jboolean, ()) != 0 - push!(sig, "[") - cls = jcall(cls, "getComponentType", JClass, ()) - end - clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) - push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") - join(sig, "") + atype, dim = arrayinfo(clsname) + dim > 0 ? javaclassname(clsname) : "L" * javaclassname(clsname) * ";" + end +end + +function getcomponentclass(class::JClass) + while jcall(class, "isArray", jboolean, ()) != 0 + class = jcall(class, "getComponentType", JClass, ()) end + class end function proxyMethodSignature(rettype, argtypes) @@ -773,15 +957,29 @@ function juliaTypeFor(name::AbstractString) info != nothing ? info.juliaType : JavaObject{Symbol(name)} end -function infoFor(class::JClass; deep=false) - name = legalClassName(class) - haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class, deep=deep) +function infoFor(class::JClass) + if isNull(class) + nothing + else + name = legalClassName(class) + #println("INFO FOR ", name) + haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class) + end end getname(thing::Union{JClass, JMethod, JField}) = jcall(thing, "getName", JString, ()) getname(thing::JConstructor) = "" -classforlegalname(n::AbstractString) = (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) +#classforlegalname(n::AbstractString) = (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) + +function classforlegalname(n::AbstractString) + try + (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) + catch x + #println("Error finding class: $n, type: $(typeof(n))") + throw(x) + end +end classfortype(t::Type{JavaObject{T}}) where T = classforname(string(T)) @@ -789,7 +987,24 @@ listfields(cls::AbstractString) = listfields(classforname(cls)) listfields(cls::Type{JavaObject{C}}) where C = listfields(classforname(string(C))) listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) -fielddict(class::JClass) = Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) +function fielddict(class::JClass) + if isArray(class) + Dict([:length => JReadonlyField((obj)->arraylength(obj.ptr))]) + else + Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) + end +end + +#arraylength(obj) = ccall(jnifunc.GetArrayLength, jint, (Ptr{JNIEnv}, Ptr{Nothing}), penv, obj) +arraylength(obj) = jni(jnifunc.GetArrayLength, jint, (Ptr{Nothing}), obj) + +#function Base.getindex(pxy::JProxy{Array{T}}, I::Vararg{Int, N}) where {T, N} +#end +function Base.getindex(pxy::JProxy{Array{T, 1}}, i::Int) where T + asJulia(JObject, JObject(@jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + pxyptr(pxy), jint(i)))) +end function methoddict(class) d = Dict() @@ -811,70 +1026,207 @@ isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 superclass(obj::JavaObject) = jcall(obj, "getSuperclass", @jimport(java.lang.Class), ()) -function getField(p::JProxy, name::Symbol, field::JFieldInfo) - result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), - penv, pxyStatic(p) ? getclass(obj) : pxyObj(p).ptr, field.id) - result == C_NULL && geterror() - result = (field.info.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) - asJulia(field.info.juliaType, result) +function getField(p::JProxy, field::JFieldInfo) + asJulia(field.typeInfo.juliaType, @jnicall(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}), + pxystatic(p) ? getclass(obj) : pxyptr(p), field.id)) end -function Base.getproperty(p::JProxy, name::Symbol) - obj = pxyObj(p) - info = pxyInfo(p) - static = pxyStatic(p) - result = if name in info.methods - println("MAKING METHOD PROXY") - JMethodProxy{name, gettype(javaType(obj))}(obj, info.methods[name], static) +function Base.getproperty(p::JProxy{T}, name::Symbol) where T + info = pxyinfo(p) + if haskey(info.methods, name) + JMethodProxy(name, T, p, info.methods[name]) else - field = info.fields[name] - result = ccall(static ? field.info.staticGetter : field.info.getter, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), - penv, static ? getclass(obj) : obj.ptr, field.id) - result == C_NULL && geterror() - result = (field.info.primitive ? convert(field.info.juliaType, result) : result == C_NULL ? jnull : narrow(JavaObject(JObject, result))) - asJulia(field.info.juliaType, result) + finfo = info.fields[name] + asJulia(finfo.typeInfo, getproxyfield(p, info, finfo)) end - result != jnull && isa(result, JavaObject) ? JProxy(result) : result +end + +getproxyfield(p::JProxy, info::JClassInfo, field::JReadonlyField) = field.get(pxyptr(p)) +function getproxyfield(p::JProxy, info::JClassInfo, field::JFieldInfo) + static = pxystatic(p) + ptr = pxyptr(p) + asJulia(field.typeInfo.juliaType, @jnicall(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}), + static ? getclass(ptr) : ptr, field.id)) end function Base.setproperty!(p::JProxy, name::Symbol, value) - obj = pxyObj(p) - info = pxyInfo(p) + info = pxyinfo(p) meths = get(info.methods, name, nothing) - static = pxyStatic(p) + static = pxystatic(p) result = if meths != nothing throw(JavaCallError("Attempt to set a method")) else - if isa(value, JProxy); value = JavaObject(value); end field = info.fields[name] - value = convert(field.info.primitive ? field.info.juliaType : field.info.class, value) - result = field.info.setterFunc(field, obj, value) + value = convert(field.typeInfo.primitive ? field.typeInfo.juliaType : field.typeInfo.class, value) + result = field.typeInfo.setterFunc(field, obj, value) result == C_NULL && geterror() value end isa(result, JavaObject) ? JProxy(result) : result end -function (pxy::JMethodProxy)(args...) +function (pxy::JMethodProxy{N})(args...) where N targets = Set(m for m in pxy.methods if fits(m, args)) + #println("LOCATING MESSAGE ", N, " FOR ARGS ", repr(args)) if !isempty(targets) # Find the most specific method - meth = reduce(((x, y)-> generality(x, y) < generality(y, x) ? x : y), filterStatic(pxy, targets)) - convertedArgs = convert.(meth.argTypes, args) - result = _jcall(meth.static ? meth.owner : pxy.receiver, meth.id, C_NULL, meth.typeInfo.juliaType, meth.argTypes, convertedArgs...) - if !isVoid(meth); asJulia(meth.typeInfo.convertType, result); end + argTypes = typeof(args).parameters + meth = reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) + #println("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType) + #withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.juliaType, meth.argTypes, args...)) do result + withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.juliaType, meth.dynArgTypes, args...)) do result + asJulia(meth.typeInfo.convertType, result) + end + end +end + +withlocalref(func, result::Any) = func(result) +function withlocalref(func, ptr::Ptr{Nothing}) + ref = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + try + func(ref) + finally + deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ref) + end +end + +function filterStatic(pxy::JMethodProxy, targets) + static = pxy.static + Set(target for target in targets if target.static == static) +end + +fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.dynArgTypes, args)) + +#canConvert(::Type{T}, ::T) where T = true +canConvert(::Type{JavaObject{Symbol("java.lang.Object")}}, ::Union{AbstractString, Real}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Double")}}, ::Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Float")}}, ::Union{Float32, Float16, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Long")}}, ::Union{Int64, Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Integer")}}, ::Union{Int32, Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Short")}}, ::Union{Int16, Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Byte")}}, ::Union{Int8}) = true +canConvert(::Type{JavaObject{Symbol("java.lang.Character")}}, ::Union{Int8, Char}) = true +canConvert(::Type{String}, ::AbstractString) = true +canConvert(::Type{JString}, ::AbstractString) = true +canConvert(::Type{<: Real}, ::T) where {T <: Real} = true +canConvert(::Type{jboolean}, ::Bool) = true +canConvert(::Type{jchar}, ::Char) = true +canConvert(::Type{<:Integer}, ::Ptr{Nothing}) = true +canConvert(x, y) = false +convert(::Type{JObject}, pxy::JProxy) = JavaObject(pxy) + +# score relative generality of two methods as applied to a particular set of arguments +# higher means p1 is more general than p2 (i.e. p2 is the more specific one) +function moreGeneral(argTypes, p1::JMethodInfo, p2::JMethodInfo) where T + g = 0 + for i in 1:length(argTypes) + c1, c2 = p1.argClasses[i], p2.argClasses[i] + t1, t2 = p1.argTypes[i], p2.argTypes[i] + g += moreGeneral(argTypes[i], c1, t1, c2, t2) - moreGeneral(argTypes[i], c2, t2, c1, t1) + end + g +end + +isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 + +# score relative generality of corresponding arguments in two methods +# higher means c1 is more general than c2 (i.e. c2 is the more specific one) +function moreGeneral(argType::Type, c1::JClass, t1::Type, c2::JClass, t2::Type) + p1 = t1 <: JPrimitive + p2 = t2 <: JPrimitive + g1 = !p1 ? 0 : argType <: t1 ? 1 : -1 + g2 = !p2 ? 0 : argType <: t2 ? 1 : -1 + g = if !p1 && p2 || jcall(c1, "isAssignableFrom", jboolean, (@jimport(java.lang.Class),), c2) != 0 + 1 + else + 0 end + g + g2 - g1 end +function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes::Tuple, args...) where T + ptr == C_NULL && error("Attempt to call method on Java NULL") + + savedargs, convertedargs = convert_args(argtypes, args...) + result = _call(T, ptr, mId, convertedargs) + result == C_NULL && geterror() + asJulia(rettype, convert_result(rettype, result)) +end + +_call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{Bool}, obj, mId, args) = ccall(jnifunc.CallBooleanMethodA, jboolean, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jbyte}, obj, mId, args) = ccall(jnifunc.CallByteMethodA, jbyte, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jchar}, obj, mId, args) = ccall(jnifunc.CallCharMethodA, jchar, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jshort}, obj, mId, args) = ccall(jnifunc.CallShortMethodA, jshort, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jint}, obj, mId, args) = ccall(jnifunc.CallIntMethodA, jint, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jlong}, obj, mId, args) = ccall(jnifunc.CallLongMethodA, jlong, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jfloat}, obj, mId, args) = ccall(jnifunc.CallFloatMethodA, jfloat, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_call(::Type{jdouble}, obj, mId, args) = ccall(jnifunc.CallDoubleMethodA, jdouble, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) + +function staticcall(class::Ptr{Nothing}, mId, rettype::Type{T}, argtypes::Tuple, args...) where T + savedargs, convertedargs = convert_args(argtypes, args...) + result = _staticcall(T, class, mId, convertedargs) + #println("CONVERTING RESULT ", repr(result), " TO ", rettype) + result == C_NULL && geterror() + #println("RETTYPE: ", rettype) + asJulia(rettype, convert_result(rettype, result)) +end + +_staticcall(::Type{Any}, obj, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{Bool}, obj, mId, args) = ccall(jnifunc.CallStaticBooleanMethodA, jboolean, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jbyte}, obj, mId, args) = ccall(jnifunc.CallStaticByteMethodA, jbyte, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jchar}, obj, mId, args) = ccall(jnifunc.CallStaticCharMethodA, jchar, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jshort}, obj, mId, args) = ccall(jnifunc.CallStaticShortMethodA, jshort, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jint}, obj, mId, args) = ccall(jnifunc.CallStaticIntMethodA, jint, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jlong}, obj, mId, args) = ccall(jnifunc.CallStaticLongMethodA, jlong, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jfloat}, obj, mId, args) = ccall(jnifunc.CallStaticFloatMethodA, jfloat, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) +_staticcall(::Type{jdouble}, obj, mId, args) = ccall(jnifunc.CallStaticDoubleMethodA, jdouble, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) + function Base.show(io::IO, pxy::JProxy) - if pxyStatic(pxy) - print(io, "static class $(legalClassName(JavaObject(pxy)))") + if pxystatic(pxy) + print(io, "static class $(legalClassName(getclassname(pxyptr(pxy))))") else print(io, pxy.toString()) #print(io, Java.toString(pxy)) end end -JavaObject(pxy::JProxy) = pxyObj(pxy) +JavaObject(pxy::JProxy{T, C}) where {T, C} = JavaObject{C}(pxyptr(pxy)) From 60f328c00500eaa5d9ff46f2b5e9f47b1639d00d Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sat, 13 Oct 2018 20:18:49 +0300 Subject: [PATCH 18/43] improved design, need to eliminate JProxy's references to JavaObject --- src/core.jl | 17 ++- src/proxy.jl | 251 +++++++++++++++++++++++++++----------- test/Test$TestInner.class | Bin 392 -> 392 bytes test/Test.class | Bin 3087 -> 3133 bytes test/Test.java | 1 + test/runtests.jl | 11 +- 6 files changed, 200 insertions(+), 80 deletions(-) diff --git a/src/core.jl b/src/core.jl index 11065ea..662cae9 100644 --- a/src/core.jl +++ b/src/core.jl @@ -29,22 +29,29 @@ mutable struct JavaObject{T} #This below is ugly. Once we stop supporting 0.5, this can be replaced by # function JavaObject{T}(ptr) where T function JavaObject{T}(ptr) where T - j = new{T}(newglobalref(ptr)) - finalizer(deleteref, j) - return j + if ptr == C_NULL + new{T}(ptr) + else + ref = newglobalref(ptr) + reftype = ccall(jnifunc.GetObjectRefType, Int32, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + reftype == 0 && geterror(true) + reftype == 1 && deletelocalref(ptr) + finalizer(deleteref, new{T}(ref)) + end end #replace with: JavaObject{T}(argtypes::Tuple, args...) where T JavaObject{T}(argtypes::Tuple, args...) where {T} = jnew(T, argtypes, args...) JavaObject{T}() where {T} = jnew(T, ()) JavaObject(::Nothing) = new{Symbol("java.lang.Object")}(C_NULL) + JavaObject(T, ptr) = JavaObject{T}(ptr) end newglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) deleteglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) -JavaObject(T, ptr) = JavaObject{T}(ptr) +deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) function deleteref(x::JavaObject) if x.ptr == C_NULL; return; end @@ -99,7 +106,7 @@ function JString(str::AbstractString) if jstring == C_NULL geterror() else - return JString(jstring) + JString(jstring) end end diff --git a/src/proxy.jl b/src/proxy.jl index d51cfc8..3a417ff 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -91,7 +91,7 @@ struct JReadWriteField set end -struct JFieldInfo +struct JFieldInfo{T} field::JField typeInfo::JavaTypeInfo static::Bool @@ -231,6 +231,7 @@ const boxers = Dict() const juliaConverters = Dict() global jnicalls = Dict() const defaultjnicall = (instance=:CallObjectMethod,static=:CallStaticObjectMethod) +global useVerbose = false #function classnamefor(t::Type{<:java_lang}) # s = replace(string(nameof(t)), "___" => "_") @@ -263,9 +264,14 @@ global genericFieldInfo global objectClass global sigTypes +setVerbose() = global useVerbose = true +clearVerbose() = global useVerbose = false + +verbose(args...) = useVerbose && println(args...) + macro jnicall(func, rettype, types, args...) quote - result = ccall($(esc(func)), $(esc(rettype)), + local result = ccall($(esc(func)), $(esc(rettype)), (Ptr{JNIEnv}, $(esc.(types.args)...)), penv, $(esc.(args)...)) result == C_NULL && geterror() @@ -275,11 +281,12 @@ end macro message(obj, rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).instance - #println("INSTANCE FUNC: ", func) + verbose("INSTANCE FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) quote result = ccall(jnifunc.$func, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), + #(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((typeof(arg) for arg in args)...)), penv, $(esc(obj)), $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() result @@ -288,21 +295,15 @@ end macro staticmessage(rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).static - #println("STATIC FUNC: ", func) + verbose("STATIC FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) - expr = quote + quote result = ccall(jnifunc.$func, $(esc(rettype)), (Ptr{JNIEnv}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), penv, $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() result end - #println("STATIC CALL: ", expr) - #if func == defaultjnicall.static && true - # expr - #else - # :(objectClass) - #end end globalref(ptr::Ptr{Nothing}) = @jnicall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{Nothing},), ptr) @@ -356,7 +357,7 @@ function JProxy(ptr::Ptr{Nothing}) n = legalClassName(getname(cls)) end c = Symbol(n) - #println("JPROXY INFO FOR ", n, ", ", getname(cls)) + #verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) info = infoFor(cls) aType, dim = arrayinfo(n) if dim != 0 @@ -395,7 +396,7 @@ function JFieldInfo(field::JField) cls = jcall(field, "getDeclaringClass", JClass, ()) id = fieldId(getname(field), JavaObject{Symbol(legalClassName(fcl))}, static, field, cls) info = get(typeInfo, legalClassName(fcl), genericFieldInfo) - JFieldInfo(field, info, static, id, cls) + JFieldInfo{info.convertType}(field, info, static, id, cls) end function Boxing(info) @@ -434,6 +435,7 @@ end gen(name::Symbol; genmode=:none, print=false, eval=true) = _gen(classforname(string(name)), genmode, print, eval) gen(name::AbstractString; genmode=:none, print=false, eval=true) = _gen(classforname(name), genmode, print, eval) +gen(pxy::JProxy{T, C}) where {T, C} = gen(C) gen(class::JClass; genmode=:none, print=false, eval=true) = _gen(class, genmode, eval) function _gen(class::JClass, genmode, print, evalResult) n = legalClassName(class) @@ -482,7 +484,7 @@ function genClass(class::JClass, gen::GenInfo, info::JClassInfo) gen.fielddicts[legalClassName(class)] = fielddict(class) push!(gen.classList, class) sc = superclass(class) - #println("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") + #verbose("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") push!(genned, Symbol(legalClassName(class))) if !isNull(sc) supertype = typeNameFor(sc) @@ -522,7 +524,7 @@ legalClassName(pxy::JProxy) = legalClassName(getclassname(pxystatic(pxy) ? pxypt legalClassName(cls::JavaObject) = legalClassName(getname(cls)) legalClassName(cls::Symbol) = legalClassName(string(cls)) function legalClassName(name::AbstractString) - if (m = match(r"^(.*)((\[])+)$", name)) != nothing + if (m = match(r"^([^[]*)((\[])+)$", name)) != nothing dimensions = Integer(length(m.captures[2]) / 2) info = get(typeInfo, m.captures[1], nothing) base = if info != nothing && info.primitive @@ -630,7 +632,7 @@ function genMethods(class, gen, info) callinfo = jnicalls[minfo.typeInfo.classname] push!(gen.code, :($methodIdName = getmethodid($(minfo.static), $classVar, $name, $(legalClassName(minfo.returnClass)), $(legalClassName.(minfo.argClasses))))) push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $typeName})($(argDecs...))::$(genReturnType(minfo, gen)) - println($("Generated method $name$(multiple ? "(" * string(symId) * ")" : "")")) + verbose($("Generated method $name$(multiple ? "(" * string(symId) * ")" : "")")) $(genConvertResult(minfo.typeInfo.convertType, minfo, :(call(pxy.obj, $methodIdName, $(static ? callinfo.static : callinfo.instance), $(minfo.typeInfo.juliaType), ($(argSpec.(args)...),), $((argCode(arg) for arg in args)...))))) end)) end @@ -668,10 +670,10 @@ function JClassInfo(class::JClass) sc = superclass(class) parentinfo = !isNull(sc) ? infoFor(sc) : nothing tname = typeNameFor(string(n)) - #println("JCLASS INFO FOR ", n) + #verbose("JCLASS INFO FOR ", n) jtype = get!(types, n) do if tname != String - #println("DEFINING ", repr(tname), quote + #verbose("DEFINING ", repr(tname), quote # abstract type $tname <: JavaCall.$(isNull(sc) ? :java_lang : typeNameFor(Symbol(legalClassName(sc)))) end # $tname #end) @@ -698,6 +700,7 @@ function asJulia(x, ptr::Ptr{Nothing}) if ptr == C_NULL jnull else + verbose("UNBOXING ", ptr) unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ptr))))}, ptr) end end @@ -712,7 +715,7 @@ function unbox(::Type{JavaObject{T}}, obj::Ptr{Nothing}) where T if obj == C_NULL nothing else - #println("UNBOXING ", T) + #verbose("UNBOXING ", T) (get(juliaConverters, string(T)) do (x)-> JProxy(x) end)(obj) @@ -738,6 +741,8 @@ function getConstructor(class::Type, argTypes...) jcall(classfortype(class), "getConstructor", JConstructor, (Vector{JClass},), collect(argTypes)) end +getConstructors(class::Type) = jcall(classfortype(class), "getConstructors", Array{JConstructor}, ()) + methodInfo(class::AbstractString, name::AbstractString, argTypeNames::Array) = methodCache[(class, name, argTypeNames)] function methodInfo(m::Union{JMethod, JConstructor}) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) @@ -783,7 +788,7 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) JavaTypeInfo(Symbol($(string(jclass))), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) @jnicall(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), - (field.static ? field.owner : obj).ptr, field.id, $(object ? :(value.ptr) : :value)) + (field.static ? field.owner.ptr : pxyptr(obj)), field.id, $(object ? :(pxyptr(value)) : :value)) end end end @@ -799,6 +804,7 @@ function initProxy() :long => (static=:CallStaticLongMethodA, instance=:CallLongMethodA), :float => (static=:CallStaticFloatMethodA, instance=:CallFloatMethodA), :double => (static=:CallStaticDoubleMethodA, instance=:CallDoubleMethodA), + :Nothing => (static=:CallStaticVoidMethodA, instance=:CallVoidMethodA), ) global objectClass = classforname("java.lang.Object") global classClass = classforname("java.lang.Class") @@ -823,6 +829,7 @@ function initProxy() global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true, Object) global methodId_object_getClass = getmethodid("java.lang.Object", "getClass", "java.lang.Class") global methodId_class_getName = getmethodid("java.lang.Class", "getName", "java.lang.String") + global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) for info in (t->typeInfo[string(t)]).(:(int, long, byte, boolean, char, short, float, double).args) infoName = string(info.classname) boxVar = Symbol(infoName * "Box") @@ -857,12 +864,15 @@ function initProxy() end) end end - #println("BOX METHOD FOR ", box.boxType) - #println(expr) + #verbose("BOX METHOD FOR ", box.boxType) + #verbose(expr) eval(expr) end end +#jgc() = @staticmessage(Nothing, methodId_system_gc) +jgc() = staticcall(methodId_system_gc, Nothing, ()) + metaclass(class::AbstractString) = metaclass(Symbol(class)) getclass(obj::Ptr{Nothing}) = @message(obj, Object, methodId_object_getClass) @@ -879,7 +889,7 @@ getmethodid(static, cls::JClass, name, rettype, argtypes) = getmethodid(static, function getmethodid(static::Bool, clsname::AbstractString, name::AbstractString, rettype::AbstractString, argtypes::Vector{<:Union{JClass, AbstractString}}) sig = proxyMethodSignature(rettype, argtypes) jclass = metaclass(clsname) - #println(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + #verbose(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, # (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), # jclass, name, sig)) @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, @@ -962,7 +972,7 @@ function infoFor(class::JClass) nothing else name = legalClassName(class) - #println("INFO FOR ", name) + #verbose("INFO FOR ", name) haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class) end end @@ -976,7 +986,7 @@ function classforlegalname(n::AbstractString) try (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) catch x - #println("Error finding class: $n, type: $(typeof(n))") + #verbose("Error finding class: $n, type: $(typeof(n))") throw(x) end end @@ -1037,19 +1047,99 @@ function Base.getproperty(p::JProxy{T}, name::Symbol) where T if haskey(info.methods, name) JMethodProxy(name, T, p, info.methods[name]) else - finfo = info.fields[name] - asJulia(finfo.typeInfo, getproxyfield(p, info, finfo)) + getproxyfield(p, info.fields[name]) end end -getproxyfield(p::JProxy, info::JClassInfo, field::JReadonlyField) = field.get(pxyptr(p)) -function getproxyfield(p::JProxy, info::JClassInfo, field::JFieldInfo) +getter(field::JFieldInfo) = field.static ? field.typeInfo.staticGetter : field.typeInfo.getter + +setter(field::JFieldInfo) = field.static ? field.typeInfo.staticSetter : field.typeInfo.setter + +getproxyfield(p::JProxy, field::JReadonlyField) = field.get(pxyptr(p)) +function getproxyfield(p::JProxy, field::JFieldInfo) static = pxystatic(p) ptr = pxyptr(p) - asJulia(field.typeInfo.juliaType, @jnicall(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{Nothing}), - static ? getclass(ptr) : ptr, field.id)) -end + result = _getproxyfield(p, field) + geterror() + verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) + asJulia(field.typeInfo.convertType, result) +end +_getproxyfield(p::JProxy, field::JFieldInfo) = @jnicall(getter(field), Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{Bool}) = @jnicall(getter(field), jboolean, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jbyte}) = @jnicall(getter(field), jbyte, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jchar}) = @jnicall(getter(field), jchar, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jshort}) = @jnicall(getter(field), jshort, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jint}) = @jnicall(getter(field), jint, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jlong}) = @jnicall(getter(field), jlong, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jfloat}) = @jnicall(getter(field), jfloat, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) +_getproxyfield(p::JProxy, field::JFieldInfo{jdouble}) = @jnicall(getter(field), jdouble, + (Ptr{Nothing}, Ptr{Nothing}), + field.static ? C_NULL : pxyptr(p), field.id) + +function setproxyfield(p::JProxy, field::JFieldInfo{T}, value) where T + primsetproxyfield(p, field, convert(T, value)) +end +setproxyfield(p::JProxy, field::JFieldInfo, value::JProxy) = primsetproxyfield(p, field, pxyptr(value)) +setproxyfield(p::JProxy, field::JFieldInfo, value::JavaObject) = primsetproxyfield(p, field, value.ptr) +function setproxyfield(p::JProxy, field::JFieldInfo{String}, value::AbstractString) + str = JString(convert(String, value)) + primsetproxyfield(p, field, str.ptr) +end +function primsetproxyfield(p::JProxy, field::JFieldInfo, value) + result = _setproxyfield(pxystatic(p) ? C_NULL : pxyptr(p), field, value) + geterror() + verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) + asJulia(field.typeInfo.convertType, result) +end +function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{JavaObject{T}}, value::Ptr{Nothing}) where T + @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + p, field.id, value) +end +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{String}, value::Ptr{Nothing}) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{Bool}, value::jboolean) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jboolean), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jbyte}, value::jbyte) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jbyte), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jchar}, value::jchar) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jchar), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jshort}, value::jshort) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jshort), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jint}, value::jint) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jint), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jlong}, value::jlong) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jlong), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jfloat}, value::jfloat) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jfloat), + p, field.id, value) +_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jdouble}) = @jnicall(setter(field), Nothing, + (Ptr{Nothing}, Ptr{Nothing}, jdouble), + p, field.id, value) + function Base.setproperty!(p::JProxy, name::Symbol, value) info = pxyinfo(p) @@ -1058,10 +1148,12 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) result = if meths != nothing throw(JavaCallError("Attempt to set a method")) else - field = info.fields[name] - value = convert(field.typeInfo.primitive ? field.typeInfo.juliaType : field.typeInfo.class, value) - result = field.typeInfo.setterFunc(field, obj, value) - result == C_NULL && geterror() + #field = info.fields[name] + #value = convert(field.typeInfo.primitive ? field.typeInfo.juliaType : field.typeInfo.class, value) + #result = field.typeInfo.setterFunc(field, p, value) + #result == C_NULL && geterror() + #value + setproxyfield(p, info.fields[name], value) value end isa(result, JavaObject) ? JProxy(result) : result @@ -1069,19 +1161,32 @@ end function (pxy::JMethodProxy{N})(args...) where N targets = Set(m for m in pxy.methods if fits(m, args)) - #println("LOCATING MESSAGE ", N, " FOR ARGS ", repr(args)) + #verbose("LOCATING MESSAGE ", N, " FOR ARGS ", repr(args)) if !isempty(targets) # Find the most specific method argTypes = typeof(args).parameters meth = reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) - #println("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType) - #withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.juliaType, meth.argTypes, args...)) do result - withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.juliaType, meth.dynArgTypes, args...)) do result - asJulia(meth.typeInfo.convertType, result) + verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) + #withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...)) do result + # #asJulia(meth.typeInfo.convertType, result) + # result + #end + if meth.static + staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) + else + call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) end end end +function findmethod(pxy::JMethodProxy, args::Tuple) + targets = Set(m for m in pxy.methods if fits(m, args)) + if !isempty(targets) + argTypes = typeof(args).parameters + reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) + end +end + withlocalref(func, result::Any) = func(result) function withlocalref(func, ptr::Ptr{Nothing}) ref = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) @@ -1136,8 +1241,8 @@ isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 function moreGeneral(argType::Type, c1::JClass, t1::Type, c2::JClass, t2::Type) p1 = t1 <: JPrimitive p2 = t2 <: JPrimitive - g1 = !p1 ? 0 : argType <: t1 ? 1 : -1 - g2 = !p2 ? 0 : argType <: t2 ? 1 : -1 + g1 = !p1 ? 0 : argType <: t1 ? -1 : 1 + g2 = !p2 ? 0 : argType <: t2 ? -1 : 1 g = if !p1 && p2 || jcall(c1, "isAssignableFrom", jboolean, (@jimport(java.lang.Class),), c2) != 0 1 else @@ -1148,11 +1253,11 @@ end function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes::Tuple, args...) where T ptr == C_NULL && error("Attempt to call method on Java NULL") - + verbose("CALL METHOD RETURNING ", rettype) savedargs, convertedargs = convert_args(argtypes, args...) result = _call(T, ptr, mId, convertedargs) result == C_NULL && geterror() - asJulia(rettype, convert_result(rettype, result)) + asJulia(rettype, result) end _call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, @@ -1182,43 +1287,49 @@ _call(::Type{jfloat}, obj, mId, args) = ccall(jnifunc.CallFloatMethodA, jfloat, _call(::Type{jdouble}, obj, mId, args) = ccall(jnifunc.CallDoubleMethodA, jdouble, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), penv, obj, mId, args) +_call(::Type{Nothing}, obj, mId, args) = ccall(jnifunc.CallNothingMethodA, Nothing, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) -function staticcall(class::Ptr{Nothing}, mId, rettype::Type{T}, argtypes::Tuple, args...) where T +function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T savedargs, convertedargs = convert_args(argtypes, args...) - result = _staticcall(T, class, mId, convertedargs) - #println("CONVERTING RESULT ", repr(result), " TO ", rettype) + result = _staticcall(T, mId, convertedargs) + verbose("CONVERTING RESULT ", repr(result), " TO ", rettype) result == C_NULL && geterror() - #println("RETTYPE: ", rettype) - asJulia(rettype, convert_result(rettype, result)) + verbose("RETTYPE: ", rettype) + asJulia(rettype, result) end -_staticcall(::Type{Any}, obj, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, +_staticcall(::Type, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{Bool}, obj, mId, args) = ccall(jnifunc.CallStaticBooleanMethodA, jboolean, + penv, C_NULL, mId, args) +_staticcall(::Type{Bool}, mId, args) = ccall(jnifunc.CallStaticBooleanMethodA, jboolean, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jbyte}, obj, mId, args) = ccall(jnifunc.CallStaticByteMethodA, jbyte, + penv, C_NULL, mId, args) +_staticcall(::Type{jbyte}, mId, args) = ccall(jnifunc.CallStaticByteMethodA, jbyte, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jchar}, obj, mId, args) = ccall(jnifunc.CallStaticCharMethodA, jchar, + penv, C_NULL, mId, args) +_staticcall(::Type{jchar}, mId, args) = ccall(jnifunc.CallStaticCharMethodA, jchar, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jshort}, obj, mId, args) = ccall(jnifunc.CallStaticShortMethodA, jshort, + penv, C_NULL, mId, args) +_staticcall(::Type{jshort}, mId, args) = ccall(jnifunc.CallStaticShortMethodA, jshort, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jint}, obj, mId, args) = ccall(jnifunc.CallStaticIntMethodA, jint, + penv, C_NULL, mId, args) +_staticcall(::Type{jint}, mId, args) = ccall(jnifunc.CallStaticIntMethodA, jint, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jlong}, obj, mId, args) = ccall(jnifunc.CallStaticLongMethodA, jlong, + penv, C_NULL, mId, args) +_staticcall(::Type{jlong}, mId, args) = ccall(jnifunc.CallStaticLongMethodA, jlong, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jfloat}, obj, mId, args) = ccall(jnifunc.CallStaticFloatMethodA, jfloat, + penv, C_NULL, mId, args) +_staticcall(::Type{jfloat}, mId, args) = ccall(jnifunc.CallStaticFloatMethodA, jfloat, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_staticcall(::Type{jdouble}, obj, mId, args) = ccall(jnifunc.CallStaticDoubleMethodA, jdouble, + penv, C_NULL, mId, args) +_staticcall(::Type{jdouble}, mId, args) = ccall(jnifunc.CallStaticDoubleMethodA, jdouble, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) + penv, C_NULL, mId, args) +_staticcall(::Type{Nothing}, mId, args) = ccall(jnifunc.CallStaticVoidMethodA, Nothing, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, C_NULL, mId, args) function Base.show(io::IO, pxy::JProxy) if pxystatic(pxy) diff --git a/test/Test$TestInner.class b/test/Test$TestInner.class index 4f1b61f3d788396d9f6bf379436564d716c7e175..e56f4f75a5b74f587cf159a548581be987ebecad 100644 GIT binary patch delta 19 acmeBR?qJ^F!pImj*_Ba)F=TQcqZ9x$=LFFJ delta 19 acmeBR?qJ^F!pImn*_Ba)F?ezwqZ9x$*96Z1 diff --git a/test/Test.class b/test/Test.class index 1d7d8b0276cd59a9fc3f1ed96a8eb73165ea26c4..8a20f564a3ba84bf75f1ea4a5be447905b5b4d49 100644 GIT binary patch literal 3133 zcmb7FSyxk66#g!mU`mzwOxbdnRuZivJF1+Ew1s5(#@QkaebXeEbqy*Vq`{PO~rKb3E z(lFwxqK7m!9+M!Qq)OA%>a?27tHKgiq|!-EpSCoCT90*iRQ@a#o#6 zS8zFE8tCHe(sV7|Ey3mw9G774H)5)U>Jd#>M>BJiYI0nej8o%DGu_CHkxbLc9|=Sx zxP&UKn~HFN8F__bz{r>YyMG`sAi*tkBZfXLV21*SET&N0P)uMb5VC-|tR=Mj%a0N$ zS6S4fnRr}6gWvMVq_ueGK_xYFSV{17wIX_NGN~-|rgBG?P@nfTYgH(Ockuj_nn-Jg zo^s($mSE{HCV|L5aEd1~Idi8p4cn=6&{VMH4pOi+3R|W8R~C{R(NZkNs(fPRAxyk5 z@ED$mHmxh^Op=Y*RN1SmJVG~-_Blnj8CYWFp}a{R*8PsRVb^3n$oMtyE0@L zvB?FM5Yt%PVi}*}GZ{lTDC00jWrV?t;x(h2iOV>GqnwaZ;=YU)kBJw=Au8jS@j1Q_ z;!Eb&(ZZ)AAVbFtPay0?ndN(Mov^eCVLe7|W)RKjJf^2Ii3D%F8XMH7j2IuA!W}81 ziY%nk>KvaCBf}UCxk)6!QNB-NHdJMfqnJB*v6_h|gcA#7u5r$)csMu%;O5wZw3TZFaW!{u{l;o;hpg}3QafyPT0Kqh=8p`5n=3#}#8 zM<~a$0JWK<%cHhuv7-H(lBkV@t|D|8BiDeg%R*lFRSC42&^3heW)z^g;w_Kbn#Ic2 zuS=jUgsvx)x91wrU>4T}zb%1oAhey(TQFupo4ATkm9*sRG<8AxMKI1vh}QO)!@$-$ zenItc(B`Z0)n?&~1ns^$Yqvz;qcgCD4en&Xt+*cBaEux;Psd3+ffE+(PWp%hRy%)o zMQ!z{AB)=S(Kr@$)MNG7aM=NNacp)MaYb>`;)-y91lJy>*c7!@WQ;`ZmcH4HwAPGt z!&sy|)IMH;{Y9Zp;Z`fuAm>$t3NY0AsEx8U%94=XR~MnQ6gOIma`GN*(LNFM)tO6aod_~rd5U8S!D&2Em9CmDJ4lMMHYclyEl-w6l@A5EXuSQ(_lIivmkZf_k9QV z-4E_ZLKXG9e)W^zJjegx@p&hcv{~dFC+FOE=HB}}_rCYu_s#GB{PZh;-T1;sp?k3l zh6gFJ-X%oZiwv?J7gpbwkykOvRD`hUDAo=dL& z--<7P>@puW9&z>mSycYT=zLuLe;2Kfy78C?kGt`N51XM2@uZKbJSD`_LOkQavtoVD zjpu#ngzmu$VtrAFmppjcgIC1st8TpJ#_MjpA;CMXWwK#i*U}PXa~)2qnT(dC-!2}6$y*jQl9VhSY<)dYqDAq!Z@T0)1vdMklyl|?<7OC}{W`7MuJ zHj(T;q-N$1t10fTK|~)+r`5&5Okv9s8jHSWtxARPjb513QrU!|XWV#`C0O2!Ng(nM zoZ^m5&cZHD!%pg)G?i?Hjg)N7!d5H))urSn5*Zd_Z80%(6DD35xD9V45!cmhF3m=C z*7oYHj?hP>V?j;m5}N%rG7$ZC#kE2U*?xA=m9ah@{;)86X)gAqyl$l#^Ab{YX{yya zi>6q9;a_|&^S3%7!g{hskIn(f>!23I?>eIY&&f^%v+HN=h{ z(vnGo1w1w#+9%^fd_>)hkrt=VCR)9wMnxsmSDr#1lY@p4n_Xn<)|kff?#lQWpU4=& zsEjd;%Lw5h4}=-rOkBnU4s$pviHCBDWK7%?PEi!Uj8E~I5T7%@ZLPc_0y6FdqdJ7W zEVE+yuM?JbA*@TM-3*d2x{v9ZTq?!GuEj?6IU~kt@#q&va2!n zrRF%DMq-Wzxd{?p8VkzM>h0_>2)Ao5KT18AH_pTid$KU0owc7BjLH;`3tTa589PFr9O{_NYJ4u*4r{cp{-qT zVmkwNV;y#2ieNzzA?-AdS+u+9BWSO8{p^m~8?k08>S#pMRMgpswNv9&8`#Zy?8Idzs?8sJ$j*BwF%)g)3V_Vt9%=7C(i)C`z*$0#}YbbUU63yvQCnE zfu8)ovHS Date: Mon, 15 Oct 2018 09:44:57 +0300 Subject: [PATCH 19/43] improving value conversion to prepare to handle array parameters --- src/proxy.jl | 192 ++++++++++++++++-------------------------------- test/Test.class | Bin 3133 -> 3487 bytes test/Test.java | 16 ++++ 3 files changed, 79 insertions(+), 129 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 3a417ff..a428dd8 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -2,6 +2,13 @@ import Base.== +global useVerbose = false + +setVerbose() = global useVerbose = true +clearVerbose() = global useVerbose = false + +verbose(args...) = useVerbose && println(args...) + abstract type java_lang end classnamefor(t::Type{<:java_lang}) = classnamefor(nameof(t)) @@ -12,11 +19,16 @@ function classnamefor(s::AbstractString) replace(s, "_" => ".") end +_defjtype(a::Type, b::Type) = _defjtype(nameof(a), nameof(b)) function _defjtype(a, b) symA = Symbol(a) + verbose("DEFINING ", string(symA), " ", quote + abstract type $symA <: $b end + $symA + end) eval(quote - abstract type $symA <: $b end - types[Symbol($(classnamefor(a)))] = $symA + abstract type $symA <: $b end + types[Symbol($(classnamefor(a)))] = $symA end) end @@ -214,13 +226,11 @@ end struct GenArgInfo name::Symbol javaType::Type - #juliaType::Union{Type,Expr} juliaType spec end const JLegalArg = Union{Number, String, JProxy, Array{Number}, Array{String}, Array{JProxy}} - const methodsById = Dict() const genned = Set() const emptyset = Set() @@ -231,44 +241,12 @@ const boxers = Dict() const juliaConverters = Dict() global jnicalls = Dict() const defaultjnicall = (instance=:CallObjectMethod,static=:CallStaticObjectMethod) -global useVerbose = false - -#function classnamefor(t::Type{<:java_lang}) -# s = replace(string(nameof(t)), "___" => "_") -# s = replace(s, "_s_" => "\$") -# Symbol(replace(s, "_" => ".")) -#end -# -#const types = Dict([classnamefor(t) => t for t in [ -# java_lang_Object -# java_util_AbstractCollection -# java_lang_Number -# java_lang_Double -# java_lang_Float -# java_lang_Long -# java_lang_Integer -# java_lang_Short -# java_lang_Byte -# java_lang_Character -# java_lang_Boolean -#]]) - -#const types = Dict([ -# Symbol("java.lang.Object") => java_lang_Object, -# Symbol("java.util.AbstractCollection") => java_util_AbstractCollection, -# Symbol("java.lang.String") => String, -#]) const dynamicTypeCache = Dict() global genericFieldInfo global objectClass global sigTypes -setVerbose() = global useVerbose = true -clearVerbose() = global useVerbose = false - -verbose(args...) = useVerbose && println(args...) - macro jnicall(func, rettype, types, args...) quote local result = ccall($(esc(func)), $(esc(rettype)), @@ -285,7 +263,6 @@ macro message(obj, rettype, methodid, args...) flush(stdout) quote result = ccall(jnifunc.$func, Ptr{Nothing}, - #(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((typeof(arg) for arg in args)...)), penv, $(esc(obj)), $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() @@ -321,11 +298,12 @@ end function finalizeproxy(pxy::JProxy) ptr = pxyptr(pxy) if ptr == C_NULL || penv == C_NULL; return; end - #ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) @jnicall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{Nothing},), ptr) setfield!(pxy, :ptr, C_NULL) #Safety in case this function is called direcly, rather than at finalize end +arraycomponent(::Type{Array{T}}) where T = T + signatureClassFor(name) = length(name) == 1 ? sigTypes[name].classname : name isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing @@ -357,7 +335,7 @@ function JProxy(ptr::Ptr{Nothing}) n = legalClassName(getname(cls)) end c = Symbol(n) - #verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) + verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) info = infoFor(cls) aType, dim = arrayinfo(n) if dim != 0 @@ -416,10 +394,7 @@ hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodDicts, string(n function makeType(name::AbstractString, supername::Symbol, gen) if string(name) != "String" && !haskey(types, Symbol(name)) && !haskey(gen.methodDicts, name) typeName = typeNameFor(name) - push!(gen.typeCode, - :(abstract type $typeName <: $supername end), -# :(types[Symbol($name)] = $typeName) - ) + push!(gen.typeCode, :(abstract type $typeName <: $supername end)) end end @@ -427,7 +402,7 @@ function registerclass(name::AbstractString, classType::Type) registerclass(Symbol(name), classType) end function registerclass(name::Symbol, classType::Type) - if !haskey(types, name) + if !(classType <: Union{Array, String}) && !haskey(types, name) types[name] = classType end infoFor(classforname(string(name))) @@ -507,7 +482,6 @@ end argType(t, gen) = t argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String -#argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = JLegalArg argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JLegalArg argType(::Type{<: Number}, gen) = Number argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) @@ -519,7 +493,6 @@ argSpec(::Type{<: Number}, gen) = Number argSpec(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) argSpec(arg::GenArgInfo) = arg.spec -#legalClassName(cls::Ptr{Nothing}) = legalClassName(getclassname(getclass(cls))) legalClassName(pxy::JProxy) = legalClassName(getclassname(pxystatic(pxy) ? pxyptr(pxy) : getclass(pxyptr(pxy)))) legalClassName(cls::JavaObject) = legalClassName(getname(cls)) legalClassName(cls::Symbol) = legalClassName(string(cls)) @@ -541,6 +514,12 @@ end componentType(e::Expr) = e.args[2] componentType(sym::Symbol) = sym +""" + typeNameFor(thing) + +Attempt to return the type for thing, otherwise return a symbol +representing the type, should it come to exist +""" typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) function typeNameFor(T::AbstractString, gen::GenInfo) aType, dims = arrayinfo(T) @@ -551,6 +530,8 @@ function typeNameFor(T::AbstractString, gen::GenInfo) end typeNameFor(T) end +typeNameFor(t::Type) = t +typeNameFor(::Type{JavaObject{T}}) where T = typeNameFor(string(T)) typeNameFor(class::JClass) = typeNameFor(legalClassName(class)) typeNameFor(className::Symbol) = typeNameFor(string(className)) function typeNameFor(className::AbstractString) @@ -564,13 +545,14 @@ function typeNameFor(className::AbstractString) n = replace(n, "." => "_") aType, dims = arrayinfo(n) if dims != 0 - :(Array{$(typeNameFor(aType)), $(length(dims))}) + Array{typeNameFor(aType), dims} else t = get(typeInfo, n, genericFieldInfo) if t.primitive t.juliaType else - Symbol(n) + sn = Symbol(n) + get(types, sn, sn) end end end @@ -583,7 +565,6 @@ end function argCode(arg::GenArgInfo) argname = arg.name if arg.juliaType == String - #:(JString($argname)) argname elseif arg.juliaType == JLegalArg :(box($argname)) @@ -600,7 +581,6 @@ function fieldEntry((name, fld)) end function genMethods(class, gen, info) - #push!(genned, Symbol(typeNameFor(legalClassName(class)))) methodList = listmethods(class) classname = legalClassName(class) gen.methodDicts[classname] = methods = Dict() @@ -653,7 +633,6 @@ end genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) genConvertResult(toType::Type{String}, info, expr) = :(unsafe_string($expr)) -#genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($expr)) genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($(toType.parameters[1]), $expr)) function genConvertResult(toType, info, expr) if isVoid(info) || info.typeInfo.primitive @@ -671,16 +650,13 @@ function JClassInfo(class::JClass) parentinfo = !isNull(sc) ? infoFor(sc) : nothing tname = typeNameFor(string(n)) #verbose("JCLASS INFO FOR ", n) - jtype = get!(types, n) do - if tname != String - #verbose("DEFINING ", repr(tname), quote - # abstract type $tname <: JavaCall.$(isNull(sc) ? :java_lang : typeNameFor(Symbol(legalClassName(sc)))) end - # $tname - #end) - JavaCall.eval(quote - abstract type $tname <: JavaCall.$(isNull(sc) ? :java_lang : typeNameFor(Symbol(legalClassName(sc)))) end - $tname - end) + jtype = if tname == String + String + elseif isa(tname, Type) && tname <: Array + tname + else + get!(types, n) do + _defjtype(tname, isNull(sc) ? java_lang : typeNameFor(Symbol(legalClassName(sc)))) end end classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) @@ -688,6 +664,7 @@ end genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) +typeFor(::Type{JavaObject{T}}) where T = typeFor(T) function typeFor(sym::Symbol) aType, dims = arrayinfo(string(sym)) dims != 0 ? Array{get(types, Symbol(aType), java_lang), length(dims)} : get(types, sym, java_lang) @@ -707,8 +684,7 @@ end box(str::AbstractString) = str box(pxy::JProxy) = ptrObj(pxy) -#function box(array::Array{T,N}) -#end + unbox(obj) = obj unbox(::Type{T}, obj) where T = obj function unbox(::Type{JavaObject{T}}, obj::Ptr{Nothing}) where T @@ -870,9 +846,6 @@ function initProxy() end end -#jgc() = @staticmessage(Nothing, methodId_system_gc) -jgc() = staticcall(methodId_system_gc, Nothing, ()) - metaclass(class::AbstractString) = metaclass(Symbol(class)) getclass(obj::Ptr{Nothing}) = @message(obj, Object, methodId_object_getClass) @@ -898,11 +871,6 @@ function getmethodid(static::Bool, clsname::AbstractString, name::AbstractString end function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} - #id = ccall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, - # (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - # penv, metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) - #id == C_NULL && geterror(true) - #id @jnicall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) @@ -913,25 +881,6 @@ function infoSignature(cls::AbstractString) if info != nothing; info.signature; end end -#function proxyClassSignature(cls::AbstractString) -# sig = infoSignature(cls) -# sig != nothing ? sig : proxyClassSignature(classforname(cls)) -#end -#function proxyClassSignature(cls::JClass) -# info = get(typeInfo, getname(cls), nothing) -# if info != nothing && info.primitive -# info.signature -# else -# sig = [] -# while jcall(cls, "isArray", jboolean, ()) != 0 -# push!(sig, "[") -# cls = jcall(cls, "getComponentType", JClass, ()) -# end -# clSig = infoSignature(jcall(cls, "getSimpleName", JString, ())) -# push!(sig, clSig != nothing ? clSig : "L" * javaclassname(getname(cls)) * ";") -# join(sig, "") -# end -#end proxyClassSignature(cls::JClass) = proxyClassSignature(legalClassName(cls)) function proxyClassSignature(clsname::AbstractString) info = get(typeInfo, clsname, nothing) @@ -980,8 +929,6 @@ end getname(thing::Union{JClass, JMethod, JField}) = jcall(thing, "getName", JString, ()) getname(thing::JConstructor) = "" -#classforlegalname(n::AbstractString) = (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) - function classforlegalname(n::AbstractString) try (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) @@ -1005,11 +952,8 @@ function fielddict(class::JClass) end end -#arraylength(obj) = ccall(jnifunc.GetArrayLength, jint, (Ptr{JNIEnv}, Ptr{Nothing}), penv, obj) arraylength(obj) = jni(jnifunc.GetArrayLength, jint, (Ptr{Nothing}), obj) -#function Base.getindex(pxy::JProxy{Array{T}}, I::Vararg{Int, N}) where {T, N} -#end function Base.getindex(pxy::JProxy{Array{T, 1}}, i::Int) where T asJulia(JObject, JObject(@jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, (Ptr{Nothing}, jint), @@ -1148,11 +1092,6 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) result = if meths != nothing throw(JavaCallError("Attempt to set a method")) else - #field = info.fields[name] - #value = convert(field.typeInfo.primitive ? field.typeInfo.juliaType : field.typeInfo.class, value) - #result = field.typeInfo.setterFunc(field, p, value) - #result == C_NULL && geterror() - #value setproxyfield(p, info.fields[name], value) value end @@ -1167,19 +1106,17 @@ function (pxy::JMethodProxy{N})(args...) where N argTypes = typeof(args).parameters meth = reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) - #withlocalref((meth.static ? staticcall : call)(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...)) do result - # #asJulia(meth.typeInfo.convertType, result) - # result - #end if meth.static staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) else call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) end + else + throw(ArgumentError("No $N method for argument types $(typeof.(args))")) end end -function findmethod(pxy::JMethodProxy, args::Tuple) +function findmethod(pxy::JMethodProxy, args...) targets = Set(m for m in pxy.methods if fits(m, args)) if !isempty(targets) argTypes = typeof(args).parameters @@ -1204,22 +1141,26 @@ end fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.dynArgTypes, args)) -#canConvert(::Type{T}, ::T) where T = true -canConvert(::Type{JavaObject{Symbol("java.lang.Object")}}, ::Union{AbstractString, Real}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Double")}}, ::Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Float")}}, ::Union{Float32, Float16, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Long")}}, ::Union{Int64, Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Integer")}}, ::Union{Int32, Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Short")}}, ::Union{Int16, Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Byte")}}, ::Union{Int8}) = true -canConvert(::Type{JavaObject{Symbol("java.lang.Character")}}, ::Union{Int8, Char}) = true -canConvert(::Type{String}, ::AbstractString) = true -canConvert(::Type{JString}, ::AbstractString) = true -canConvert(::Type{<: Real}, ::T) where {T <: Real} = true -canConvert(::Type{jboolean}, ::Bool) = true -canConvert(::Type{jchar}, ::Char) = true -canConvert(::Type{<:Integer}, ::Ptr{Nothing}) = true -canConvert(x, y) = false +canConvert(::Type{T}, ::T) where T = true +canConvert(t::Type, ::T) where T = canConvertType(t, T) +canConvert(t::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertType(T1, T2) + +canConvertType(::Type{T}, ::Type{T}) where T = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Object")}, java_lang_Object}}, ::Type{<:Union{AbstractString, Real}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Double")}, java_lang_Double}}, ::Type{<:Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Float")}, java_lang_Float}}, ::Type{<:Union{Float32, Float16, Int32, Int16, Int8}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Long")}, java_lang_Long}}, ::Type{<:Union{Int64, Int32, Int16, Int8}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Integer")}, java_lang_Integer}}, ::Type{<:Union{Int32, Int16, Int8}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Short")}, java_lang_Short}}, ::Type{<:Union{Int16, Int8}}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Byte")}, java_lang_Byte}}, ::Type{Int8}) = true +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Character")}, java_lang_Character}}, ::Type{<:Union{Int8, Char}}) = true +canConvertType(::Type{<:AbstractString}, ::Type{<:AbstractString}) = true +canConvertType(::Type{JString}, ::Type{<:AbstractString}) = true +canConvertType(::Type{<: Real}, ::Type{<:Real}) = true +canConvertType(::Type{jboolean}, ::Type{Bool}) = true +canConvertType(::Type{jchar}, ::Type{Char}) = true +canConvertType(x, y) = false + convert(::Type{JObject}, pxy::JProxy) = JavaObject(pxy) # score relative generality of two methods as applied to a particular set of arguments @@ -1331,13 +1272,6 @@ _staticcall(::Type{Nothing}, mId, args) = ccall(jnifunc.CallStaticVoidMethodA, N (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), penv, C_NULL, mId, args) -function Base.show(io::IO, pxy::JProxy) - if pxystatic(pxy) - print(io, "static class $(legalClassName(getclassname(pxyptr(pxy))))") - else - print(io, pxy.toString()) - #print(io, Java.toString(pxy)) - end -end +Base.show(io::IO, pxy::JProxy) = print(io, pxystatic(pxy) ? "static class $(legalClassName(pxy))" : pxy.toString()) JavaObject(pxy::JProxy{T, C}) where {T, C} = JavaObject{C}(pxyptr(pxy)) diff --git a/test/Test.class b/test/Test.class index 8a20f564a3ba84bf75f1ea4a5be447905b5b4d49..64f491f5e931af819362db976871d8ab711195a5 100644 GIT binary patch delta 1525 zcmZvc+jEmu6vfv`n|>!BhpE(QB9<_*7JDJn;z+TyDh9AkYXzl6ffyt$v9yL%(}1>! z6})osQa|svisB7LsHGL0(Klaw@WnsCcgMfMaqW}NBn|dq?K5Zn&OUqZ^QFJsG+USa z{okK|1+Wpt$5KaAZJAVdq;2QWSb8|$CGfO6Fn%OAobDdYq!j{# z%-AZR!F}j9xEuEvY{9(>Iu;oi+||3I#~_BS2HUXR!M6q%@SQ?+Z#L4$Q_)B+J$$e; z;^2FOi^v-MfH9FvB9}$xM6wFL#8w4={DyQUGp8)jThhm)c9cz;(#Ron|XVW$^;cBjj! zUmYjq!bdfz#!3XFk^ga5(XFA&=Bzun>}-&3i0)22xa5W!)-J-Vy=aRmUQ$PJ4M7gd zf{nVggb3YR=yG1x9n$r&;@Nd{Z=<^#4~xspH#5qgPhF-e2wP?MTF5i^ z!vV|v8R#tGwxE$uReEm64tve(pSh%n~1Fl-c@Q zuE1YD+!U?D$S$G9)aw`e;V;jfW^So?oUc)+r8IX{dG1j{PvR*-y-O=XCEw>^CXze( zi?QxsD>j$3D<>w97l&onF-p4?H5IATH35J8sFRuOsg) ak_GSti+YA-`7nvQ7+oIeh?)B}p*Rb23q%Tv$-+q7o3Lp#pHnP!X@vU8? z!^A$Z`$Y~|IEX_g4%;|_qaw#dj*FZS=@dC>;FO8eCeGMM!C4dMy2HupeD~4h&;`N!WFr-ABA0Cjz9Mo}Ths9D0on9lDwt{Pk@KR&>_+Y_6 zJWO&HpOq+b$$;f|63n=1Lfi0AHK_?CnD>8(i6kbGU_CC;YNkZ(Po&pR?`o_W@%mF| zeuAF)p1~Ey4oV_8gCKh|deE+*WLJxo$fzid%{~99DF^Fsfbdhn Date: Mon, 15 Oct 2018 14:37:45 +0300 Subject: [PATCH 20/43] switching methods to use generated arg types --- src/core.jl | 2 + src/proxy.jl | 139 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 101 insertions(+), 40 deletions(-) diff --git a/src/core.jl b/src/core.jl index 662cae9..225d368 100644 --- a/src/core.jl +++ b/src/core.jl @@ -101,6 +101,8 @@ const JClassLoader = JavaObject{Symbol("java.lang.ClassLoader")} const JString = JavaObject{Symbol("java.lang.String")} const jnull = JavaObject(nothing) +JavaObject(ptr::Ptr{Nothing}) = ptr == C_NULL ? JavaObject(ptr) : JavaObject{Symbol(getclassname(getclass(ptr)))}(ptr) + function JString(str::AbstractString) jstring = ccall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, String(str)) if jstring == C_NULL diff --git a/src/proxy.jl b/src/proxy.jl index a428dd8..7e445f9 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -10,6 +10,7 @@ clearVerbose() = global useVerbose = false verbose(args...) = useVerbose && println(args...) abstract type java_lang end +abstract type interface <: java_lang end classnamefor(t::Type{<:java_lang}) = classnamefor(nameof(t)) classnamefor(s::Symbol) = classnamefor(string(s)) @@ -26,10 +27,12 @@ function _defjtype(a, b) abstract type $symA <: $b end $symA end) - eval(quote - abstract type $symA <: $b end - types[Symbol($(classnamefor(a)))] = $symA - end) + get!(types, Symbol(classnamefor(a))) do + eval(quote + abstract type $symA <: $b end + $symA + end) + end end macro defjtype(expr) @@ -78,7 +81,6 @@ const JBoxed = Union{ struct JavaTypeInfo setterFunc - #class::Type{JavaObject{T}} where T # narrowed JavaObject type classname::Symbol # legal classname as a symbol signature::AbstractString juliaType::Type # the Julia representation of the Java type, like jboolean (which is a UInt8), for call-in @@ -129,7 +131,8 @@ struct JClassInfo class::JClass fields::Dict{Symbol, Union{JFieldInfo, JReadonlyField}} methods::Dict{Symbol, Set{JMethodInfo}} - classType::Type + classtype::Type + classtypes::Type end struct JMethodProxy{N, T} @@ -205,7 +208,7 @@ hello ``` """ # mutable because it can have a finalizer -mutable struct JProxy{T<:Union{<:java_lang, Array{<:java_lang}, <:AbstractString, <:Number}, C} +mutable struct JProxy{T, C} ptr::Ptr{Nothing} info::JClassInfo static::Bool @@ -326,6 +329,7 @@ end # like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` JProxy(s::AbstractString) = JProxy(JString(s)) JProxy{T, C}(ptr::Ptr{Nothing}) where {T, C} = JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) +JProxy(obj::JavaObject) = JProxy(obj.ptr) function JProxy(ptr::Ptr{Nothing}) if ptr == C_NULL cls = objectClass @@ -340,22 +344,9 @@ function JProxy(ptr::Ptr{Nothing}) aType, dim = arrayinfo(n) if dim != 0 t = typeFor(Symbol(aType)) - JProxy{Array{typeFor(Symbol(aType)), dim}, c}(ptr, info, false) - else - JProxy{typeFor(c), c}(ptr, info, false) - end -end -function JProxy(obj::JavaObject) - cls = isNull(obj) ? objectClass : getclass(obj) - n = legalClassName(getname(cls)) - c = Symbol(n) - info = infoFor(cls) - aType, dim = arrayinfo(n) - if dim != 0 - t = typeFor(Symbol(aType)) - JProxy{Array{typeFor(Symbol(aType)), dim}, c}(JObject(obj.ptr), info, false) + JProxy{info.classtypes, c}(ptr, info, false) else - JProxy{typeFor(c), c}(obj.ptr, info, false) + JProxy{info.classtypes, c}(ptr, info, false) end end @@ -391,7 +382,7 @@ hasClass(name::Symbol) = name in genned hasClass(gen, name::AbstractString) = hasClass(gen, Symbol(name)) hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodDicts, string(name)) -function makeType(name::AbstractString, supername::Symbol, gen) +function genTypeDecl(name::AbstractString, supername::Symbol, gen) if string(name) != "String" && !haskey(types, Symbol(name)) && !haskey(gen.methodDicts, name) typeName = typeNameFor(name) push!(gen.typeCode, :(abstract type $typeName <: $supername end)) @@ -446,9 +437,9 @@ function genType(class, gen::GenInfo) end supertype = typeNameFor(sc) cType = componentType(supertype) - makeType(name, cType, gen) + genTypeDecl(name, cType, gen) else - makeType(name, :java_lang, gen) + genTypeDecl(name, :java_lang, gen) end end @@ -465,9 +456,9 @@ function genClass(class::JClass, gen::GenInfo, info::JClassInfo) supertype = typeNameFor(sc) cType = componentType(supertype) !hasClass(gen, cType) && genClass(sc, gen) - makeType(name, cType, gen) + genTypeDecl(name, cType, gen) else - makeType(name, :java_lang, gen) + genTypeDecl(name, :java_lang, gen) end genMethods(class, gen, info) end @@ -644,10 +635,16 @@ end isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 +unionize(::Type{T1}, ::Type{T2}) where {T1, T2} = Union{T1, T2} + +classtypes(ct, interfaces) = reduce(unionize, [ct, (i->i.classtype).(interfaces)...]) + function JClassInfo(class::JClass) n = Symbol(legalClassName(class)) + verbose("INFO FOR $(string(n))") sc = superclass(class) parentinfo = !isNull(sc) ? infoFor(sc) : nothing + interfaces = [infoFor(JClass(ptr)) for ptr in allinterfaces(class.ptr)] tname = typeNameFor(string(n)) #verbose("JCLASS INFO FOR ", n) jtype = if tname == String @@ -656,10 +653,10 @@ function JClassInfo(class::JClass) tname else get!(types, n) do - _defjtype(tname, isNull(sc) ? java_lang : typeNameFor(Symbol(legalClassName(sc)))) + _defjtype(tname, tname == Symbol("java.lang.Object") ? java_lang : isNull(sc) ? interface : typeNameFor(Symbol(legalClassName(sc)))) end end - classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) + classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype, classtypes(jtype, interfaces)) end genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) @@ -670,12 +667,24 @@ function typeFor(sym::Symbol) dims != 0 ? Array{get(types, Symbol(aType), java_lang), length(dims)} : get(types, sym, java_lang) end +function makeTypeFor(class::JClass) + cln = Symbol(getname(class)) + t = typeFor(cln) + if t == java_lang + sc = superclass(class) + sct = isNull(sc) ? java_lang : makeTypeFor(sc) + _defjtype(typeNameFor(cln), nameof(sct)) + end + t +end + asJulia(t, obj) = obj asJulia(::Type{Bool}, obj) = obj != 0 asJulia(t, obj::JBoxed) = unbox(obj) function asJulia(x, ptr::Ptr{Nothing}) + verbose("ASJULIA: ", repr(ptr)) if ptr == C_NULL - jnull + nothing else verbose("UNBOXING ", ptr) unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ptr))))}, ptr) @@ -719,6 +728,21 @@ end getConstructors(class::Type) = jcall(classfortype(class), "getConstructors", Array{JConstructor}, ()) +function argtypefor(class::JClass) + cln = getclassname(class.ptr) + tinfo = gettypeinfo(cln) + if tinfo.primitive + tinfo.convertType + elseif cln == "java.lang.String" + String + else + sn = Symbol(cln) + makeTypeFor(class) + cltype = typeFor(sn) + JProxy{cltype, sn} + end +end + methodInfo(class::AbstractString, name::AbstractString, argTypeNames::Array) = methodCache[(class, name, argTypeNames)] function methodInfo(m::Union{JMethod, JConstructor}) name, returnType, argTypes = getname(m), getreturntype(m), getparametertypes(m) @@ -729,7 +753,7 @@ function methodInfo(m::Union{JMethod, JConstructor}) typeName = legalClassName(returnType) info = get(typeInfo, typeName, genericFieldInfo) owner = metaclass(legalClassName(cls)) - methodsById[length(methodsById)] = JMethodInfo(name, info, Symbol(typeName), returnType, Tuple(juliaTypeFor.(argTypes)), argTypes, methodId, isStatic(m), owner, get(jnicalls, typeName, Tuple(filterDynArgType.(juliaTypeFor.(argTypes))))) + methodsById[length(methodsById)] = JMethodInfo(name, info, Symbol(typeName), returnType, Tuple(argtypefor.(argTypes)), argTypes, methodId, isStatic(m), owner, get(jnicalls, typeName, Tuple(filterDynArgType.(juliaTypeFor.(argTypes))))) end end @@ -802,9 +826,11 @@ function initProxy() "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object), ) global sigTypes = Dict([inf.signature => inf for (key, inf) in typeInfo if inf.primitive]) - global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object", JObject, Object, true, Object) + global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object;", JObject, Object, true, Object) global methodId_object_getClass = getmethodid("java.lang.Object", "getClass", "java.lang.Class") global methodId_class_getName = getmethodid("java.lang.Class", "getName", "java.lang.String") + global methodId_class_getInterfaces = getmethodid("java.lang.Class", "getInterfaces", "[Ljava.lang.Class;") + global methodId_class_isInterface = getmethodid("java.lang.Class", "isInterface", "boolean") global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) for info in (t->typeInfo[string(t)]).(:(int, long, byte, boolean, char, short, float, double).args) infoName = string(info.classname) @@ -848,9 +874,31 @@ end metaclass(class::AbstractString) = metaclass(Symbol(class)) -getclass(obj::Ptr{Nothing}) = @message(obj, Object, methodId_object_getClass) +getclass(obj::Ptr{Nothing}) = @message(obj, Ptr{Nothing}, methodId_object_getClass) + +getclassname(class::Ptr{Nothing}) = unsafe_string(@message(class, Ptr{Nothing}, methodId_class_getName)) + +isinterface(class::Ptr{Nothing}) = @message(class, jboolean, methodId_class_isInterface) != 0 -getclassname(class::Ptr{Nothing}) = unsafe_string(@message(class, Object, methodId_class_getName)) +getinterfaces(class::Ptr{Nothing}) = jarray(@message(class, Ptr{Nothing}, methodId_class_getInterfaces)) + +jarray(array::Ptr{Nothing}) = [arrayat(array, i) for i in 1:arraylength(array)] + +function allinterfaces(class::Ptr{Nothing}) + result = [] + queue = [class] + seen = Set() + while !isempty(queue) + for interface in getinterfaces(pop!(queue)) + if !(interface in seen) + push!(seen, interface) + push!(result, interface) + push!(queue, interface) + end + end + end + reverse(result) +end function getmethodid(cls::AbstractString, name, rettype::AbstractString, argtypes::AbstractString...) getmethodid(false, cls, name, rettype, collect(argtypes)) @@ -952,12 +1000,21 @@ function fielddict(class::JClass) end end -arraylength(obj) = jni(jnifunc.GetArrayLength, jint, (Ptr{Nothing}), obj) +arraylength(obj::JavaObject) = arraylength(obj.ptr) +arraylength(obj) = @jnicall(jnifunc.GetArrayLength, jint, (Ptr{Nothing},), obj) + +arrayat(obj::JavaObject, i) = arrayat(obj.ptr, i) +arrayat(obj, i) = @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + obj, jint(i) - 1) + +Base.length(obj::JavaObject) = Base.length(JProxy(obj)) +Base.length(pxy::JProxy{>:Array}) = arraylength(pxyptr(pxy)) -function Base.getindex(pxy::JProxy{Array{T, 1}}, i::Int) where T - asJulia(JObject, JObject(@jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, - (Ptr{Nothing}, jint), - pxyptr(pxy), jint(i)))) +function Base.getindex(pxy::JProxy{>:Array}, i::Integer) + asJulia(T, @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + pxyptr(pxy), jint(i) - 1)) end function methoddict(class) @@ -989,7 +1046,9 @@ end function Base.getproperty(p::JProxy{T}, name::Symbol) where T info = pxyinfo(p) if haskey(info.methods, name) - JMethodProxy(name, T, p, info.methods[name]) + m = pxystatic(p) ? filter(m->m.static, info.methods[name]) : info.methods[name] + isempty(m) && throw(KeyError("key: $name not found")) + JMethodProxy(name, T, p, m) else getproxyfield(p, info.fields[name]) end From 97a8bc8d5ea2a05ba722fad595f81daf3c211676 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 22 Oct 2018 14:12:12 +0300 Subject: [PATCH 21/43] localrefs for convert_arg, code cleanup, interfaces --- src/convert.jl | 4 +- src/core.jl | 15 +++ src/proxy.jl | 321 ++++++++++++++++++++++++------------------------- 3 files changed, 172 insertions(+), 168 deletions(-) diff --git a/src/convert.jl b/src/convert.jl index 630334a..7f272b7 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -70,7 +70,7 @@ end function convert_arg(argtype::Type{JString}, arg) x = convert(JString, arg) - return x, x.ptr + return x, allocatelocal(x.ptr) end function convert_arg(argtype::Type, arg) @@ -79,7 +79,7 @@ function convert_arg(argtype::Type, arg) end function convert_arg(argtype::Type{T}, arg) where T<:JavaObject x = convert(T, arg)::T - return x, x.ptr + return x, allocatelocal(x.ptr) end for (x, y, z) in [ (:jboolean, :(jnifunc.NewBooleanArray), :(jnifunc.SetBooleanArrayRegion)), diff --git a/src/core.jl b/src/core.jl index 225d368..b2e60bc 100644 --- a/src/core.jl +++ b/src/core.jl @@ -1,3 +1,4 @@ +const allocatedrefs = [] # jni_md.h const jint = Cint @@ -51,8 +52,22 @@ newglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr deleteglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) +newlocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) +function allocatelocal(ptr::Ptr{Nothing}) + ref = newlocalref(ptr) + push!(allocatedrefs, ref) + ref +end + +function deletelocals() + while !isempty(allocatedrefs) + deletelocalref(pop!(allocatedrefs)) + end +end + function deleteref(x::JavaObject) if x.ptr == C_NULL; return; end if (penv==C_NULL); return; end diff --git a/src/proxy.jl b/src/proxy.jl index 7e445f9..41a48a2 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -132,7 +132,6 @@ struct JClassInfo fields::Dict{Symbol, Union{JFieldInfo, JReadonlyField}} methods::Dict{Symbol, Set{JMethodInfo}} classtype::Type - classtypes::Type end struct JMethodProxy{N, T} @@ -149,6 +148,7 @@ struct Boxing info::JavaTypeInfo boxType::Type boxClass::JClass + boxClassType::Type primClass::JClass boxer::Ptr{Nothing} unboxer::Ptr{Nothing} @@ -343,11 +343,9 @@ function JProxy(ptr::Ptr{Nothing}) info = infoFor(cls) aType, dim = arrayinfo(n) if dim != 0 - t = typeFor(Symbol(aType)) - JProxy{info.classtypes, c}(ptr, info, false) - else - JProxy{info.classtypes, c}(ptr, info, false) + typeFor(Symbol(aType)) end + JProxy{info.classtype, c}(ptr, info, false) end function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) @@ -371,7 +369,7 @@ end function Boxing(info) boxer = methodInfo(getConstructor(info.boxType, info.primClass)).id unboxer = methodInfo(getMethod(info.boxType, info.accessorName)).id - Boxing(info, info.boxType, info.boxClass, info.primClass, boxer, unboxer) + Boxing(info, info.boxType, info.boxClass, types[Symbol(getname(info.boxClass))], info.primClass, boxer, unboxer) end gettypeinfo(class::Symbol) = gettypeinfo(string(class)) @@ -637,7 +635,11 @@ isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 unionize(::Type{T1}, ::Type{T2}) where {T1, T2} = Union{T1, T2} -classtypes(ct, interfaces) = reduce(unionize, [ct, (i->i.classtype).(interfaces)...]) +function definterfacecvt(ct, interfaces) + if !isempty(interfaces) + eval(:(canConvertType(::Type{<:$(reduce(unionize, [i.classtype for i in interfaces]))}, ::Type{$ct}) = true)) + end +end function JClassInfo(class::JClass) n = Symbol(legalClassName(class)) @@ -656,7 +658,8 @@ function JClassInfo(class::JClass) _defjtype(tname, tname == Symbol("java.lang.Object") ? java_lang : isNull(sc) ? interface : typeNameFor(Symbol(legalClassName(sc)))) end end - classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype, classtypes(jtype, interfaces)) + definterfacecvt(jtype, interfaces) + classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) end genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) @@ -738,8 +741,7 @@ function argtypefor(class::JClass) else sn = Symbol(cln) makeTypeFor(class) - cltype = typeFor(sn) - JProxy{cltype, sn} + typeFor(sn) end end @@ -781,11 +783,13 @@ macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object, jBoxType) _typeInf(jclass, ctyp, sig, jtyp, Typ, object, "", "java.lang." * string(jBoxType)) end +sym(s) = :(Symbol($(string(s)))) + function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) quote begin - JavaTypeInfo(Symbol($(string(jclass))), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) + JavaTypeInfo($(sym(jclass)), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) @jnicall(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), (field.static ? field.owner.ptr : pxyptr(obj)), field.id, $(object ? :(pxyptr(value)) : :value)) @@ -794,6 +798,46 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) end end +macro defbox(primclass, boxtype, juliatype, javatype = juliatype) + :(eval(_defbox($(sym(primclass)), $(sym(boxtype)), $(sym(juliatype)), $(sym(javatype))))) +end + +function _defbox(primclass, boxtype, juliatype, javatype) + boxclass = JavaObject{Symbol(classnamefor(boxtype))} + primname = string(primclass) + boxVar = Symbol(primname * "Box") + quote + const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) + function convert(::Type{JavaObject{T}}, obj::$juliatype) where T + box(obj) + end + function box(data::$juliatype) + JProxy(_jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $boxclass, ($juliatype,), data)) + end + function unbox(::Type{$boxclass}, ptr::Ptr{Nothing}) + $(if juliatype == :Bool + :(call(ptr, $boxVar.unboxer, jboolean, ()) != 0) + else + :(call(ptr, $boxVar.unboxer, $javatype, ())) + end) + end + function unbox(::Type{$boxtype}, ptr::Ptr{Nothing}) + $(if juliatype == :Bool + :(call(ptr, $boxVar.unboxer, jboolean, ()) != 0) + else + :(call(ptr, $boxVar.unboxer, $javatype, ())) + end) + end + function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) + $(if juliatype == :Bool + :(_jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0) + else + :(_jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ())) + end) + end + end +end + function initProxy() push!(jnicalls, :boolean => (static=:CallStaticBooleanMethodA, instance=:CallBooleanMethodA), @@ -832,44 +876,14 @@ function initProxy() global methodId_class_getInterfaces = getmethodid("java.lang.Class", "getInterfaces", "[Ljava.lang.Class;") global methodId_class_isInterface = getmethodid("java.lang.Class", "isInterface", "boolean") global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) - for info in (t->typeInfo[string(t)]).(:(int, long, byte, boolean, char, short, float, double).args) - infoName = string(info.classname) - boxVar = Symbol(infoName * "Box") - box = boxers[infoName] = Boxing(info) - expr = quote - $boxVar = boxers[$infoName] - function convert(::Type{JavaObject{T}}, obj::$(info.convertType)) where T - box(obj) - end - function box(data::$(info.convertType)) - _jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $(box.boxType), ($(box.info.juliaType),), data) - end - function unbox(::Type{$(info.boxType)}, ptr::Ptr{Nothing}) - $(if box.info.convertType == Bool - :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ()) != 0) - else - :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ())) - end) - end - function unbox(::Type{$(types[javaType(info.boxType)])}, ptr::Ptr{Nothing}) - $(if box.info.convertType == Bool - :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ()) != 0) - else - :(call(ptr, $boxVar.unboxer, $boxVar.info.juliaType, ())) - end) - end - function unbox(obj::$(info.boxType)) - $(if box.info.convertType == Bool - :(_jcall(obj, $boxVar.unboxer, C_NULL, $(box.info.juliaType), ()) != 0) - else - :(_jcall(obj, $boxVar.unboxer, C_NULL, $(box.info.juliaType), ())) - end) - end - end - #verbose("BOX METHOD FOR ", box.boxType) - #verbose(expr) - eval(expr) - end + @defbox(boolean, java_lang_Boolean, Bool, jboolean) + @defbox(char, java_lang_Character, Char, jchar) + @defbox(byte, java_lang_Byte, jbyte) + @defbox(short, java_lang_Short, jshort) + @defbox(int, java_lang_Integer, jint) + @defbox(long, java_lang_Long, jlong) + @defbox(float, java_lang_Float, jfloat) + @defbox(double, java_lang_Double, jdouble) end metaclass(class::AbstractString) = metaclass(Symbol(class)) @@ -987,6 +1001,7 @@ function classforlegalname(n::AbstractString) end classfortype(t::Type{JavaObject{T}}) where T = classforname(string(T)) +classfortype(t::Type{T}) where {T <: java_lang} = classforname(classnamefor(nameof(T))) listfields(cls::AbstractString) = listfields(classforname(cls)) listfields(cls::Type{JavaObject{C}}) where C = listfields(classforname(string(C))) @@ -1061,39 +1076,30 @@ setter(field::JFieldInfo) = field.static ? field.typeInfo.staticSetter : field.t getproxyfield(p::JProxy, field::JReadonlyField) = field.get(pxyptr(p)) function getproxyfield(p::JProxy, field::JFieldInfo) static = pxystatic(p) - ptr = pxyptr(p) - result = _getproxyfield(p, field) + ptr = field.static ? C_NULL : pxyptr(p) + result = _getproxyfield(ptr, field) geterror() verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) asJulia(field.typeInfo.convertType, result) end -_getproxyfield(p::JProxy, field::JFieldInfo) = @jnicall(getter(field), Ptr{Nothing}, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{Bool}) = @jnicall(getter(field), jboolean, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jbyte}) = @jnicall(getter(field), jbyte, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jchar}) = @jnicall(getter(field), jchar, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jshort}) = @jnicall(getter(field), jshort, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jint}) = @jnicall(getter(field), jint, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jlong}) = @jnicall(getter(field), jlong, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jfloat}) = @jnicall(getter(field), jfloat, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) -_getproxyfield(p::JProxy, field::JFieldInfo{jdouble}) = @jnicall(getter(field), jdouble, - (Ptr{Nothing}, Ptr{Nothing}), - field.static ? C_NULL : pxyptr(p), field.id) +macro defgetfield(juliat, javat = juliat) + :(function _getproxyfield(p::Ptr{Nothing}, field::JFieldInfo{$juliat}) + local result = ccall(getter(field), $javat, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), + penv, p, field.id) + result == C_NULL && geterror() + result + end) +end +@defgetfield(<:Any, Ptr{Nothing}) +@defgetfield(Bool, jboolean) +@defgetfield(jbyte) +@defgetfield(jchar) +@defgetfield(jshort) +@defgetfield(jint) +@defgetfield(jlong) +@defgetfield(jfloat) +@defgetfield(jdouble) function setproxyfield(p::JProxy, field::JFieldInfo{T}, value) where T primsetproxyfield(p, field, convert(T, value)) @@ -1115,34 +1121,24 @@ function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{JavaObject{T}}, value (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), p, field.id, value) end -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{String}, value::Ptr{Nothing}) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{Bool}, value::jboolean) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jboolean), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jbyte}, value::jbyte) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jbyte), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jchar}, value::jchar) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jchar), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jshort}, value::jshort) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jshort), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jint}, value::jint) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jint), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jlong}, value::jlong) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jlong), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jfloat}, value::jfloat) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jfloat), - p, field.id, value) -_setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{jdouble}) = @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, jdouble), - p, field.id, value) - +macro defsetfield(juliat, javat = juliat) + :(function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{$juliat}, value::$javat) + local result = ccall(setter(field), Nothing, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $javat), + penv, p, field.id, value) + result == C_NULL && geterror() + result + end) +end +@defsetfield(String, Ptr{Nothing}) +@defsetfield(Bool, jboolean) +@defsetfield(jbyte) +@defsetfield(jchar) +@defsetfield(jshort) +@defsetfield(jint) +@defsetfield(jlong) +@defsetfield(jfloat) +@defsetfield(jdouble) function Base.setproperty!(p::JProxy, name::Symbol, value) info = pxyinfo(p) @@ -1168,7 +1164,9 @@ function (pxy::JMethodProxy{N})(args...) where N if meth.static staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) else - call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) + #call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) + println("argTypes: ", meth.argTypes) + call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.argTypes, args...) end else throw(ArgumentError("No $N method for argument types $(typeof.(args))")) @@ -1202,7 +1200,8 @@ fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(ar canConvert(::Type{T}, ::T) where T = true canConvert(t::Type, ::T) where T = canConvertType(t, T) -canConvert(t::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertType(T1, T2) +canConvert(::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertType(T1, T2) +canConvert(::Type{T1}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) canConvertType(::Type{T}, ::Type{T}) where T = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Object")}, java_lang_Object}}, ::Type{<:Union{AbstractString, Real}}) = true @@ -1220,7 +1219,11 @@ canConvertType(::Type{jboolean}, ::Type{Bool}) = true canConvertType(::Type{jchar}, ::Type{Char}) = true canConvertType(x, y) = false -convert(::Type{JObject}, pxy::JProxy) = JavaObject(pxy) +convert_arg(t::Type{JavaObject}, p::JPrimitive) = convert_arg(t, JavaObject(box(p))) +convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) +convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, JavaObject(x)) +convert_arg(::Type{T}, x::JPrimitive) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, JavaObject(box(x))) +convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) # score relative generality of two methods as applied to a particular set of arguments # higher means p1 is more general than p2 (i.e. p2 is the more specific one) @@ -1238,6 +1241,7 @@ isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 # score relative generality of corresponding arguments in two methods # higher means c1 is more general than c2 (i.e. c2 is the more specific one) +moreGeneral(::Type{JProxy{T}}, c1, t1, c2, t2) where T = moreGeneral(T, c1, t1, c2, t2) function moreGeneral(argType::Type, c1::JClass, t1::Type, c2::JClass, t2::Type) p1 = t1 <: JPrimitive p2 = t2 <: JPrimitive @@ -1251,45 +1255,40 @@ function moreGeneral(argType::Type, c1::JClass, t1::Type, c2::JClass, t2::Type) g + g2 - g1 end +#convert_arg(t::Type{T}, arg) where {T <: java_lang, C} = compatible(t, box(arg)) +# +#compatible(::Type{T1}, arg::JProxy{T2}) where {T1, T2 <: T1} = arg +#compatible(x, y) = throw(ArgumentError("$(typeof(y)) is not compatible with $x")) + function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes::Tuple, args...) where T ptr == C_NULL && error("Attempt to call method on Java NULL") - verbose("CALL METHOD RETURNING ", rettype) + verbose("CALL METHOD RETURNING $rettype WITH ARG TYPES $(argtypes)") savedargs, convertedargs = convert_args(argtypes, args...) result = _call(T, ptr, mId, convertedargs) result == C_NULL && geterror() - asJulia(rettype, result) + result = asJulia(rettype, result) + deletelocals() + result +end + +macro defcall(t, f, ft) + :(_call(::Type{$t}, obj, mId, args) = ccall(jnifunc.$(Symbol("Call" * string(f) * "MethodA")), $ft, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args)) end _call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), penv, obj, mId, args) -_call(::Type{Bool}, obj, mId, args) = ccall(jnifunc.CallBooleanMethodA, jboolean, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jbyte}, obj, mId, args) = ccall(jnifunc.CallByteMethodA, jbyte, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jchar}, obj, mId, args) = ccall(jnifunc.CallCharMethodA, jchar, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jshort}, obj, mId, args) = ccall(jnifunc.CallShortMethodA, jshort, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jint}, obj, mId, args) = ccall(jnifunc.CallIntMethodA, jint, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jlong}, obj, mId, args) = ccall(jnifunc.CallLongMethodA, jlong, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jfloat}, obj, mId, args) = ccall(jnifunc.CallFloatMethodA, jfloat, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{jdouble}, obj, mId, args) = ccall(jnifunc.CallDoubleMethodA, jdouble, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) -_call(::Type{Nothing}, obj, mId, args) = ccall(jnifunc.CallNothingMethodA, Nothing, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) +@defcall(Bool, Boolean, jboolean) +@defcall(jbyte, Byte, jbyte) +@defcall(jchar, Char, jchar) +@defcall(jshort, Short, jshort) +@defcall(jint, Int, jint) +@defcall(jlong, Long, jlong) +@defcall(jfloat, Float, jfloat) +@defcall(jdouble, Double, jdouble) +@defcall(Nothing, Void, Nothing) function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T savedargs, convertedargs = convert_args(argtypes, args...) @@ -1297,39 +1296,29 @@ function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T verbose("CONVERTING RESULT ", repr(result), " TO ", rettype) result == C_NULL && geterror() verbose("RETTYPE: ", rettype) - asJulia(rettype, result) + result = asJulia(rettype, result) + deletelocals() + result +end + +macro defstaticcall(t, f, ft) + :(_staticcall(::Type{$t}, mId, args) = ccall(jnifunc.$(Symbol("CallStatic" * string(f) * "MethodA")), $ft, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), + penv, mId, args)) end _staticcall(::Type, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), penv, C_NULL, mId, args) -_staticcall(::Type{Bool}, mId, args) = ccall(jnifunc.CallStaticBooleanMethodA, jboolean, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jbyte}, mId, args) = ccall(jnifunc.CallStaticByteMethodA, jbyte, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jchar}, mId, args) = ccall(jnifunc.CallStaticCharMethodA, jchar, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jshort}, mId, args) = ccall(jnifunc.CallStaticShortMethodA, jshort, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jint}, mId, args) = ccall(jnifunc.CallStaticIntMethodA, jint, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jlong}, mId, args) = ccall(jnifunc.CallStaticLongMethodA, jlong, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jfloat}, mId, args) = ccall(jnifunc.CallStaticFloatMethodA, jfloat, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{jdouble}, mId, args) = ccall(jnifunc.CallStaticDoubleMethodA, jdouble, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) -_staticcall(::Type{Nothing}, mId, args) = ccall(jnifunc.CallStaticVoidMethodA, Nothing, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) +@defstaticcall(Bool, Boolean, jboolean) +@defstaticcall(jbyte, Byte, jbyte) +@defstaticcall(jchar, Char, jchar) +@defstaticcall(jshort, Short, jshort) +@defstaticcall(jint, Int, jint) +@defstaticcall(jlong, Long, jlong) +@defstaticcall(jfloat, Float, jfloat) +@defstaticcall(jdouble, Double, jdouble) +@defstaticcall(Nothing, Void, Nothing) Base.show(io::IO, pxy::JProxy) = print(io, pxystatic(pxy) ? "static class $(legalClassName(pxy))" : pxy.toString()) From 22cab6fde8a7196f09a7cf506f93ba69125f5f47 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Tue, 23 Oct 2018 00:29:00 +0300 Subject: [PATCH 22/43] local ref management, PtrBox, interfaces --- src/convert.jl | 17 ++- src/core.jl | 52 +++++++--- src/proxy.jl | 262 +++++++++++++++++++++++++++++++++-------------- test/runtests.jl | 5 +- 4 files changed, 231 insertions(+), 105 deletions(-) diff --git a/src/convert.jl b/src/convert.jl index 7f272b7..8d27b4d 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -21,17 +21,14 @@ convert(::Type{JavaObject{T}}, ::Nothing) where T = jnull #Cast java object from S to T . Needed for polymorphic calls function convert(::Type{JavaObject{T}}, obj::JavaObject{S}) where {T,S} - if isConvertible(T, S) #Safe static cast - ptr = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, obj.ptr) - ptr === C_NULL && geterror() - return JavaObject{T}(ptr) + if isnull(obj) + return JavaObject{T}(obj.ptr) + elseif isConvertible(T, S) #Safe static cast + return JavaObject{T}(obj.ptr) end - isnull(obj) && throw(ArgumentError("Cannot convert NULL")) realClass = ccall(jnifunc.GetObjectClass, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing} ), penv, obj.ptr) if isConvertible(T, realClass) #dynamic cast - ptr = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, obj.ptr) - ptr === C_NULL && geterror() - return JavaObject{T}(ptr) + return JavaObject{T}(obj.ptr) end throw(JavaCallError("Cannot cast java object from $S to $T")) end @@ -70,7 +67,7 @@ end function convert_arg(argtype::Type{JString}, arg) x = convert(JString, arg) - return x, allocatelocal(x.ptr) + return x, x.ptr end function convert_arg(argtype::Type, arg) @@ -79,7 +76,7 @@ function convert_arg(argtype::Type, arg) end function convert_arg(argtype::Type{T}, arg) where T<:JavaObject x = convert(T, arg)::T - return x, allocatelocal(x.ptr) + return x, x.ptr end for (x, y, z) in [ (:jboolean, :(jnifunc.NewBooleanArray), :(jnifunc.SetBooleanArrayRegion)), diff --git a/src/core.jl b/src/core.jl index b2e60bc..cc5fed8 100644 --- a/src/core.jl +++ b/src/core.jl @@ -1,4 +1,4 @@ -const allocatedrefs = [] +const allocatedrefs = Set() # jni_md.h const jint = Cint @@ -17,8 +17,13 @@ const jdouble = Cdouble const jsize = jint jprimitive = Union{jboolean, jchar, jshort, jfloat, jdouble, jint, jlong} -struct JavaMetaClass{T} +mutable struct JavaMetaClass{T} ptr::Ptr{Nothing} + + function JavaMetaClass{T}(ptr::Ptr{Nothing}) where T + allocatelocal(ptr) + finalizer(deleteref, new{T}(newglobalref(ptr))) + end end #The metaclass, sort of equivalent to a the @@ -33,11 +38,8 @@ mutable struct JavaObject{T} if ptr == C_NULL new{T}(ptr) else - ref = newglobalref(ptr) - reftype = ccall(jnifunc.GetObjectRefType, Int32, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) - reftype == 0 && geterror(true) - reftype == 1 && deletelocalref(ptr) - finalizer(deleteref, new{T}(ref)) + allocatelocal(ptr) + finalizer(deleteref, new{T}(newglobalref(ptr))) end end @@ -48,6 +50,13 @@ mutable struct JavaObject{T} JavaObject(T, ptr) = JavaObject{T}(ptr) end +function registerlocal(ptr::Ptr{Nothing}) + ptr != C_NULL && getreftype(ptr) == 1 && push!(allocatedrefs, ptr) + ptr +end + +getreftype(ptr::Ptr{Nothing}) = ccall(jnifunc.GetObjectRefType, Int32, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) + newglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) deleteglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) @@ -57,9 +66,12 @@ newlocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{J deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) function allocatelocal(ptr::Ptr{Nothing}) - ref = newlocalref(ptr) - push!(allocatedrefs, ref) - ref + if ptr != C_NULL + reftype = getreftype(ptr) + reftype == 0 && geterror(true) + reftype == 1 && push!(allocatedrefs, ptr) + end + ptr end function deletelocals() @@ -68,14 +80,20 @@ function deletelocals() end end +function deleteref(x::JavaMetaClass) + deleteref(x.ptr) + x.ptr=C_NULL #Safety in case this function is called direcly, rather than at finalize +end function deleteref(x::JavaObject) - if x.ptr == C_NULL; return; end - if (penv==C_NULL); return; end + deleteref(x.ptr) + x.ptr=C_NULL #Safety in case this function is called direcly, rather than at finalize +end +function deleteref(ptr::Ptr{Nothing}) + if ptr == C_NULL; return; end + if penv==C_NULL; return; end #ccall(:jl_,Nothing,(Any,),x) #ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, x.ptr) - deleteglobalref(x.ptr) - x.ptr=C_NULL #Safety in case this function is called direcly, rather than at finalize - return + deleteglobalref(ptr) end @@ -173,7 +191,9 @@ function jcall(typ::Type{JavaObject{T}}, method::AbstractString, rettype::Type, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T), String(method), sig) jmethodId==C_NULL && geterror(true) - _jcall(metaclass(T), jmethodId, C_NULL, rettype, argtypes, args...) + result = _jcall(metaclass(T), jmethodId, C_NULL, rettype, argtypes, args...) + deletelocals() + result end # Call instance methods diff --git a/src/proxy.jl b/src/proxy.jl index 41a48a2..f100d62 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -3,6 +3,7 @@ import Base.== global useVerbose = false +global initialized = false setVerbose() = global useVerbose = true clearVerbose() = global useVerbose = false @@ -28,6 +29,7 @@ function _defjtype(a, b) $symA end) get!(types, Symbol(classnamefor(a))) do + #println("DEFINING ", symA) eval(quote abstract type $symA <: $b end $symA @@ -154,6 +156,22 @@ struct Boxing unboxer::Ptr{Nothing} end +""" + PtrBox(ptr::Ptr{Nothing} + +Temporarily holds a globalref to a Java object during JProxy creation +""" +# mutable because it can have a finalizer +mutable struct PtrBox + ptr::Ptr{Nothing} + + PtrBox(obj::JavaObject) = PtrBox(obj.ptr) + function PtrBox(ptr::Ptr{Nothing}) + registerlocal(ptr) + finalizer(finalizebox, new(ptr)) + end +end + """ JProxy(s::AbstractString) JProxy(::JavaMetaClass) @@ -212,8 +230,11 @@ mutable struct JProxy{T, C} ptr::Ptr{Nothing} info::JClassInfo static::Bool - function JProxy{T, C}(ptr::Ptr{Nothing}, info, static) where {T, C} - finalizer(finalizeproxy, new{T, C}(globalref(ptr), info, static)) + function JProxy{T, C}(obj::JavaObject, info, static) where {T, C} + finalizer(finalizeproxy, new{T, C}(newglobalref(obj.ptr), info, static)) + end + function JProxy{T, C}(obj::PtrBox, info, static) where {T, C} + finalizer(finalizeproxy, new{T, C}(newglobalref(obj.ptr), info, static)) end end @@ -251,12 +272,18 @@ global objectClass global sigTypes macro jnicall(func, rettype, types, args...) + #println("TYPE ", rettype) quote local result = ccall($(esc(func)), $(esc(rettype)), (Ptr{JNIEnv}, $(esc.(types.args)...)), penv, $(esc.(args)...)) result == C_NULL && geterror() - result + $(if rettype == Ptr{Nothing} + #println("PTR") + :(registerreturn(result)) + else + :(result) + end) end end @@ -264,12 +291,18 @@ macro message(obj, rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).instance verbose("INSTANCE FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) + #println("TYPE ", rettype) quote result = ccall(jnifunc.$func, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((typeof(arg) for arg in args)...)), penv, $(esc(obj)), $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() - result + $(if rettype == Ptr{Nothing} + #println("PTR") + :(registerreturn(result)) + else + :(result) + end) end end @@ -277,16 +310,26 @@ macro staticmessage(rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).static verbose("STATIC FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) + #println("TYPE ", rettype) quote result = ccall(jnifunc.$func, $(esc(rettype)), (Ptr{JNIEnv}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), penv, $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() - result + $(if rettype == Ptr{Nothing} + #println("PTR") + :(registerreturn(result)) + else + :(result) + end) end end -globalref(ptr::Ptr{Nothing}) = @jnicall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{Nothing},), ptr) +registerreturn(x) = x +function registerreturn(x::Ptr{Nothing}) + #println("RESULT REF TYPE: ", getreftype(x)) + allocatelocal(x) +end function arrayinfo(str) if (m = match(r"^(\[+)(.)$", str)) != nothing @@ -301,10 +344,16 @@ end function finalizeproxy(pxy::JProxy) ptr = pxyptr(pxy) if ptr == C_NULL || penv == C_NULL; return; end - @jnicall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{Nothing},), ptr) + deleteglobalref(ptr) setfield!(pxy, :ptr, C_NULL) #Safety in case this function is called direcly, rather than at finalize end +function finalizebox(box::PtrBox) + if box.ptr == C_NULL || penv == C_NULL; return; end + deleteglobalref(box.ptr) + box.ptr = C_NULL #Safety in case this function is called direcly, rather than at finalize +end + arraycomponent(::Type{Array{T}}) where T = T signatureClassFor(name) = length(name) == 1 ? sigTypes[name].classname : name @@ -322,20 +371,23 @@ function JProxy(::Type{JavaObject{C}}) where C c = Symbol(legalClassName(string(C))) obj = classforname(string(c)) info = infoFor(obj) - JProxy{typeFor(c), c}(obj.ptr, info, true) + JProxy{typeFor(c), c}(obj, info, true) end # Proxies on classes are on the class objects, they don't get you static members # To access static members, use types or metaclasses # like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` JProxy(s::AbstractString) = JProxy(JString(s)) -JProxy{T, C}(ptr::Ptr{Nothing}) where {T, C} = JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) -JProxy(obj::JavaObject) = JProxy(obj.ptr) -function JProxy(ptr::Ptr{Nothing}) - if ptr == C_NULL +function JProxy{T, C}(ptr::PtrBox) where {T, C} + JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) +end +JProxy(obj::JavaObject) = JProxy(PtrBox(obj)) +JProxy(ptr::Ptr{Nothing}) = JProxy(PtrBox(ptr)) +function JProxy(obj::PtrBox) + if obj.ptr == C_NULL cls = objectClass n = "java.lang.Object" else - cls = JClass(getclass(ptr)) + cls = JClass(getclass(obj.ptr)) n = legalClassName(getname(cls)) end c = Symbol(n) @@ -345,7 +397,7 @@ function JProxy(ptr::Ptr{Nothing}) if dim != 0 typeFor(Symbol(aType)) end - JProxy{info.classtype, c}(ptr, info, false) + JProxy{info.classtype, c}(obj, info, false) end function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) @@ -637,7 +689,11 @@ unionize(::Type{T1}, ::Type{T2}) where {T1, T2} = Union{T1, T2} function definterfacecvt(ct, interfaces) if !isempty(interfaces) - eval(:(canConvertType(::Type{<:$(reduce(unionize, [i.classtype for i in interfaces]))}, ::Type{$ct}) = true)) + union = reduce(unionize, [i.classtype for i in interfaces]) + if ct <: interface + union = unionize(ct, union) + end + eval(:(interfacehas(::Type{<:$union}, ::Type{$ct}) = true)) end end @@ -645,8 +701,8 @@ function JClassInfo(class::JClass) n = Symbol(legalClassName(class)) verbose("INFO FOR $(string(n))") sc = superclass(class) - parentinfo = !isNull(sc) ? infoFor(sc) : nothing - interfaces = [infoFor(JClass(ptr)) for ptr in allinterfaces(class.ptr)] + parentinfo = !isNull(sc) ? _infoFor(sc) : nothing + interfaces = [_infoFor(cl) for cl in allinterfaces(class)] tname = typeNameFor(string(n)) #verbose("JCLASS INFO FOR ", n) jtype = if tname == String @@ -806,35 +862,46 @@ function _defbox(primclass, boxtype, juliatype, javatype) boxclass = JavaObject{Symbol(classnamefor(boxtype))} primname = string(primclass) boxVar = Symbol(primname * "Box") - quote - const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) - function convert(::Type{JavaObject{T}}, obj::$juliatype) where T - box(obj) - end - function box(data::$juliatype) - JProxy(_jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $boxclass, ($juliatype,), data)) - end - function unbox(::Type{$boxclass}, ptr::Ptr{Nothing}) - $(if juliatype == :Bool - :(call(ptr, $boxVar.unboxer, jboolean, ()) != 0) - else - :(call(ptr, $boxVar.unboxer, $javatype, ())) - end) + varpart = if juliatype == :Bool + quote + convert(::Type{JavaObject{T}}, obj::Union{jboolean, Bool}) where T = JavaObject(box(obj)) + function unbox(::Type{$boxclass}, ptr::Ptr{Nothing}) + call(ptr, $boxVar.unboxer, jboolean, ()) != 0 + end + function unbox(::Type{$boxtype}, ptr::Ptr{Nothing}) + call(ptr, $boxVar.unboxer, jboolean, ()) != 0 + end + function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) + _jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0 + end end - function unbox(::Type{$boxtype}, ptr::Ptr{Nothing}) - $(if juliatype == :Bool - :(call(ptr, $boxVar.unboxer, jboolean, ()) != 0) + else + quote + #convert(::Type{JavaObject{T}}, obj::$juliatype) where T = JavaObject(box(obj)) + $(if juliatype == :jchar + :(convert(::Type{JavaObject{T}}, obj::Char) where T = JavaObject(box(obj))) else - :(call(ptr, $boxVar.unboxer, $javatype, ())) + () end) + function unbox(::Type{$boxclass}, ptr::Ptr{Nothing}) + call(ptr, $boxVar.unboxer, $javatype, ()) + end + function unbox(::Type{$boxtype}, ptr::Ptr{Nothing}) + call(ptr, $boxVar.unboxer, $javatype, ()) + end + function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) + _jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ()) + end end - function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) - $(if juliatype == :Bool - :(_jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0) - else - :(_jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ())) - end) + end + quote + const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) + boxer(::Type{$juliatype}) = $boxVar + function box(data::$juliatype) + #println("BOXING ", $primname, ", boxvar: ", $(string(boxVar))) + JProxy(_jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $boxclass, ($juliatype,), data)) end + $varpart end end @@ -876,6 +943,7 @@ function initProxy() global methodId_class_getInterfaces = getmethodid("java.lang.Class", "getInterfaces", "[Ljava.lang.Class;") global methodId_class_isInterface = getmethodid("java.lang.Class", "isInterface", "boolean") global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) + global initialized = true @defbox(boolean, java_lang_Boolean, Bool, jboolean) @defbox(char, java_lang_Character, Char, jchar) @defbox(byte, java_lang_Byte, jbyte) @@ -888,17 +956,24 @@ end metaclass(class::AbstractString) = metaclass(Symbol(class)) -getclass(obj::Ptr{Nothing}) = @message(obj, Ptr{Nothing}, methodId_object_getClass) +function getclass(obj::Ptr{Nothing}) + initialized ? @message(obj, Ptr{Nothing}, methodId_object_getClass) : C_NULL +end -getclassname(class::Ptr{Nothing}) = unsafe_string(@message(class, Ptr{Nothing}, methodId_class_getName)) +function getclassname(class::Ptr{Nothing}) + initialized ? unsafe_string(@message(class, Ptr{Nothing}, methodId_class_getName)) : "UNKNOWN" +end isinterface(class::Ptr{Nothing}) = @message(class, jboolean, methodId_class_isInterface) != 0 -getinterfaces(class::Ptr{Nothing}) = jarray(@message(class, Ptr{Nothing}, methodId_class_getInterfaces)) +function getinterfaces(class::JClass) + array = @message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces) + [JClass(arrayat(array, i)) for i in 1:arraylength(array)] +end jarray(array::Ptr{Nothing}) = [arrayat(array, i) for i in 1:arraylength(array)] -function allinterfaces(class::Ptr{Nothing}) +function allinterfaces(class::JClass) result = [] queue = [class] seen = Set() @@ -979,6 +1054,11 @@ function juliaTypeFor(name::AbstractString) end function infoFor(class::JClass) + result = _infoFor(class) + deletelocals() + result +end +function _infoFor(class::JClass) if isNull(class) nothing else @@ -1154,18 +1234,18 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) end function (pxy::JMethodProxy{N})(args...) where N - targets = Set(m for m in pxy.methods if fits(m, args)) + targets = Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)) #verbose("LOCATING MESSAGE ", N, " FOR ARGS ", repr(args)) if !isempty(targets) # Find the most specific method argTypes = typeof(args).parameters - meth = reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) + meth = reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) if meth.static staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) else #call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) - println("argTypes: ", meth.argTypes) + #println("argTypes: ", meth.argTypes) call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.argTypes, args...) end else @@ -1174,10 +1254,10 @@ function (pxy::JMethodProxy{N})(args...) where N end function findmethod(pxy::JMethodProxy, args...) - targets = Set(m for m in pxy.methods if fits(m, args)) + targets = Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)) if !isempty(targets) argTypes = typeof(args).parameters - reduce(((x, y)-> moreGeneral(argTypes, x, y) < moreGeneral(argTypes, y, x) ? x : y), filterStatic(pxy, targets)) + reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) end end @@ -1196,7 +1276,8 @@ function filterStatic(pxy::JMethodProxy, targets) Set(target for target in targets if target.static == static) end -fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.dynArgTypes, args)) +#fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.dynArgTypes, args)) +fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.argTypes, args)) canConvert(::Type{T}, ::T) where T = true canConvert(t::Type, ::T) where T = canConvertType(t, T) @@ -1204,7 +1285,8 @@ canConvert(::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertTyp canConvert(::Type{T1}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) canConvertType(::Type{T}, ::Type{T}) where T = true -canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Object")}, java_lang_Object}}, ::Type{<:Union{AbstractString, Real}}) = true +canConvertType(::Type{T1}, t::Type{T2}) where {T1 <: java_lang_Object, T2 <: java_lang_Object} = T2 <: T1 +canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Object")}, java_lang_Object}}, ::Type{<:Union{AbstractString, JPrimitive}}) = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Double")}, java_lang_Double}}, ::Type{<:Union{Float64, Float32, Float16, Int64, Int32, Int16, Int8}}) = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Float")}, java_lang_Float}}, ::Type{<:Union{Float32, Float16, Int32, Int16, Int8}}) = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Long")}, java_lang_Long}}, ::Type{<:Union{Int64, Int32, Int16, Int8}}) = true @@ -1217,54 +1299,78 @@ canConvertType(::Type{JString}, ::Type{<:AbstractString}) = true canConvertType(::Type{<: Real}, ::Type{<:Real}) = true canConvertType(::Type{jboolean}, ::Type{Bool}) = true canConvertType(::Type{jchar}, ::Type{Char}) = true -canConvertType(x, y) = false +canConvertType(x, y) = interfacehas(x, y) -convert_arg(t::Type{JavaObject}, p::JPrimitive) = convert_arg(t, JavaObject(box(p))) +interfacehas(x, y) = false + +# ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG +function convert_arg(t::Type{<:Union{JObject, java_lang_Object}}, x::JPrimitive) + #result = JavaObject(box(x)) + #convert_arg(typeof(result), result) + result = box(x) + result, pxyptr(result) +end convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) -convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, JavaObject(x)) -convert_arg(::Type{T}, x::JPrimitive) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, JavaObject(box(x))) +convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) -# score relative generality of two methods as applied to a particular set of arguments -# higher means p1 is more general than p2 (i.e. p2 is the more specific one) -function moreGeneral(argTypes, p1::JMethodInfo, p2::JMethodInfo) where T +# score specificity of a method +function specificity(argTypes, mi::JMethodInfo) where T g = 0 for i in 1:length(argTypes) - c1, c2 = p1.argClasses[i], p2.argClasses[i] - t1, t2 = p1.argTypes[i], p2.argTypes[i] - g += moreGeneral(argTypes[i], c1, t1, c2, t2) - moreGeneral(argTypes[i], c2, t2, c1, t1) + g += specificity(argTypes[i], mi.argTypes[i]) end g end isPrimitive(cls::JavaObject) = jcall(cls, "isPrimitive", jboolean, ()) != 0 +const specificityworst = -1000000 +const specificitybest = 1000000 +const specificitybox = 100000 +const specificityinherit = 10000 + # score relative generality of corresponding arguments in two methods # higher means c1 is more general than c2 (i.e. c2 is the more specific one) -moreGeneral(::Type{JProxy{T}}, c1, t1, c2, t2) where T = moreGeneral(T, c1, t1, c2, t2) -function moreGeneral(argType::Type, c1::JClass, t1::Type, c2::JClass, t2::Type) - p1 = t1 <: JPrimitive - p2 = t2 <: JPrimitive - g1 = !p1 ? 0 : argType <: t1 ? -1 : 1 - g2 = !p2 ? 0 : argType <: t2 ? -1 : 1 - g = if !p1 && p2 || jcall(c1, "isAssignableFrom", jboolean, (@jimport(java.lang.Class),), c2) != 0 - 1 +specificity(::Type{JProxy{T}}, t1) where T = specificity(T, t1) +specificity(argType::Type{<:Union{JBoxTypes,JPrimitive}}, t1::Type{<:JPrimitive}) = specificitybest +specificity(argType::Type{<:JBoxTypes}, t1::Type{<:JBoxTypes}) = specificitybest +specificity(argType::Type{<:JPrimitive}, t1::Type{<:JBoxTypes}) = specificitybox +function specificity(argType::Type, t1::Type) + if argType == t1 || interfacehas(t1, argType) + specificitybest + elseif argType <: t1 + at = argType + spec = specificityinherit + while at != t1 + spec -= 1 + at = supertype(at) + end + spec else - 0 + specificityworst end - g + g2 - g1 end -#convert_arg(t::Type{T}, arg) where {T <: java_lang, C} = compatible(t, box(arg)) -# -#compatible(::Type{T1}, arg::JProxy{T2}) where {T1, T2 <: T1} = arg -#compatible(x, y) = throw(ArgumentError("$(typeof(y)) is not compatible with $x")) - function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes::Tuple, args...) where T ptr == C_NULL && error("Attempt to call method on Java NULL") - verbose("CALL METHOD RETURNING $rettype WITH ARG TYPES $(argtypes)") savedargs, convertedargs = convert_args(argtypes, args...) + for i in 1:length(argtypes) + if isa(savedargs[i], JavaObject) && convertedargs[i] != 0 + aptr = Ptr{Nothing}(convertedargs[i]) + if getreftype(aptr) == 1 + #println("LOCAL REF") + push!(allocatedrefs, aptr) + end + #println("class: ", getclassname(getclass(aptr)), " type: ", argtypes[i], " arg: ", args[i], " saved: ", savedargs[i], " REFTYPE: ", getreftype(aptr)) + end + end + verbose("CALL METHOD RETURNING $rettype WITH ARG TYPES $(argtypes): ACTUAL TYPES $(join(types, ", "))") result = _call(T, ptr, mId, convertedargs) + if rettype <: JavaObject && result != C_NULL + #println("RESULT REF TYPE: ", getreftype(result)) + push!(allocatedrefs, result) + end result == C_NULL && geterror() result = asJulia(rettype, result) deletelocals() diff --git a/test/runtests.jl b/test/runtests.jl index c7f596f..1a649c8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -143,7 +143,10 @@ end gc() for i in 1:100000 a=JString("A"^10000); #deleteref(a); - if (i%10000 == 0); gc(); end + if (i%10000 == 0) + JavaCall.deletelocals() + gc() + end end @testset "sinx_1" begin From d84c9d2b1b647108a136139921c89e1af0f0bbc8 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 11 Nov 2018 19:42:55 +0200 Subject: [PATCH 23/43] Basic version that seems to work but needs much improvement need to remove references to JavaObject from proxy.jl --- src/core.jl | 65 ++++--- src/gen.jl | 220 ++++++++++++++++++++++++ src/proxy.jl | 430 +++++++++++------------------------------------ test/runtests.jl | 5 +- 4 files changed, 367 insertions(+), 353 deletions(-) create mode 100644 src/gen.jl diff --git a/src/core.jl b/src/core.jl index cc5fed8..df467c3 100644 --- a/src/core.jl +++ b/src/core.jl @@ -21,8 +21,10 @@ mutable struct JavaMetaClass{T} ptr::Ptr{Nothing} function JavaMetaClass{T}(ptr::Ptr{Nothing}) where T - allocatelocal(ptr) - finalizer(deleteref, new{T}(newglobalref(ptr))) + #registerlocal(ptr) + cl = finalizer(deleteref, new{T}(newglobalref(ptr))) + #getreftype(ptr) == 1 && deletelocalref(ptr) + cl end end @@ -38,8 +40,10 @@ mutable struct JavaObject{T} if ptr == C_NULL new{T}(ptr) else - allocatelocal(ptr) - finalizer(deleteref, new{T}(newglobalref(ptr))) + #registerlocal(ptr) + obj = finalizer(deleteref, new{T}(newglobalref(ptr))) + #getreftype(ptr) == 1 && deletelocalref(ptr) + obj end end @@ -50,14 +54,11 @@ mutable struct JavaObject{T} JavaObject(T, ptr) = JavaObject{T}(ptr) end -function registerlocal(ptr::Ptr{Nothing}) - ptr != C_NULL && getreftype(ptr) == 1 && push!(allocatedrefs, ptr) - ptr -end - getreftype(ptr::Ptr{Nothing}) = ccall(jnifunc.GetObjectRefType, Int32, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) -newglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) +function newglobalref(ptr::Ptr{Nothing}) + ccall(jnifunc.NewGlobalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) +end deleteglobalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteGlobalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) @@ -65,10 +66,10 @@ newlocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{J deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) -function allocatelocal(ptr::Ptr{Nothing}) +function registerlocal(ptr::Ptr{Nothing}) if ptr != C_NULL reftype = getreftype(ptr) - reftype == 0 && geterror(true) + #reftype == 0 && geterror() reftype == 1 && push!(allocatedrefs, ptr) end ptr @@ -89,11 +90,11 @@ function deleteref(x::JavaObject) x.ptr=C_NULL #Safety in case this function is called direcly, rather than at finalize end function deleteref(ptr::Ptr{Nothing}) - if ptr == C_NULL; return; end - if penv==C_NULL; return; end - #ccall(:jl_,Nothing,(Any,),x) - #ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, x.ptr) - deleteglobalref(ptr) + if ptr != C_NULL && penv != C_NULL + #ccall(:jl_,Nothing,(Any,),x) + #ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, x.ptr) + deleteglobalref(ptr) + end end @@ -141,7 +142,9 @@ function JString(str::AbstractString) if jstring == C_NULL geterror() else - JString(jstring) + str = JString(jstring) + deletelocalref(jstring) + str end end @@ -180,7 +183,9 @@ function jnew(T::Symbol, argtypes::Tuple, args...) if jmethodId == C_NULL throw(JavaCallError("No constructor for $T with signature $sig")) end - return _jcall(metaclass(T), jmethodId, jnifunc.NewObjectA, JavaObject{T}, argtypes, args...) + result = _jcall(metaclass(T), jmethodId, jnifunc.NewObjectA, JavaObject{T}, argtypes, args...) + deletelocals() + result end # Call static methods @@ -203,7 +208,9 @@ function jcall(obj::JavaObject, method::AbstractString, rettype::Type, argtypes: (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(obj), String(method), sig) jmethodId==C_NULL && geterror(true) - _jcall(obj, jmethodId, C_NULL, rettype, argtypes, args...) + result = _jcall(obj, jmethodId, C_NULL, rettype, argtypes, args...) + deletelocals() + result end function jfield(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) where T @@ -248,7 +255,11 @@ function _jfield(obj, jfieldID::Ptr{Nothing}, fieldType::Type) result = ccall(callmethod, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, obj.ptr, jfieldID) result==C_NULL && geterror() - return convert_result(fieldType, result) + #result != C_NULL && registerlocal(result) + finalresult = convert_result(fieldType, result) + #deletelocals() + deletelocalref(result) + finalresult end #Generate these methods to satisfy ccall's compile time constant requirement @@ -298,7 +309,11 @@ function _jcall(obj, jmethodId::Ptr{Nothing}, callmethod::Ptr{Nothing}, rettype: result = ccall(callmethod, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), penv, obj.ptr, jmethodId, convertedArgs) result==C_NULL && geterror() - return convert_result(rettype, result) + registerlocal(result) + finalresult = convert_result(rettype, result) + #deletelocals() + #deletelocalref(result) + finalresult end @@ -308,7 +323,11 @@ function _metaclass(class::Symbol) jclass=javaclassname(class) jclassptr = ccall(jnifunc.FindClass, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, jclass) jclassptr == C_NULL && throw(JavaCallError("Class Not Found $jclass")) - return JavaMetaClass(class, jclassptr) + #registerlocal(jclassptr) + finalresult = JavaMetaClass(class, jclassptr) + #deletelocals() + deletelocalref(jclassptr) + finalresult end function metaclass(class::Symbol) diff --git a/src/gen.jl b/src/gen.jl new file mode 100644 index 0000000..b871cea --- /dev/null +++ b/src/gen.jl @@ -0,0 +1,220 @@ +# This code is not used yet +# It was moved from the work-in-progress code in proxy.jl + +struct GenInfo + code + typeCode + deps + classList + methodDicts + fielddicts +end + +struct GenArgInfo + name::Symbol + javaType::Type + juliaType + spec +end + +const genned = Set() + +hasClass(name::AbstractString) = hasClass(Symbol(name)) +hasClass(name::Symbol) = name in genned +hasClass(gen, name::AbstractString) = hasClass(gen, Symbol(name)) +hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodDicts, string(name)) + +function genTypeDecl(name::AbstractString, supername::Symbol, gen) + if string(name) != "String" && !haskey(types, Symbol(name)) && !haskey(gen.methodDicts, name) + typeName = typeNameFor(name) + push!(gen.typeCode, :(abstract type $typeName <: $supername end)) + end +end + +function registerclass(name::AbstractString, classType::Type) + registerclass(Symbol(name), classType) +end +function registerclass(name::Symbol, classType::Type) + if !(classType <: Union{Array, String}) && !haskey(types, name) + types[name] = classType + end + infoFor(classforname(string(name))) +end + +gen(name::Symbol; genmode=:none, print=false, eval=true) = _gen(classforname(string(name)), genmode, print, eval) +gen(name::AbstractString; genmode=:none, print=false, eval=true) = _gen(classforname(name), genmode, print, eval) +gen(pxy::JProxy{T, C}) where {T, C} = gen(C) +gen(class::JClass; genmode=:none, print=false, eval=true) = _gen(class, genmode, eval) +function _gen(class::JClass, genmode, print, evalResult) + n = legalClassName(class) + gen = GenInfo() + genClass(class, gen) + if genmode == :deep + while !isempty(gen.deps) + cls = pop!(gen.deps) + !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) + end + else + while !isempty(gen.deps) + cls = pop!(gen.deps) + !hasClass(gen, cls) && genType(classforname(string(cls)), gen) + end + end + expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) + if print + for e in expr.args + println(e) + end + end + evalResult && eval(expr) +end + +function genType(class, gen::GenInfo) + name = getname(class) + sc = superclass(class) + push!(genned, Symbol(legalClassName(class))) + if !isNull(sc) + if !(Symbol(legalClassName(sc)) in genned) + genType(getcomponentclass(sc), gen) + end + supertype = typeNameFor(sc) + cType = componentType(supertype) + genTypeDecl(name, cType, gen) + else + genTypeDecl(name, :java_lang, gen) + end +end + +genClass(class::JClass, gen::GenInfo) = genClass(class, gen, infoFor(class)) +function genClass(class::JClass, gen::GenInfo, info::JClassInfo) + name = getname(class) + if !(Symbol(name) in genned) + gen.fielddicts[legalClassName(class)] = fielddict(class) + push!(gen.classList, class) + sc = superclass(class) + #@verbose("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") + push!(genned, Symbol(legalClassName(class))) + if !isNull(sc) + supertype = typeNameFor(sc) + cType = componentType(supertype) + !hasClass(gen, cType) && genClass(sc, gen) + genTypeDecl(name, cType, gen) + else + genTypeDecl(name, :java_lang, gen) + end + genMethods(class, gen, info) + end +end + +GenInfo() = GenInfo([], [], Set(), [], Dict(), Dict()) + +function GenArgInfo(index, info::JMethodInfo, gen::GenInfo) + javaType = info.argTypes[index] + GenArgInfo(Symbol("a" * string(index)), javaType, argType(javaType, gen), argSpec(javaType, gen)) +end + +argType(t, gen) = t +argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String +argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JLegalArg +argType(::Type{<: Number}, gen) = Number +argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) + +argSpec(t, gen) = t +argSpec(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String +argSpec(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JObject +argSpec(::Type{<: Number}, gen) = Number +argSpec(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) +argSpec(arg::GenArgInfo) = arg.spec + +typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) +function typeNameFor(T::AbstractString, gen::GenInfo) + aType, dims = arrayinfo(T) + c = dims != 0 ? aType : T + csym = Symbol(c) + if (dims == 0 || length(c) > 1) && !(csym in gen.deps) && !hasClass(gen, csym) && !get(typeInfo, c, genericFieldInfo).primitive + push!(gen.deps, csym) + end + typeNameFor(T) +end + +function argCode(arg::GenArgInfo) + argname = arg.name + if arg.juliaType == String + argname + elseif arg.juliaType == JLegalArg + :(box($argname)) + elseif arg.juliaType == Number + :($(arg.javaType)($argname)) + else + argname + end +end + +function fieldEntry((name, fld)) + fieldType = JavaObject{Symbol(legalClassName(fld.owner))} + name => :(jfield($(fld.typeInfo.class), $(string(name)), $fieldType)) +end + +function genMethods(class, gen, info) + methodList = listmethods(class) + classname = legalClassName(class) + gen.methodDicts[classname] = methods = Dict() + typeName = typeNameFor(classname, gen) + classVar = Symbol("class_" * string(typeName)) + fieldsVar = Symbol("fields_" * string(typeName)) + methodsVar = Symbol("staticMethods_" * string(typeName)) + push!(gen.code, :($classVar = classforname($classname))) + push!(gen.code, :($fieldsVar = Dict([$([fieldEntry(f) for f in gen.fielddicts[classname]]...)]))) + push!(gen.code, :($methodsVar = Set($([string(n) for (n, m) in info.methods if any(x->x.static, m)])))) + push!(gen.code, :(function Base.getproperty(p::JProxy{T, C}, name::Symbol) where {T <: $typeName, C} + if (f = get($fieldsVar, name, nothing)) != nothing + getField(p, name, f) + else + JMethodProxy(name, $typeName, p, emptyset) + end + end)) + for nameSym in sort(collect(keys(info.methods))) + name = string(nameSym) + multiple = length(info.methods[nameSym]) > 1 + symId = 0 + for minfo in info.methods[nameSym] + owner = javaType(minfo.owner) + if isSame(class.ptr, minfo.owner.ptr) + symId += 1 + args = (GenArgInfo(i, minfo, gen) for i in 1:length(minfo.argTypes)) + argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) + methodIdName = Symbol("method_" * string(typeName) * "__" * name * (multiple ? string(symId) : "")) + callinfo = jnicalls[minfo.typeInfo.classname] + push!(gen.code, :($methodIdName = getmethodid($(minfo.static), $classVar, $name, $(legalClassName(minfo.returnClass)), $(legalClassName.(minfo.argClasses))))) + push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $typeName})($(argDecs...))::$(genReturnType(minfo, gen)) + @verbose($("Generated method $name$(multiple ? "(" * string(symId) * ")" : "")")) + $(genConvertResult(minfo.typeInfo.convertType, minfo, :(call(pxy.obj, $methodIdName, $(static ? callinfo.static : callinfo.instance), $(minfo.typeInfo.juliaType), ($(argSpec.(args)...),), $((argCode(arg) for arg in args)...))))) + end)) + end + end + end + push!(gen.code, :(push!(genned, Symbol($(legalClassName(class)))))) +end + +function genReturnType(methodInfo, gen) + t = methodInfo.typeInfo.convertType + if methodInfo.typeInfo.primitive || t <: String || t == Nothing + t + else + :(JProxy{<:$(typeNameFor(methodInfo.returnType, gen))}) + end +end + +genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) +genConvertResult(toType::Type{String}, info, expr) = :(unsafe_string($expr)) +genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($(toType.parameters[1]), $expr)) +function genConvertResult(toType, info, expr) + if isVoid(info) || info.typeInfo.primitive + expr + else + :(asJulia($toType, $expr)) + end +end + +genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) + diff --git a/src/proxy.jl b/src/proxy.jl index f100d62..73d9a83 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -8,7 +8,14 @@ global initialized = false setVerbose() = global useVerbose = true clearVerbose() = global useVerbose = false -verbose(args...) = useVerbose && println(args...) +location(source) = replace(string(source.file), r"^.*/([^/]*)$" => s"\1") * string(source.line) * ": " + +macro verbose(args...) + :(useVerbose && println($(location(__source__)), $(esc.(args)...))) +end +macro Verbose(args...) + :(useVerbose && println("@@@ ", $(location(__source__)), $(esc.(args)...))) +end abstract type java_lang end abstract type interface <: java_lang end @@ -24,12 +31,11 @@ end _defjtype(a::Type, b::Type) = _defjtype(nameof(a), nameof(b)) function _defjtype(a, b) symA = Symbol(a) - verbose("DEFINING ", string(symA), " ", quote + @verbose("DEFINING ", string(symA), " ", quote abstract type $symA <: $b end $symA end) get!(types, Symbol(classnamefor(a))) do - #println("DEFINING ", symA) eval(quote abstract type $symA <: $b end $symA @@ -167,7 +173,8 @@ mutable struct PtrBox PtrBox(obj::JavaObject) = PtrBox(obj.ptr) function PtrBox(ptr::Ptr{Nothing}) - registerlocal(ptr) + #registerlocal(ptr) + newglobalref(ptr) finalizer(finalizebox, new(ptr)) end end @@ -238,25 +245,8 @@ mutable struct JProxy{T, C} end end -struct GenInfo - code - typeCode - deps - classList - methodDicts - fielddicts -end - -struct GenArgInfo - name::Symbol - javaType::Type - juliaType - spec -end - const JLegalArg = Union{Number, String, JProxy, Array{Number}, Array{String}, Array{JProxy}} const methodsById = Dict() -const genned = Set() const emptyset = Set() const classes = Dict() const methodCache = Dict{Tuple{String, String, Array{String}}, JMethodInfo}() @@ -272,33 +262,31 @@ global objectClass global sigTypes macro jnicall(func, rettype, types, args...) - #println("TYPE ", rettype) + _jnicall(func, rettype, types, args) +end +macro jnicallregistered(func, rettype, types, args...) + :(registerreturn($(_jnicall(func, rettype, types, args)))) +end +function _jnicall(func, rettype, types, args) quote local result = ccall($(esc(func)), $(esc(rettype)), (Ptr{JNIEnv}, $(esc.(types.args)...)), penv, $(esc.(args)...)) result == C_NULL && geterror() - $(if rettype == Ptr{Nothing} - #println("PTR") - :(registerreturn(result)) - else - :(result) - end) + result end end macro message(obj, rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).instance - verbose("INSTANCE FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) + @verbose("INSTANCE FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) - #println("TYPE ", rettype) quote result = ccall(jnifunc.$func, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $((typeof(arg) for arg in args)...)), penv, $(esc(obj)), $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() - $(if rettype == Ptr{Nothing} - #println("PTR") + $(if rettype == Ptr{Nothing} || rettype == :(Ptr{Nothing}) :(registerreturn(result)) else :(result) @@ -308,16 +296,14 @@ end macro staticmessage(rettype, methodid, args...) func = get(jnicalls, rettype, defaultjnicall).static - verbose("STATIC FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) + @verbose("STATIC FUNC: ", func, " RETURNING ", rettype, " ARGS ", typeof.(args)) flush(stdout) - #println("TYPE ", rettype) quote result = ccall(jnifunc.$func, $(esc(rettype)), (Ptr{JNIEnv}, Ptr{Nothing}, $((Ptr{Nothing} for i in args)...)), penv, $(esc(methodid)), $(esc.(args)...)) result == C_NULL && geterror() - $(if rettype == Ptr{Nothing} - #println("PTR") + $(if rettype == Ptr{Nothing} || rettype == :(Ptr{Nothing}) :(registerreturn(result)) else :(result) @@ -327,8 +313,7 @@ end registerreturn(x) = x function registerreturn(x::Ptr{Nothing}) - #println("RESULT REF TYPE: ", getreftype(x)) - allocatelocal(x) + registerlocal(x) end function arrayinfo(str) @@ -391,7 +376,7 @@ function JProxy(obj::PtrBox) n = legalClassName(getname(cls)) end c = Symbol(n) - verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) + @verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) info = infoFor(cls) aType, dim = arrayinfo(n) if dim != 0 @@ -427,113 +412,6 @@ end gettypeinfo(class::Symbol) = gettypeinfo(string(class)) gettypeinfo(class::AbstractString) = get(typeInfo, class, genericFieldInfo) -hasClass(name::AbstractString) = hasClass(Symbol(name)) -hasClass(name::Symbol) = name in genned -hasClass(gen, name::AbstractString) = hasClass(gen, Symbol(name)) -hasClass(gen, name::Symbol) = name in genned || haskey(gen.methodDicts, string(name)) - -function genTypeDecl(name::AbstractString, supername::Symbol, gen) - if string(name) != "String" && !haskey(types, Symbol(name)) && !haskey(gen.methodDicts, name) - typeName = typeNameFor(name) - push!(gen.typeCode, :(abstract type $typeName <: $supername end)) - end -end - -function registerclass(name::AbstractString, classType::Type) - registerclass(Symbol(name), classType) -end -function registerclass(name::Symbol, classType::Type) - if !(classType <: Union{Array, String}) && !haskey(types, name) - types[name] = classType - end - infoFor(classforname(string(name))) -end - -gen(name::Symbol; genmode=:none, print=false, eval=true) = _gen(classforname(string(name)), genmode, print, eval) -gen(name::AbstractString; genmode=:none, print=false, eval=true) = _gen(classforname(name), genmode, print, eval) -gen(pxy::JProxy{T, C}) where {T, C} = gen(C) -gen(class::JClass; genmode=:none, print=false, eval=true) = _gen(class, genmode, eval) -function _gen(class::JClass, genmode, print, evalResult) - n = legalClassName(class) - gen = GenInfo() - genClass(class, gen) - if genmode == :deep - while !isempty(gen.deps) - cls = pop!(gen.deps) - !hasClass(gen, cls) && genClass(classforname(string(cls)), gen) - end - else - while !isempty(gen.deps) - cls = pop!(gen.deps) - !hasClass(gen, cls) && genType(classforname(string(cls)), gen) - end - end - expr = :(begin $(gen.typeCode...); $(gen.code...); $(genClasses(getname.(gen.classList))...); end) - if print - for e in expr.args - println(e) - end - end - evalResult && eval(expr) -end - -function genType(class, gen::GenInfo) - name = getname(class) - sc = superclass(class) - push!(genned, Symbol(legalClassName(class))) - if !isNull(sc) - if !(Symbol(legalClassName(sc)) in genned) - genType(getcomponentclass(sc), gen) - end - supertype = typeNameFor(sc) - cType = componentType(supertype) - genTypeDecl(name, cType, gen) - else - genTypeDecl(name, :java_lang, gen) - end -end - -genClass(class::JClass, gen::GenInfo) = genClass(class, gen, infoFor(class)) -function genClass(class::JClass, gen::GenInfo, info::JClassInfo) - name = getname(class) - if !(Symbol(name) in genned) - gen.fielddicts[legalClassName(class)] = fielddict(class) - push!(gen.classList, class) - sc = superclass(class) - #verbose("SUPERCLASS OF $name is $(isNull(sc) ? "" : "not ")null") - push!(genned, Symbol(legalClassName(class))) - if !isNull(sc) - supertype = typeNameFor(sc) - cType = componentType(supertype) - !hasClass(gen, cType) && genClass(sc, gen) - genTypeDecl(name, cType, gen) - else - genTypeDecl(name, :java_lang, gen) - end - genMethods(class, gen, info) - end -end - -GenInfo() = GenInfo([], [], Set(), [], Dict(), Dict()) - -function GenArgInfo(index, info::JMethodInfo, gen::GenInfo) - javaType = info.argTypes[index] - GenArgInfo(Symbol("a" * string(index)), javaType, argType(javaType, gen), argSpec(javaType, gen)) -end - -argType(t, gen) = t -argType(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String -argType(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JLegalArg -argType(::Type{<: Number}, gen) = Number -argType(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) - -argSpec(t, gen) = t -argSpec(::Type{JavaObject{Symbol("java.lang.String")}}, gen) = String -argSpec(::Type{JavaObject{Symbol("java.lang.Object")}}, gen) = :JObject -argSpec(::Type{<: Number}, gen) = Number -argSpec(typ::Type{JavaObject{T}}, gen) where T = :(JProxy{<:$(typeNameFor(T, gen)), T}) -argSpec(arg::GenArgInfo) = arg.spec - legalClassName(pxy::JProxy) = legalClassName(getclassname(pxystatic(pxy) ? pxyptr(pxy) : getclass(pxyptr(pxy)))) legalClassName(cls::JavaObject) = legalClassName(getname(cls)) legalClassName(cls::Symbol) = legalClassName(string(cls)) @@ -561,16 +439,6 @@ componentType(sym::Symbol) = sym Attempt to return the type for thing, otherwise return a symbol representing the type, should it come to exist """ -typeNameFor(T::Symbol, gen::GenInfo) = typeNameFor(string(T), gen) -function typeNameFor(T::AbstractString, gen::GenInfo) - aType, dims = arrayinfo(T) - c = dims != 0 ? aType : T - csym = Symbol(c) - if (dims == 0 || length(c) > 1) && !(csym in gen.deps) && !hasClass(gen, csym) && !get(typeInfo, c, genericFieldInfo).primitive - push!(gen.deps, csym) - end - typeNameFor(T) -end typeNameFor(t::Type) = t typeNameFor(::Type{JavaObject{T}}) where T = typeNameFor(string(T)) typeNameFor(class::JClass) = typeNameFor(legalClassName(class)) @@ -603,86 +471,6 @@ macro jp(s) :(JProxy{$(s), Symbol($(classnamefor(s)))}) end -function argCode(arg::GenArgInfo) - argname = arg.name - if arg.juliaType == String - argname - elseif arg.juliaType == JLegalArg - :(box($argname)) - elseif arg.juliaType == Number - :($(arg.javaType)($argname)) - else - argname - end -end - -function fieldEntry((name, fld)) - fieldType = JavaObject{Symbol(legalClassName(fld.owner))} - name => :(jfield($(fld.typeInfo.class), $(string(name)), $fieldType)) -end - -function genMethods(class, gen, info) - methodList = listmethods(class) - classname = legalClassName(class) - gen.methodDicts[classname] = methods = Dict() - typeName = typeNameFor(classname, gen) - classVar = Symbol("class_" * string(typeName)) - fieldsVar = Symbol("fields_" * string(typeName)) - methodsVar = Symbol("staticMethods_" * string(typeName)) - push!(gen.code, :($classVar = classforname($classname))) - push!(gen.code, :($fieldsVar = Dict([$([fieldEntry(f) for f in gen.fielddicts[classname]]...)]))) - push!(gen.code, :($methodsVar = Set($([string(n) for (n, m) in info.methods if any(x->x.static, m)])))) - push!(gen.code, :(function Base.getproperty(p::JProxy{T, C}, name::Symbol) where {T <: $typeName, C} - if (f = get($fieldsVar, name, nothing)) != nothing - getField(p, name, f) - else - JMethodProxy(name, $typeName, p, emptyset) - end - end)) - for nameSym in sort(collect(keys(info.methods))) - name = string(nameSym) - multiple = length(info.methods[nameSym]) > 1 - symId = 0 - for minfo in info.methods[nameSym] - owner = javaType(minfo.owner) - if isSame(class.ptr, minfo.owner.ptr) - symId += 1 - args = (GenArgInfo(i, minfo, gen) for i in 1:length(minfo.argTypes)) - argDecs = (:($(arg.name)::$(arg.juliaType)) for arg in args) - methodIdName = Symbol("method_" * string(typeName) * "__" * name * (multiple ? string(symId) : "")) - callinfo = jnicalls[minfo.typeInfo.classname] - push!(gen.code, :($methodIdName = getmethodid($(minfo.static), $classVar, $name, $(legalClassName(minfo.returnClass)), $(legalClassName.(minfo.argClasses))))) - push!(gen.code, :(function (pxy::JMethodProxy{Symbol($name), <: $typeName})($(argDecs...))::$(genReturnType(minfo, gen)) - verbose($("Generated method $name$(multiple ? "(" * string(symId) * ")" : "")")) - $(genConvertResult(minfo.typeInfo.convertType, minfo, :(call(pxy.obj, $methodIdName, $(static ? callinfo.static : callinfo.instance), $(minfo.typeInfo.juliaType), ($(argSpec.(args)...),), $((argCode(arg) for arg in args)...))))) - end)) - end - end - end - push!(gen.code, :(push!(genned, Symbol($(legalClassName(class)))))) -end - -function genReturnType(methodInfo, gen) - t = methodInfo.typeInfo.convertType - if methodInfo.typeInfo.primitive || t <: String || t == Nothing - t - else - :(JProxy{<:$(typeNameFor(methodInfo.returnType, gen))}) - end -end - - -genConvertResult(toType::Type{Bool}, info, expr) = :($expr != 0) -genConvertResult(toType::Type{String}, info, expr) = :(unsafe_string($expr)) -genConvertResult(toType::Type{<:JBoxTypes}, info, expr) = :(unbox($(toType.parameters[1]), $expr)) -function genConvertResult(toType, info, expr) - if isVoid(info) || info.typeInfo.primitive - expr - else - :(asJulia($toType, $expr)) - end -end - isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 unionize(::Type{T1}, ::Type{T2}) where {T1, T2} = Union{T1, T2} @@ -699,12 +487,12 @@ end function JClassInfo(class::JClass) n = Symbol(legalClassName(class)) - verbose("INFO FOR $(string(n))") + @verbose("INFO FOR $(string(n))") sc = superclass(class) parentinfo = !isNull(sc) ? _infoFor(sc) : nothing interfaces = [_infoFor(cl) for cl in allinterfaces(class)] tname = typeNameFor(string(n)) - #verbose("JCLASS INFO FOR ", n) + #@verbose("JCLASS INFO FOR ", n) jtype = if tname == String String elseif isa(tname, Type) && tname <: Array @@ -718,8 +506,6 @@ function JClassInfo(class::JClass) classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) end -genClasses(classNames) = (:(registerclass($name, $(Symbol(typeNameFor(name))))) for name in reverse(classNames)) - typeFor(::Type{JavaObject{T}}) where T = typeFor(T) function typeFor(sym::Symbol) aType, dims = arrayinfo(string(sym)) @@ -741,12 +527,16 @@ asJulia(t, obj) = obj asJulia(::Type{Bool}, obj) = obj != 0 asJulia(t, obj::JBoxed) = unbox(obj) function asJulia(x, ptr::Ptr{Nothing}) - verbose("ASJULIA: ", repr(ptr)) + @verbose("ASJULIA: ", repr(ptr)) if ptr == C_NULL nothing else - verbose("UNBOXING ", ptr) - unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ptr))))}, ptr) + ref = newglobalref(ptr) + @verbose("UNBOXING ", ref) + @verbose(" CLASS: ", getclassname(getclass(ref))) + result = unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ref))))}, ref) + deleteglobalref(ref) + result end end @@ -754,15 +544,18 @@ box(str::AbstractString) = str box(pxy::JProxy) = ptrObj(pxy) unbox(obj) = obj -unbox(::Type{T}, obj) where T = obj +function unbox(::Type, obj) + @Verbose("NOOP UNBOXED $(typeof(obj))") + obj +end +unbox(::Type{JavaObject{Symbol("java.lang.String")}}, obj::Ptr{Nothing}) = unsafe_string(obj) function unbox(::Type{JavaObject{T}}, obj::Ptr{Nothing}) where T if obj == C_NULL nothing else - #verbose("UNBOXING ", T) - (get(juliaConverters, string(T)) do - (x)-> JProxy(x) - end)(obj) + result = JProxy(obj) + @Verbose("UNBOXED $(obj) TO $(typeof(result)), $(pxyptr(result)), CLASS $(getclassname(getclass(obj)))") + result end end @@ -773,7 +566,7 @@ pxystatic(p::JProxy) = getfield(p, :static) ==(j1::JProxy, j2::JProxy) = isSame(pxyptr(j1), pxyptr(j2)) isSame(j1::JavaObject, j2::JavaObject) = isSame(j1.ptr, j2.ptr) -isSame(j1::Ptr{Nothing}, j2::Ptr{Nothing}) = @jnicall(jnifunc.IsSameObject, Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}), j1, j2) != C_NULL +isSame(j1::Ptr{Nothing}, j2::Ptr{Nothing}) = @jnicall(jnifunc.IsSameObject, jboolean, (Ptr{Nothing}, Ptr{Nothing}), j1, j2) != 0 getreturntype(c::JConstructor) = voidClass @@ -826,7 +619,7 @@ function isStatic(meth::Union{JMethod,JField}) jcall(modifiers, "isStatic", jboolean, (jint,), mods) != 0 end -conv(func::Function, typ::AbstractString) = juliaConverters[typ] = func +conv(func::Function, typ::Symbol) = juliaConverters[typ] = func macro typeInf(jclass, sig, jtyp, jBoxType) _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false, string(jclass) * "Value", "java.lang." * string(jBoxType)) @@ -846,9 +639,9 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) quote begin JavaTypeInfo($(sym(jclass)), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) - @jnicall(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), - (field.static ? field.owner.ptr : pxyptr(obj)), field.id, $(object ? :(pxyptr(value)) : :value)) + @jnicallregistered(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), + (field.static ? field.owner.ptr : pxyptr(obj)), field.id, $(object ? :(pxyptr(value)) : :value)) end end end @@ -872,7 +665,8 @@ function _defbox(primclass, boxtype, juliatype, javatype) call(ptr, $boxVar.unboxer, jboolean, ()) != 0 end function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) - _jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0 + #_jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0 + @message(obj, jboolean, $boxVar.unboxer) != 0 end end else @@ -890,7 +684,8 @@ function _defbox(primclass, boxtype, juliatype, javatype) call(ptr, $boxVar.unboxer, $javatype, ()) end function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) - _jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ()) + #_jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ()) + call(obj.ptr, $boxVar.unboxer, $juliatype, ()) end end end @@ -898,8 +693,7 @@ function _defbox(primclass, boxtype, juliatype, javatype) const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) boxer(::Type{$juliatype}) = $boxVar function box(data::$juliatype) - #println("BOXING ", $primname, ", boxvar: ", $(string(boxVar))) - JProxy(_jcall($boxVar.boxClass, $boxVar.boxer, jnifunc.NewObjectA, $boxclass, ($juliatype,), data)) + JProxy(call($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data)) end $varpart end @@ -921,9 +715,9 @@ function initProxy() global classClass = classforname("java.lang.Class") global voidClass = jfield(JavaObject{Symbol("java.lang.Void")}, "TYPE", JClass) global methodid_getmethod = getmethodid("java.lang.Class", "getMethod", "java.lang.reflect.Method", "java.lang.String", "[Ljava.lang.Class;") - conv("java.lang.String") do x; unsafe_string(x); end - conv("java.lang.Integer") do x; @jp(java_lang_Integer)(x).intValue(); end - conv("java.lang.Long") do x; @jp(java_lang_Long)(x).longValue(); end + conv(Symbol("java.lang.String")) do x; unsafe_string(x); end + conv(Symbol("java.lang.Integer")) do x; @jp(java_lang_Integer)(x).intValue(); end + conv(Symbol("java.lang.Long")) do x; @jp(java_lang_Long)(x).longValue(); end push!(typeInfo, "void" => @vtypeInf(void, jint, "V", Nothing, Object, false, Void), "boolean" => @typeInf(boolean, "Z", Bool, Boolean), @@ -999,18 +793,18 @@ getmethodid(static, cls::JClass, name, rettype, argtypes) = getmethodid(static, function getmethodid(static::Bool, clsname::AbstractString, name::AbstractString, rettype::AbstractString, argtypes::Vector{<:Union{JClass, AbstractString}}) sig = proxyMethodSignature(rettype, argtypes) jclass = metaclass(clsname) - #verbose(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + #@verbose(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, # (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), # jclass, name, sig)) @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - jclass, name, sig) + (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + jclass, name, sig) end function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} @jnicall(static ? jnifunc.GetStaticFieldID : jnifunc.GetFieldID, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) + (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + metaclass(legalClassName(cls)), name, proxyClassSignature(string(C))) end function infoSignature(cls::AbstractString) @@ -1063,7 +857,7 @@ function _infoFor(class::JClass) nothing else name = legalClassName(class) - #verbose("INFO FOR ", name) + #@verbose("INFO FOR ", name) haskey(classes, name) ? classes[name] : classes[name] = JClassInfo(class) end end @@ -1075,7 +869,7 @@ function classforlegalname(n::AbstractString) try (i = get(typeInfo, n, nothing)) != nothing && i.primitive ? i.primClass : classforname(n) catch x - #verbose("Error finding class: $n, type: $(typeof(n))") + #@verbose("Error finding class: $n, type: $(typeof(n))") throw(x) end end @@ -1099,17 +893,17 @@ arraylength(obj::JavaObject) = arraylength(obj.ptr) arraylength(obj) = @jnicall(jnifunc.GetArrayLength, jint, (Ptr{Nothing},), obj) arrayat(obj::JavaObject, i) = arrayat(obj.ptr, i) -arrayat(obj, i) = @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, - (Ptr{Nothing}, jint), - obj, jint(i) - 1) +arrayat(obj, i) = @jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + obj, jint(i) - 1) Base.length(obj::JavaObject) = Base.length(JProxy(obj)) Base.length(pxy::JProxy{>:Array}) = arraylength(pxyptr(pxy)) function Base.getindex(pxy::JProxy{>:Array}, i::Integer) - asJulia(T, @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, - (Ptr{Nothing}, jint), - pxyptr(pxy), jint(i) - 1)) + asJulia(T, @jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + pxyptr(pxy), jint(i) - 1)) end function methoddict(class) @@ -1133,9 +927,9 @@ isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 superclass(obj::JavaObject) = jcall(obj, "getSuperclass", @jimport(java.lang.Class), ()) function getField(p::JProxy, field::JFieldInfo) - asJulia(field.typeInfo.juliaType, @jnicall(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{Nothing}), - pxystatic(p) ? getclass(obj) : pxyptr(p), field.id)) + asJulia(field.typeInfo.juliaType, @jnicallregistered(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{Nothing}), + pxystatic(p) ? getclass(obj) : pxyptr(p), field.id)) end function Base.getproperty(p::JProxy{T}, name::Symbol) where T @@ -1156,10 +950,9 @@ setter(field::JFieldInfo) = field.static ? field.typeInfo.staticSetter : field.t getproxyfield(p::JProxy, field::JReadonlyField) = field.get(pxyptr(p)) function getproxyfield(p::JProxy, field::JFieldInfo) static = pxystatic(p) - ptr = field.static ? C_NULL : pxyptr(p) - result = _getproxyfield(ptr, field) + result = _getproxyfield(field.static ? C_NULL : pxyptr(p), field) geterror() - verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) + @verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) asJulia(field.typeInfo.convertType, result) end macro defgetfield(juliat, javat = juliat) @@ -1168,7 +961,11 @@ macro defgetfield(juliat, javat = juliat) (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), penv, p, field.id) result == C_NULL && geterror() - result + $(if juliat == Ptr{Nothing} || juliat == :(Ptr{Nothing}) + :(registerreturn(result)) + else + :(result) + end) end) end @defgetfield(<:Any, Ptr{Nothing}) @@ -1181,34 +978,34 @@ end @defgetfield(jfloat) @defgetfield(jdouble) -function setproxyfield(p::JProxy, field::JFieldInfo{T}, value) where T - primsetproxyfield(p, field, convert(T, value)) -end +setproxyfield(p::JProxy, field::JFieldInfo{T}, value) where T = primsetproxyfield(p, field, convert(T, value)) setproxyfield(p::JProxy, field::JFieldInfo, value::JProxy) = primsetproxyfield(p, field, pxyptr(value)) setproxyfield(p::JProxy, field::JFieldInfo, value::JavaObject) = primsetproxyfield(p, field, value.ptr) function setproxyfield(p::JProxy, field::JFieldInfo{String}, value::AbstractString) str = JString(convert(String, value)) primsetproxyfield(p, field, str.ptr) end + function primsetproxyfield(p::JProxy, field::JFieldInfo, value) - result = _setproxyfield(pxystatic(p) ? C_NULL : pxyptr(p), field, value) + _setproxyfield(pxystatic(p) ? C_NULL : pxyptr(p), field, value) geterror() - verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) - asJulia(field.typeInfo.convertType, result) end -function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{JavaObject{T}}, value::Ptr{Nothing}) where T - @jnicall(setter(field), Nothing, - (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - p, field.id, value) + +function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo, value::Ptr{Nothing}) + #@jnicall(setter(field), Nothing, + # (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + # p, field.id, value) + ccall(setter(field), Nothing, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, p, field.id, value) end + macro defsetfield(juliat, javat = juliat) :(function _setproxyfield(p::Ptr{Nothing}, field::JFieldInfo{$juliat}, value::$javat) - local result = ccall(setter(field), Nothing, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $javat), - penv, p, field.id, value) - result == C_NULL && geterror() - result - end) + ccall(setter(field), Nothing, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $javat), + penv, p, field.id, value) + end) end @defsetfield(String, Ptr{Nothing}) @defsetfield(Bool, jboolean) @@ -1235,17 +1032,14 @@ end function (pxy::JMethodProxy{N})(args...) where N targets = Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)) - #verbose("LOCATING MESSAGE ", N, " FOR ARGS ", repr(args)) if !isempty(targets) # Find the most specific method argTypes = typeof(args).parameters meth = reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) - verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) + @Verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) if meth.static staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) else - #call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) - #println("argTypes: ", meth.argTypes) call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.argTypes, args...) end else @@ -1261,16 +1055,6 @@ function findmethod(pxy::JMethodProxy, args...) end end -withlocalref(func, result::Any) = func(result) -function withlocalref(func, ptr::Ptr{Nothing}) - ref = ccall(jnifunc.NewLocalRef, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ptr) - try - func(ref) - finally - deletelocalref(ptr::Ptr{Nothing}) = ccall(jnifunc.DeleteLocalRef, Nothing, (Ptr{JNIEnv}, Ptr{Nothing}), penv, ref) - end -end - function filterStatic(pxy::JMethodProxy, targets) static = pxy.static Set(target for target in targets if target.static == static) @@ -1355,22 +1139,7 @@ end function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes::Tuple, args...) where T ptr == C_NULL && error("Attempt to call method on Java NULL") savedargs, convertedargs = convert_args(argtypes, args...) - for i in 1:length(argtypes) - if isa(savedargs[i], JavaObject) && convertedargs[i] != 0 - aptr = Ptr{Nothing}(convertedargs[i]) - if getreftype(aptr) == 1 - #println("LOCAL REF") - push!(allocatedrefs, aptr) - end - #println("class: ", getclassname(getclass(aptr)), " type: ", argtypes[i], " arg: ", args[i], " saved: ", savedargs[i], " REFTYPE: ", getreftype(aptr)) - end - end - verbose("CALL METHOD RETURNING $rettype WITH ARG TYPES $(argtypes): ACTUAL TYPES $(join(types, ", "))") result = _call(T, ptr, mId, convertedargs) - if rettype <: JavaObject && result != C_NULL - #println("RESULT REF TYPE: ", getreftype(result)) - push!(allocatedrefs, result) - end result == C_NULL && geterror() result = asJulia(rettype, result) deletelocals() @@ -1384,8 +1153,8 @@ macro defcall(t, f, ft) end _call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, obj, mId, args) + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, obj, mId, args) @defcall(Bool, Boolean, jboolean) @defcall(jbyte, Byte, jbyte) @defcall(jchar, Char, jchar) @@ -1399,9 +1168,12 @@ _call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T savedargs, convertedargs = convert_args(argtypes, args...) result = _staticcall(T, mId, convertedargs) - verbose("CONVERTING RESULT ", repr(result), " TO ", rettype) result == C_NULL && geterror() - verbose("RETTYPE: ", rettype) + if rettype <: JavaObject && result != C_NULL + registerreturn(result) + end + @verbose("RETTYPE: ", rettype) + @verbose("CONVERTING RESULT ", repr(result), " TO ", rettype) result = asJulia(rettype, result) deletelocals() result diff --git a/test/runtests.jl b/test/runtests.jl index 1a649c8..3e10834 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,6 +5,8 @@ import Dates using Base.GC: gc +const pxyptr = JavaCall.pxyptr + JavaCall.init(["-Djava.class.path=$(@__DIR__)"]) # JavaCall.init(["-verbose:gc","-Djava.class.path=$(@__DIR__)"]) # JavaCall.init() @@ -143,8 +145,8 @@ end gc() for i in 1:100000 a=JString("A"^10000); #deleteref(a); + JavaCall.deletelocals() if (i%10000 == 0) - JavaCall.deletelocals() gc() end end @@ -260,6 +262,7 @@ end @test(t.stringField == "hello") @test(t.toString() == "Test(3, hello)") t.objectField = t + @test(t.objectField == t) @test(t.objectField.stringField == "hello") @test(t.objectField.getInt() == 3) @test(t.objectField.getString() == "hello") From 590ed821c2d2ec4fc64f6f35bcc425705528b1bc Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 11 Nov 2018 20:36:11 +0200 Subject: [PATCH 24/43] Boxing still not working --- src/proxy.jl | 51 +++++++++++++++++++++++++++++++----------------- test/runtests.jl | 3 +++ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 73d9a83..9cf69ad 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -8,7 +8,7 @@ global initialized = false setVerbose() = global useVerbose = true clearVerbose() = global useVerbose = false -location(source) = replace(string(source.file), r"^.*/([^/]*)$" => s"\1") * string(source.line) * ": " +location(source) = replace(string(source.file), r"^.*/([^/]*)$" => s"\1") * ":" * string(source.line) * ": " macro verbose(args...) :(useVerbose && println($(location(__source__)), $(esc.(args)...))) @@ -312,9 +312,7 @@ macro staticmessage(rettype, methodid, args...) end registerreturn(x) = x -function registerreturn(x::Ptr{Nothing}) - registerlocal(x) -end +registerreturn(x::Ptr{Nothing}) = registerlocal(x) function arrayinfo(str) if (m = match(r"^(\[+)(.)$", str)) != nothing @@ -532,7 +530,7 @@ function asJulia(x, ptr::Ptr{Nothing}) nothing else ref = newglobalref(ptr) - @verbose("UNBOXING ", ref) + @verbose("PROXY FOR ", ref) @verbose(" CLASS: ", getclassname(getclass(ref))) result = unbox(JavaObject{Symbol(legalClassName(getclassname(getclass(ref))))}, ref) deleteglobalref(ref) @@ -541,7 +539,7 @@ function asJulia(x, ptr::Ptr{Nothing}) end box(str::AbstractString) = str -box(pxy::JProxy) = ptrObj(pxy) +box(pxy::JProxy) = pxyptr(pxy) unbox(obj) = obj function unbox(::Type, obj) @@ -647,11 +645,11 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) end end -macro defbox(primclass, boxtype, juliatype, javatype = juliatype) - :(eval(_defbox($(sym(primclass)), $(sym(boxtype)), $(sym(juliatype)), $(sym(javatype))))) +macro defbox(primclass, boxtype, juliatype, javatype, boxclassname) + :(eval(_defbox($(sym(primclass)), $(sym(boxtype)), $(sym(juliatype)), $(sym(javatype)), $(sym(boxclassname))))) end -function _defbox(primclass, boxtype, juliatype, javatype) +function _defbox(primclass, boxtype, juliatype, javatype, boxclassname) boxclass = JavaObject{Symbol(classnamefor(boxtype))} primname = string(primclass) boxVar = Symbol(primname * "Box") @@ -693,7 +691,13 @@ function _defbox(primclass, boxtype, juliatype, javatype) const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) boxer(::Type{$juliatype}) = $boxVar function box(data::$juliatype) - JProxy(call($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data)) + #call($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data) + #@message($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data) + result = ccall(jnifunc.$(Symbol("Call" * string(boxclassname) * "Method")), Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $juliatype), + penv, $boxVar.boxClass.ptr, $boxVar.boxer, data) + result == C_NULL && geterror() + registerreturn(result) end $varpart end @@ -738,14 +742,14 @@ function initProxy() global methodId_class_isInterface = getmethodid("java.lang.Class", "isInterface", "boolean") global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) global initialized = true - @defbox(boolean, java_lang_Boolean, Bool, jboolean) - @defbox(char, java_lang_Character, Char, jchar) - @defbox(byte, java_lang_Byte, jbyte) - @defbox(short, java_lang_Short, jshort) - @defbox(int, java_lang_Integer, jint) - @defbox(long, java_lang_Long, jlong) - @defbox(float, java_lang_Float, jfloat) - @defbox(double, java_lang_Double, jdouble) + @defbox(boolean, java_lang_Boolean, Bool, jboolean, Boolean) + @defbox(char, java_lang_Character, Char, jchar, Character) + @defbox(byte, java_lang_Byte, jbyte, jbyte, Byte) + @defbox(short, java_lang_Short, jshort, jshort, Short) + @defbox(int, java_lang_Integer, jint, jint, Integer) + @defbox(long, java_lang_Long, jlong, jlong, Long) + @defbox(float, java_lang_Float, jfloat, jfloat, Float) + @defbox(double, java_lang_Double, jdouble, jdouble, Double) end metaclass(class::AbstractString) = metaclass(Symbol(class)) @@ -1146,6 +1150,17 @@ function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{T}, argtypes:: result end +function call(ptr::Ptr{Nothing}, mId::Ptr{Nothing}, rettype::Type{Ptr{Nothing}}, argtypes::Tuple, args...) + ptr == C_NULL && error("Attempt to call method on Java NULL") + savedargs, convertedargs = convert_args(argtypes, args...) + result = _call(Ptr{Nothing}, ptr, mId, convertedargs) + result == C_NULL && geterror() + getreftype(result) == 1 && registerlocal(result) + result = asJulia(rettype, result) + deletelocals() + result +end + macro defcall(t, f, ft) :(_call(::Type{$t}, obj, mId, args) = ccall(jnifunc.$(Symbol("Call" * string(f) * "MethodA")), $ft, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), diff --git a/test/runtests.jl b/test/runtests.jl index 3e10834..5a4cf92 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -251,6 +251,9 @@ end removed = a.remove(0) @test typeof(removed) == String @test removed == "one" + println("@@@ BOXING NOT WORKING PROPERLY @@@") + a.add(1) + @test a.get(0) == 1 end @testset "proxy_test_class" begin From 7d84f8276a80d145b934021835bfe0182c6ad864 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 12 Nov 2018 18:52:47 +0200 Subject: [PATCH 25/43] All tests work (including boxing) --- src/proxy.jl | 8 ++------ test/runtests.jl | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 9cf69ad..7e3c2fc 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -691,9 +691,7 @@ function _defbox(primclass, boxtype, juliatype, javatype, boxclassname) const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) boxer(::Type{$juliatype}) = $boxVar function box(data::$juliatype) - #call($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data) - #@message($boxVar.boxClass.ptr, $boxVar.boxer, $boxVar.boxType, ($juliatype,), data) - result = ccall(jnifunc.$(Symbol("Call" * string(boxclassname) * "Method")), Ptr{Nothing}, + result = ccall(jnifunc.$(Symbol("NewObject")), Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $juliatype), penv, $boxVar.boxClass.ptr, $boxVar.boxer, data) result == C_NULL && geterror() @@ -1093,10 +1091,8 @@ interfacehas(x, y) = false # ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG function convert_arg(t::Type{<:Union{JObject, java_lang_Object}}, x::JPrimitive) - #result = JavaObject(box(x)) - #convert_arg(typeof(result), result) result = box(x) - result, pxyptr(result) + result, result end convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) diff --git a/test/runtests.jl b/test/runtests.jl index 5a4cf92..382551d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -251,9 +251,9 @@ end removed = a.remove(0) @test typeof(removed) == String @test removed == "one" - println("@@@ BOXING NOT WORKING PROPERLY @@@") a.add(1) @test a.get(0) == 1 + @test a.toString() == "[1]" end @testset "proxy_test_class" begin From 6267c074309b7a2db0b5cc7a4e3704b07709459c Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 12 Nov 2018 18:55:55 +0200 Subject: [PATCH 26/43] Removed GC from tests --- test/runtests.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 382551d..221bbda 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -145,10 +145,6 @@ end gc() for i in 1:100000 a=JString("A"^10000); #deleteref(a); - JavaCall.deletelocals() - if (i%10000 == 0) - gc() - end end @testset "sinx_1" begin From 0e6cbbf06d53d3e3cd6833053b2d4c396a4e6349 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 12 Nov 2018 18:56:20 +0200 Subject: [PATCH 27/43] removed GC from tests --- test/runtests.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 221bbda..a3c645a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -140,9 +140,6 @@ end end # Test Memory allocation and de-allocatios -# the following loop fails with an OutOfMemoryException in the absence of de-allocation -# However, since Java and Julia memory are not linked, and manual gc() is required. -gc() for i in 1:100000 a=JString("A"^10000); #deleteref(a); end From 2354c5a793109ad22c1103bf8fbfd2cbc239f146 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 12 Nov 2018 19:09:01 +0200 Subject: [PATCH 28/43] Added test for interface argument, a.addAll(b) --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index a3c645a..583fced 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -247,6 +247,9 @@ end a.add(1) @test a.get(0) == 1 @test a.toString() == "[1]" + b = JProxy(JAL(())) + b.addAll(a) + @test a.toString() == b.toString() end @testset "proxy_test_class" begin From f577272043160d34ced36d8d7b47034a79856ec8 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 25 Nov 2018 15:54:14 +0200 Subject: [PATCH 29/43] getting array argument conversion working, fixing bugs --- src/JavaCall.jl | 2 +- src/collections.jl | 24 ---- src/proxy.jl | 315 ++++++++++++++++++++++++++++++++++----------- test/runtests.jl | 14 ++ 4 files changed, 255 insertions(+), 100 deletions(-) delete mode 100644 src/collections.jl diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 9b2c50c..fa18278 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -4,7 +4,7 @@ export JavaObject, JavaMetaClass, JObject, JClass, JMethod, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, - narrow, JProxy + narrow, JProxy, @class, instance # using Compat, Compat.Dates diff --git a/src/collections.jl b/src/collections.jl deleted file mode 100644 index 487b833..0000000 --- a/src/collections.jl +++ /dev/null @@ -1,24 +0,0 @@ -function Base.iterate(col::JProxy{<:java_lang}) - cl = getclass(pxyObj(col)) - info = infoFor(cl) -end -function Base.iterate(col::JProxy{<:java_util_AbstractCollection}) - i = col.iterator() - nextGetter(col, i)() -end -Base.iterate(col::JProxy{<:java_util_AbstractCollection}, state) = state() -Base.IteratorSize(::JProxy{<:java_util_AbstractCollection}) = Base.HasLength() -Base.length(col::JProxy{<:java_util_AbstractCollection}) = col.size() - -function nextGetter(col::JProxy{<:java_util_AbstractCollection}, iter) - let pending = true, value # memoize value - function () - if pending - pending = false - value = iter.hasNext() ? (iter.next(), nextGetter(col, iter)) : nothing - else - value - end - end - end -end diff --git a/src/proxy.jl b/src/proxy.jl index 7e3c2fc..0bec451 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -1,5 +1,15 @@ # See documentation for JProxy for infomation +# TODO argtypefor(J.classforlegalname("[I")) returns Array{JavaCall.java_lang,1} +# use sigtypes[class] to get primitive type +# +# TODO -- types' keys should probably be strings, not symbols +# +# +# TODO switch from method.dynArgTypes to method.argTypes to allow full Julia type matching (array, etc.) +# TODO add specificity for Array types and add conversion rules for Array types +# TODO add iteration and access for lists, collecitons, maps + import Base.== global useVerbose = false @@ -60,6 +70,9 @@ const types = Dict() @defjtype java_lang_Byte <: java_lang_Number @defjtype java_lang_Character <: java_lang_Object @defjtype java_lang_Boolean <: java_lang_Object +@defjtype java_lang_Iterable <: interface +@defjtype java_util_List <: interface +@defjtype java_util_Collection <: interface # types const modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} @@ -88,7 +101,6 @@ const JBoxed = Union{ } struct JavaTypeInfo - setterFunc classname::Symbol # legal classname as a symbol signature::AbstractString juliaType::Type # the Julia representation of the Java type, like jboolean (which is a UInt8), for call-in @@ -102,6 +114,8 @@ struct JavaTypeInfo staticGetter::Ptr{Nothing} setter::Ptr{Nothing} staticSetter::Ptr{Nothing} + newarray::Ptr{Nothing} + arrayregionsetter::Ptr{Nothing} end struct JReadonlyField @@ -173,9 +187,7 @@ mutable struct PtrBox PtrBox(obj::JavaObject) = PtrBox(obj.ptr) function PtrBox(ptr::Ptr{Nothing}) - #registerlocal(ptr) - newglobalref(ptr) - finalizer(finalizebox, new(ptr)) + finalizer(finalizebox, new(newglobalref(ptr))) end end @@ -259,7 +271,8 @@ const dynamicTypeCache = Dict() global genericFieldInfo global objectClass -global sigTypes +global stringClass +global sigtypes macro jnicall(func, rettype, types, args...) _jnicall(func, rettype, types, args) @@ -316,7 +329,7 @@ registerreturn(x::Ptr{Nothing}) = registerlocal(x) function arrayinfo(str) if (m = match(r"^(\[+)(.)$", str)) != nothing - signatureClassFor(m.captures[2]), length(m.captures[1]) + string(signatureClassFor(m.captures[2])), length(m.captures[1]) elseif (m = match(r"^(\[+)L(.*);", str)) != nothing m.captures[2], length(m.captures[1]) else @@ -339,19 +352,33 @@ end arraycomponent(::Type{Array{T}}) where T = T -signatureClassFor(name) = length(name) == 1 ? sigTypes[name].classname : name +signatureClassFor(name) = length(name) == 1 ? sigtypes[name].classname : name isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing classtypename(ptr::Ptr{Nothing}) = typeNameFor(getclassname(getclass(ptr))) classtypename(obj::JavaObject{T}) where T = string(T) +instance(obj::Union{JProxy, Nothing}) = obj +instance(obj) = JProxy(box(obj)) # To access static members, use types or metaclasses -# like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` -# or JProxy(JString).valueOf(1) -JProxy(::JavaMetaClass{C}) where C = JProxy(JavaObject{C}) -function JProxy(::Type{JavaObject{C}}) where C - c = Symbol(legalClassName(string(C))) +# like this: `@class(java.lang.Byte).TYPE` +macro class(name::Expr) + :(JProxy(Symbol($(replace(sprint(Base.show_unquoted, name), r"[ ()]"=>""))))) +end +macro class(name::Symbol) + :(JProxy(Symbol($(string(name))))) +end +macro class(name::String) + :(JProxy(Symbol($name))) +end +class(str::String) = JProxy(Symbol(str)) +class(sym::Symbol) = JProxy(sym) +JProxy(::JavaMetaClass{C}) where C = staticproxy(string(C)) +JProxy(::Type{JavaObject{C}}) where C = staticproxy(string(C)) +JProxy(s::Symbol) = staticproxy(string(s)) +function staticproxy(classname) + c = Symbol(legalClassName(classname)) obj = classforname(string(c)) info = infoFor(obj) JProxy{typeFor(c), c}(obj, info, true) @@ -360,9 +387,7 @@ end # To access static members, use types or metaclasses # like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` JProxy(s::AbstractString) = JProxy(JString(s)) -function JProxy{T, C}(ptr::PtrBox) where {T, C} - JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) -end +JProxy{T, C}(ptr::PtrBox) where {T, C} = JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) JProxy(obj::JavaObject) = JProxy(PtrBox(obj)) JProxy(ptr::Ptr{Nothing}) = JProxy(PtrBox(ptr)) function JProxy(obj::PtrBox) @@ -383,11 +408,11 @@ function JProxy(obj::PtrBox) JProxy{info.classtype, c}(obj, info, false) end -function JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter) +function JavaTypeInfo(class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter, newarray, arrayregionsetter) boxClass = classfortype(boxType) primitive = length(signature) == 1 primClass = primitive ? jfield(boxType, "TYPE", JClass) : objectClass - info = JavaTypeInfo(setterFunc, class, signature, juliaType, convertType, primitive, accessorName, boxType, boxClass, primClass, getter, staticGetter, setter, staticSetter) + info = JavaTypeInfo(class, signature, juliaType, convertType, primitive, accessorName, boxType, boxClass, primClass, getter, staticGetter, setter, staticSetter, newarray, arrayregionsetter) info end @@ -419,6 +444,8 @@ function legalClassName(name::AbstractString) info = get(typeInfo, m.captures[1], nothing) base = if info != nothing && info.primitive info.signature + elseif length(m.captures[1]) == 1 + m.captures[1] else "L$(m.captures[1]);" end @@ -445,20 +472,17 @@ function typeNameFor(className::AbstractString) if className == "java.lang.String" String elseif length(className) == 1 - sigTypes[className].convertType + sigtypes[className].convertType else - n = replace(className, "_" => "___") - n = replace(className, "\$" => "_s_") - n = replace(n, "." => "_") - aType, dims = arrayinfo(n) + aType, dims = arrayinfo(className) if dims != 0 Array{typeNameFor(aType), dims} else - t = get(typeInfo, n, genericFieldInfo) + t = get(typeInfo, className, genericFieldInfo) if t.primitive t.juliaType else - sn = Symbol(n) + sn = Symbol(className) get(types, sn, sn) end end @@ -466,7 +490,7 @@ function typeNameFor(className::AbstractString) end macro jp(s) - :(JProxy{$(s), Symbol($(classnamefor(s)))}) + :(JProxy{$s, Symbol($(classnamefor(s)))}) end isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 @@ -505,9 +529,16 @@ function JClassInfo(class::JClass) end typeFor(::Type{JavaObject{T}}) where T = typeFor(T) +typeFor(str::String) = typeFor(Symbol(str)) function typeFor(sym::Symbol) aType, dims = arrayinfo(string(sym)) - dims != 0 ? Array{get(types, Symbol(aType), java_lang), length(dims)} : get(types, sym, java_lang) + if dims == 1 && haskey(typeInfo, aType) + JProxy{Array{get(typeInfo, aType, java_lang_Object).convertType, 1}} + elseif dims != 0 + JProxy{Array{get(types, Symbol(aType), java_lang_Object), dims}} + else + get(types, sym, java_lang_Object) + end end function makeTypeFor(class::JClass) @@ -538,7 +569,7 @@ function asJulia(x, ptr::Ptr{Nothing}) end end -box(str::AbstractString) = str +box(str::AbstractString) = JString(str) box(pxy::JProxy) = pxyptr(pxy) unbox(obj) = obj @@ -620,29 +651,71 @@ end conv(func::Function, typ::Symbol) = juliaConverters[typ] = func macro typeInf(jclass, sig, jtyp, jBoxType) - _typeInf(jclass, Symbol("j" * string(jclass)), sig, jtyp, uppercasefirst(string(jclass)), false, string(jclass) * "Value", "java.lang." * string(jBoxType)) + :(eval(_typeInf($(sym(jclass)), $(sym("j", jclass)), $sig, $(sym(jtyp)), $(uppercasefirst(string(jclass))), false, $(string(jclass)) * "Value", "java.lang." * $(string(jBoxType))))) end macro vtypeInf(jclass, ctyp, sig, jtyp, Typ, object, jBoxType) if typeof(jclass) == String jclass = Symbol(jclass) end - _typeInf(jclass, ctyp, sig, jtyp, Typ, object, "", "java.lang." * string(jBoxType)) + :(eval(_typeInf($(sym(jclass)), $(sym(ctyp)), $sig, $(sym(jtyp)), $(sym(Typ)), $(sym(object)), "", "java.lang." * $(string(jBoxType))))) end -sym(s) = :(Symbol($(string(s)))) +sym(s...) = :(Symbol($(join(string.(s))))) function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) - s = (p, t)-> :(jnifunc.$(Symbol(p * string(t) * "Field"))) - quote - begin - JavaTypeInfo($(sym(jclass)), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ))) do field, obj, value::$(object ? :JavaObject : ctyp) - @jnicallregistered(field.static ? field.typeInfo.staticSetter : field.typeInfo.setter, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{Nothing}, $(object ? :(Ptr{Nothing}) : ctyp)), - (field.static ? field.owner.ptr : pxyptr(obj)), field.id, $(object ? :(pxyptr(value)) : :value)) + j = (strs...)-> :(jnifunc.$(Symbol(reduce(*, string.(strs))))) + s = (p, t)-> j(p, t, "Field") + newarray = (length(sig) == 1 && sig != "V" ? j("New", Typ, "Array") : C_NULL) + arrayregionsetter = (length(sig) == 1 && sig != "V" ? j("Set", Typ, "ArrayRegion") : C_NULL) + arrayset = (length(sig) == 1 && sig != "V" ? j("New", Typ, "Array") : C_NULL) + arrayget = if length(sig) == 1 && sig != "V" + type_ctyp = getfield(JavaCall, ctyp) + type_jtyp = getfield(Core, jtyp) + quote + function arrayget(pxy::JProxy{<:Array{$ctyp}}, index) + result = $type_jtyp[$(type_jtyp(0))] + @jnicall($(j("Get" * Typ * "ArrayRegion")), Nothing, + (Ptr{Nothing}, Csize_t, Csize_t, Ptr{$(jtyp)}), + pxyptr(pxy), index, 1, result) + result == C_NULL && geterror() + $(type_jtyp == Bool ? :(result[1] != 0) : :(result[1])) + end + function arrayset!(pxy::JProxy{<:Array{$ctyp}}, index, value::$ctyp) + valuebuf = $type_jtyp[$type_jtyp(value)] + @jnicall($(j("Set" * Typ * "ArrayRegion")), Nothing, + (Ptr{Nothing}, Csize_t, Csize_t, Ptr{$(jtyp)}), + pxyptr(pxy), index, 1, valuebuf) + geterror() end end + else + :(()) + end + quote + $(arrayget.args...) + push!(typeInfo, $(string(jclass)) => JavaTypeInfo($(sym(jclass)), $sig, $ctyp, $jtyp, $accessor, JavaObject{Symbol($boxType)}, $(s("Get", Typ)), $(s("GetStatic", Typ)), $(s("Set", Typ)), $(s("SetStatic", Typ)), $(newarray), $(arrayregionsetter))) + end +end + +function arrayget(pxy::JProxy{<:Array}, index) + result = @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, Csize_t), + pxyptr(pxy), index) + if result == C_NULL + geterror() + else + getreftype(result) == 1 && registerlocal(result) + result = asJulia(Ptr{Nothing}, result) + deletelocals() end + result +end +function arrayset!(pxy::JProxy{<:Array}, index, value::JProxy) + @jnicall(jnifunc.SetObjectArrayElement, Nothing, + (Ptr{Nothing}, Csize_t, Ptr{Nothing}), + pxyptr(pxy), index, pxyptr(value)) + geterror() end macro defbox(primclass, boxtype, juliatype, javatype, boxclassname) @@ -691,7 +764,7 @@ function _defbox(primclass, boxtype, juliatype, javatype, boxclassname) const $boxVar = boxers[$primname] = Boxing(typeInfo[$primname]) boxer(::Type{$juliatype}) = $boxVar function box(data::$juliatype) - result = ccall(jnifunc.$(Symbol("NewObject")), Ptr{Nothing}, + result = ccall(jnifunc.NewObject, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, $juliatype), penv, $boxVar.boxClass.ptr, $boxVar.boxer, data) result == C_NULL && geterror() @@ -715,25 +788,26 @@ function initProxy() ) global objectClass = classforname("java.lang.Object") global classClass = classforname("java.lang.Class") + global stringClass = classforname("java.lang.String") global voidClass = jfield(JavaObject{Symbol("java.lang.Void")}, "TYPE", JClass) global methodid_getmethod = getmethodid("java.lang.Class", "getMethod", "java.lang.reflect.Method", "java.lang.String", "[Ljava.lang.Class;") conv(Symbol("java.lang.String")) do x; unsafe_string(x); end conv(Symbol("java.lang.Integer")) do x; @jp(java_lang_Integer)(x).intValue(); end conv(Symbol("java.lang.Long")) do x; @jp(java_lang_Long)(x).longValue(); end - push!(typeInfo, - "void" => @vtypeInf(void, jint, "V", Nothing, Object, false, Void), - "boolean" => @typeInf(boolean, "Z", Bool, Boolean), - "byte" => @typeInf(byte, "B", Int8, Byte), - "char" => @typeInf(char, "C", Char, Character), - "short" => @typeInf(short, "S", Int16, Short), - "int" => @typeInf(int, "I", Int32, Integer), - "float" => @typeInf(float, "F", Float32, Float), - "long" => @typeInf(long, "J", Int64, Long), - "double" => @typeInf(double, "D", Float64, Double), - "java.lang.String" => @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object), - ) - global sigTypes = Dict([inf.signature => inf for (key, inf) in typeInfo if inf.primitive]) - global genericFieldInfo = @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object;", JObject, Object, true, Object) + @vtypeInf(void, jint, "V", Nothing, Object, false, Void) + @typeInf(boolean, "Z", Bool, Boolean) + @typeInf(byte, "B", Int8, Byte) + @typeInf(char, "C", Char, Character) + @typeInf(short, "S", Int16, Short) + @typeInf(int, "I", Int32, Integer) + @typeInf(float, "F", Float32, Float) + @typeInf(long, "J", Int64, Long) + @typeInf(double, "D", Float64, Double) + @vtypeInf("java.lang.String", String, "Ljava/lang/String;", String, Object, true, Object) + @vtypeInf("java.lang.Object", Any, "Ljava/lang/Object;", JObject, Object, true, Object) + global sigtypes = Dict([inf.signature => inf for (key, inf) in typeInfo if inf.primitive]) + global juliatojava = Dict([inf.convertType => inf for (key, inf) in typeInfo]) + global genericFieldInfo = typeInfo["java.lang.Object"] global methodId_object_getClass = getmethodid("java.lang.Object", "getClass", "java.lang.Class") global methodId_class_getName = getmethodid("java.lang.Class", "getName", "java.lang.String") global methodId_class_getInterfaces = getmethodid("java.lang.Class", "getInterfaces", "[Ljava.lang.Class;") @@ -798,9 +872,14 @@ function getmethodid(static::Bool, clsname::AbstractString, name::AbstractString #@verbose(@macroexpand @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, # (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), # jclass, name, sig)) - @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, - (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), - jclass, name, sig) + try + @jnicall(static ? jnifunc.GetStaticMethodID : jnifunc.GetMethodID, Ptr{Nothing}, + (Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), + jclass, name, sig) + catch err + println("ERROR GETTING METHOD $clsname.$name($(join(argtypes, ",")))") + throw(err) + end end function fieldId(name, typ::Type{JavaObject{C}}, static, field, cls::JClass) where {C} @@ -885,29 +964,20 @@ listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) function fielddict(class::JClass) if isArray(class) - Dict([:length => JReadonlyField((obj)->arraylength(obj.ptr))]) + Dict([:length => JReadonlyField((ptr)->arraylength(ptr))]) else Dict([Symbol(getname(item)) => JFieldInfo(item) for item in listfields(class)]) end end arraylength(obj::JavaObject) = arraylength(obj.ptr) -arraylength(obj) = @jnicall(jnifunc.GetArrayLength, jint, (Ptr{Nothing},), obj) +arraylength(obj::Ptr{Nothing}) = @jnicall(jnifunc.GetArrayLength, jint, (Ptr{Nothing},), obj) arrayat(obj::JavaObject, i) = arrayat(obj.ptr, i) arrayat(obj, i) = @jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, (Ptr{Nothing}, jint), obj, jint(i) - 1) -Base.length(obj::JavaObject) = Base.length(JProxy(obj)) -Base.length(pxy::JProxy{>:Array}) = arraylength(pxyptr(pxy)) - -function Base.getindex(pxy::JProxy{>:Array}, i::Integer) - asJulia(T, @jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, - (Ptr{Nothing}, jint), - pxyptr(pxy), jint(i) - 1)) -end - function methoddict(class) d = Dict() for method in listmethods(class) @@ -1089,15 +1159,6 @@ canConvertType(x, y) = interfacehas(x, y) interfacehas(x, y) = false -# ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG -function convert_arg(t::Type{<:Union{JObject, java_lang_Object}}, x::JPrimitive) - result = box(x) - result, result -end -convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) -convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) -convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) - # score specificity of a method function specificity(argTypes, mi::JMethodInfo) where T g = 0 @@ -1212,3 +1273,107 @@ _staticcall(::Type, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Noth Base.show(io::IO, pxy::JProxy) = print(io, pxystatic(pxy) ? "static class $(legalClassName(pxy))" : pxy.toString()) JavaObject(pxy::JProxy{T, C}) where {T, C} = JavaObject{C}(pxyptr(pxy)) + +# ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG +function convert_arg(t::Type{<:Union{JObject, java_lang_Object}}, x::JPrimitive) + result = box(x) + result, result +end +convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) +convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) +convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) +convert_arg(::Type{JProxy{Array{A,N}}}, array::JProxy{Array{<:A, N}}) where {A, N} = pxyptr(array) +function convert_arg(::Type{JProxy{Array{A, 1}}}, array::Array{A, 1}) where {A <: JPrimitive} + typ = juliatojava[A] + newarray = PtrBox(@jnicall(typ.newarray, Ptr{Nothing}, + (jint, Ptr{Nothing}), + length(array), C_NULL)) + @jnicall(typ.arrayregionsetter, Nothing, + (Ptr{Nothing}, Int32, Int32, Ptr{Nothing}), + newarray.ptr, 0, length(array), array) + newarray, newarray.ptr +end +function convert_arg(::Type{JProxy{Array{String, 1}}}, array::Array{String, 1}) + newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, + (jint, Ptr{Nothing}, Ptr{Nothing}), + length(array), stringClass, C_NULL)) + for i in 1:length(array) + @jnicall(jnifunc.SetObjectArrayElement, Nothing, + (Ptr{Nothing}, Int32, Ptr{Nothing}), + newarray.ptr, i - 1, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), array[i])) + end + newarray, newarray.ptr +end +function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{String, 1}) + newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, + (jint, Ptr{Nothing}, Ptr{Nothing}), + length(array), objectClass, C_NULL)) + for i in 1:length(array) + @jnicall(jnifunc.SetObjectArrayElement, Nothing, + (Ptr{Nothing}, Int32, Ptr{Nothing}), + newarray.ptr, i - 1, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), array[i])) + end + newarray, newarray.ptr +end +function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{<:Union{JPrimitive, JProxy}, 1}) + newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, + (jint, Ptr{Nothing}, Ptr{Nothing}), + length(array), objectClass, C_NULL)) + for i in 1:length(array) + @jnicall(jnifunc.SetObjectArrayElement, Nothing, + (Ptr{Nothing}, Int32, Ptr{Nothing}), + newarray.ptr, i - 1, box(array[i])) + end + newarray, newarray.ptr +end + +# Julia support +Base.unsafe_convert(::Type{Ptr{Nothing}}, pxy::JProxy) = pxyptr(pxy) + +# iteration and indexing support +Base.length(obj::JavaObject) = Base.length(JProxy(obj)) +Base.length(pxy::JProxy{<:Array}) = arraylength(pxyptr(pxy)) +Base.length(col::JProxy{T}) where T = interfacehas(java_util_Collection, T) ? col.size() : 0 + +Base.getindex(pxy::JProxy{<:Array}, i) = arrayget(pxy, i - 1) +function Base.getindex(pxy::JProxy{Array}, i::Integer) + JProxy(@jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, + (Ptr{Nothing}, jint), + pxyptr(pxy), jint(i) - 1)) +end + +Base.setindex!(pxy::JProxy{<:Array{T}}, v::T, i) where {T <: JPrimitive} = arrayset!(pxy, i - 1, v) +Base.setindex!(pxy::JProxy{<:Array{<:Union{interface, java_lang}}}, v::JPrimitive, i) = Base.setindex!(pxy, JProxy(box(v)), i) +Base.setindex!(pxy::JProxy{<:Array{T}}, v::JProxy{U}, i) where {T <: java_lang_Object, U <: T} = arrayset!(pxy, i - 1, v) +function Base.setindex!(pxy::JProxy{<:Array{T}}, v::JProxy{U}, i) where {T <: interface, U <: java_lang} + if interfacehas(T, U) + arrayset!(pxy, i - 1, v) + end +end + +Base.IteratorSize(::JProxy{<:Array}) = Base.HasLength() +Base.iterate(array::JProxy{<:Array}) = Base.iterate(array, (1, length(array))) +Base.iterate(array::JProxy{<:Array}, (next, len)) = next > len ? nothing : (array[next], (next + 1, len)) + +Base.IteratorSize(::JProxy{T}) where T = interfacehas(java_util_Collection, T) ? Base.HasLength() : Base.SizeUnknown() +function Base.iterate(col::JProxy{T}) where T + if interfacehas(java_lang_Iterable, T) + i = col.iterator() + nextGetter(col, i)() + else + nothing + end +end +Base.iterate(col::JProxy, state) = state() +function nextGetter(col::JProxy, iter) + let pending = true, value # memoize value + function () + if pending + pending = false + value = iter.hasNext() ? (iter.next(), nextGetter(col, iter)) : nothing + else + value + end + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 583fced..213bede 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -250,6 +250,8 @@ end b = JProxy(JAL(())) b.addAll(a) @test a.toString() == b.toString() + a.add("two") + @test collect(a) == [1, "two"] end @testset "proxy_test_class" begin @@ -273,6 +275,18 @@ end @test(JProxy(@jimport(java.lang.Double)).MAX_VALUE == 1.7976931348623157e308) @test("class java.lang.Object" == JProxy(JavaCall.metaclass("java.lang.Class")).forName("java.lang.Object").toString()) @test("class java.io.PrintStream" == JProxy(JavaObject{Symbol("java.lang.System")}).out.getClass().toString()) + @test("static class java.util.Arrays" == string(JProxy(@jimport(java.util.Arrays)))) +end + +@testset "proxy_array" begin + s,ptr=JavaCall.convert_arg(JProxy{Array{Int,1}}, [1,2]) # convert Julia array to an unwrapped java array + p=JProxy(ptr) # wrap it + @test(length(p) == 2) + @test(p[1] == 1) + @test(p[2] == 2) + @test(collect(p) == [1, 2]) + p[1] = 3 + @test(p[1] == 3) end # At the end, unload the JVM before exiting From a88d2774baddde11e08fdd8edaa16a72004d06a7 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 25 Nov 2018 16:00:20 +0200 Subject: [PATCH 30/43] finishing removal of collections.jl --- src/JavaCall.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/JavaCall.jl b/src/JavaCall.jl index fa18278..0af8bc7 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -28,7 +28,6 @@ include("core.jl") include("convert.jl") include("reflect.jl") include("proxy.jl") -include("collections.jl") function __init__() findjvm() From 72e4a787e3119bc3703eb1b5db55bc978ffd84e4 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 25 Nov 2018 19:10:38 +0200 Subject: [PATCH 31/43] static method call fix, conversion fixes --- src/proxy.jl | 79 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 0bec451..fd12dc0 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -53,6 +53,7 @@ function _defjtype(a, b) end end +# types macro defjtype(expr) :(_defjtype($(string(expr.args[1])), $(expr.args[2]))) end @@ -60,6 +61,7 @@ end const types = Dict() @defjtype java_lang_Object <: java_lang +@defjtype java_lang_String <: java_lang_Object @defjtype java_util_AbstractCollection <: java_lang_Object @defjtype java_lang_Number <: java_lang_Object @defjtype java_lang_Double <: java_lang_Number @@ -74,7 +76,6 @@ const types = Dict() @defjtype java_util_List <: interface @defjtype java_util_Collection <: interface -# types const modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} const JField = JavaObject{Symbol("java.lang.reflect.Field")} const JPrimitive = Union{Bool, Char, UInt8, Int8, UInt16, Int16, Int32, Int64, Float32, Float64} @@ -528,6 +529,22 @@ function JClassInfo(class::JClass) classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) end +function ensureclasstypes(class::JClass) + n = Symbol(legalClassName(class)) + tn = typeNameFor(string(n)) + if isa(tn, DataType) + tn <: Array && isa(tn.parameters[1], Symbol) && ensureclasstypes(classforname(string(tn.parameters[1]))) + tn + else + sc = superclass(class) + !isNull(sc) && ensureclasstypes(sc) + for int in allinterfaces(class) + ensureclasstypes(int) + end + _defjtype(tn, isNull(sc) ? interface : typeNameFor(sc)) + end +end + typeFor(::Type{JavaObject{T}}) where T = typeFor(T) typeFor(str::String) = typeFor(Symbol(str)) function typeFor(sym::Symbol) @@ -541,17 +558,6 @@ function typeFor(sym::Symbol) end end -function makeTypeFor(class::JClass) - cln = Symbol(getname(class)) - t = typeFor(cln) - if t == java_lang - sc = superclass(class) - sct = isNull(sc) ? java_lang : makeTypeFor(sc) - _defjtype(typeNameFor(cln), nameof(sct)) - end - t -end - asJulia(t, obj) = obj asJulia(::Type{Bool}, obj) = obj != 0 asJulia(t, obj::JBoxed) = unbox(obj) @@ -615,11 +621,10 @@ function argtypefor(class::JClass) if tinfo.primitive tinfo.convertType elseif cln == "java.lang.String" - String + JProxy{java_lang_String, Symbol("java.lang.String")} else - sn = Symbol(cln) - makeTypeFor(class) - typeFor(sn) + t = ensureclasstypes(class) + t <: Union{Array, java_lang} ? JProxy{t} : t end end @@ -736,13 +741,11 @@ function _defbox(primclass, boxtype, juliatype, javatype, boxclassname) call(ptr, $boxVar.unboxer, jboolean, ()) != 0 end function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) - #_jcall(obj, $boxVar.unboxer, C_NULL, jboolean, ()) != 0 @message(obj, jboolean, $boxVar.unboxer) != 0 end end else quote - #convert(::Type{JavaObject{T}}, obj::$juliatype) where T = JavaObject(box(obj)) $(if juliatype == :jchar :(convert(::Type{JavaObject{T}}, obj::Char) where T = JavaObject(box(obj))) else @@ -755,7 +758,6 @@ function _defbox(primclass, boxtype, juliatype, javatype, boxclassname) call(ptr, $boxVar.unboxer, $javatype, ()) end function unbox(obj::JavaObject{Symbol($(classnamefor(boxtype)))}) - #_jcall(obj, $boxVar.unboxer, C_NULL, $juliatype, ()) call(obj.ptr, $boxVar.unboxer, $juliatype, ()) end end @@ -836,6 +838,11 @@ end isinterface(class::Ptr{Nothing}) = @message(class, jboolean, methodId_class_isInterface) != 0 +""" + getinterfaces + +return JClass objects for the declared and inherited interfaces of a class +""" function getinterfaces(class::JClass) array = @message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces) [JClass(arrayat(array, i)) for i in 1:arraylength(array)] @@ -1110,7 +1117,7 @@ function (pxy::JMethodProxy{N})(args...) where N meth = reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) @Verbose("SEND MESSAGE ", N, " RETURNING ", meth.typeInfo.juliaType, " ARG TYPES ", meth.argTypes) if meth.static - staticcall(meth.id, meth.typeInfo.convertType, meth.dynArgTypes, args...) + staticcall(meth.owner, meth.id, meth.typeInfo.convertType, meth.argTypes, args...) else call(pxy.obj, meth.id, meth.typeInfo.convertType, meth.argTypes, args...) end @@ -1132,13 +1139,14 @@ function filterStatic(pxy::JMethodProxy, targets) Set(target for target in targets if target.static == static) end -#fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.dynArgTypes, args)) fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(args) && all(canConvert.(method.argTypes, args)) canConvert(::Type{T}, ::T) where T = true canConvert(t::Type, ::T) where T = canConvertType(t, T) canConvert(::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertType(T1, T2) canConvert(::Type{T1}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) +canConvert(::Type{JProxy{T1}}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) +canConvert(::Type{JProxy{T1}}, ::T2) where {T1, T2} = canConvertType(T1, T2) canConvertType(::Type{T}, ::Type{T}) where T = true canConvertType(::Type{T1}, t::Type{T2}) where {T1 <: java_lang_Object, T2 <: java_lang_Object} = T2 <: T1 @@ -1151,6 +1159,7 @@ canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Short")}, java_lang_S canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Byte")}, java_lang_Byte}}, ::Type{Int8}) = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Character")}, java_lang_Character}}, ::Type{<:Union{Int8, Char}}) = true canConvertType(::Type{<:AbstractString}, ::Type{<:AbstractString}) = true +canConvertType(::Type{<:JProxy{<:Union{java_lang_String, java_lang_Object}}}, ::Type{<:AbstractString}) = true canConvertType(::Type{JString}, ::Type{<:AbstractString}) = true canConvertType(::Type{<: Real}, ::Type{<:Real}) = true canConvertType(::Type{jboolean}, ::Type{Bool}) = true @@ -1177,10 +1186,13 @@ const specificityinherit = 10000 # score relative generality of corresponding arguments in two methods # higher means c1 is more general than c2 (i.e. c2 is the more specific one) -specificity(::Type{JProxy{T}}, t1) where T = specificity(T, t1) -specificity(argType::Type{<:Union{JBoxTypes,JPrimitive}}, t1::Type{<:JPrimitive}) = specificitybest -specificity(argType::Type{<:JBoxTypes}, t1::Type{<:JBoxTypes}) = specificitybest -specificity(argType::Type{<:JPrimitive}, t1::Type{<:JBoxTypes}) = specificitybox +specificity(::Type{JProxy{T1}}, ::Type{JProxy{T2}}) where {T1, T2} = specificity(T1, T2) +specificity(::Type{JProxy{T1}}, T2) where {T1} = specificity(T1, T2) +specificity(::Type{<:Union{JBoxTypes,JPrimitive}}, t1::Type{<:JPrimitive}) = specificitybest +specificity(::Type{<:JBoxTypes}, ::Type{<:JBoxTypes}) = specificitybest +specificity(::Type{<:JPrimitive}, ::Type{<:JBoxTypes}) = specificitybox +specificity(::Type{java_lang_Object}, ::Type{<:AbstractString}) = specificityinherit +specificity(::Type{java_lang_String}, ::Type{<:AbstractString}) = specificitybest function specificity(argType::Type, t1::Type) if argType == t1 || interfacehas(t1, argType) specificitybest @@ -1237,9 +1249,9 @@ _call(::Type, obj, mId, args) = ccall(jnifunc.CallObjectMethodA, Ptr{Nothing}, @defcall(jdouble, Double, jdouble) @defcall(Nothing, Void, Nothing) -function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T +function staticcall(class, mId, rettype::Type{T}, argtypes::Tuple, args...) where T savedargs, convertedargs = convert_args(argtypes, args...) - result = _staticcall(T, mId, convertedargs) + result = _staticcall(T, class.ptr, mId, convertedargs) result == C_NULL && geterror() if rettype <: JavaObject && result != C_NULL registerreturn(result) @@ -1252,14 +1264,14 @@ function staticcall(mId, rettype::Type{T}, argtypes::Tuple, args...) where T end macro defstaticcall(t, f, ft) - :(_staticcall(::Type{$t}, mId, args) = ccall(jnifunc.$(Symbol("CallStatic" * string(f) * "MethodA")), $ft, - (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}), - penv, mId, args)) + :(_staticcall(::Type{$t}, class, mId, args) = ccall(jnifunc.$(Symbol("CallStatic" * string(f) * "MethodA")), $ft, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, class, mId, args)) end -_staticcall(::Type, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, +_staticcall(::Type, class, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, Ptr{Nothing}, (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), - penv, C_NULL, mId, args) + penv, class, mId, args) @defstaticcall(Bool, Boolean, jboolean) @defstaticcall(jbyte, Byte, jbyte) @defstaticcall(jchar, Char, jchar) @@ -1326,6 +1338,9 @@ function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{<: end newarray, newarray.ptr end +function convert_arg(::Type{<:JProxy{<:Union{java_lang_Object, java_lang_String}}}, str::AbstractString) + str, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), string(str)) +end # Julia support Base.unsafe_convert(::Type{Ptr{Nothing}}, pxy::JProxy) = pxyptr(pxy) From 865a088926553ecf4e5710de0801d2b6db4482a0 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 25 Nov 2018 20:17:59 +0200 Subject: [PATCH 32/43] array conversion, collection/map indexing --- src/proxy.jl | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index fd12dc0..850eaa3 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -75,6 +75,7 @@ const types = Dict() @defjtype java_lang_Iterable <: interface @defjtype java_util_List <: interface @defjtype java_util_Collection <: interface +@defjtype java_util_Map <: interface const modifiers = JavaObject{Symbol("java.lang.reflect.Modifier")} const JField = JavaObject{Symbol("java.lang.reflect.Field")} @@ -360,8 +361,6 @@ isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing classtypename(ptr::Ptr{Nothing}) = typeNameFor(getclassname(getclass(ptr))) classtypename(obj::JavaObject{T}) where T = string(T) -instance(obj::Union{JProxy, Nothing}) = obj -instance(obj) = JProxy(box(obj)) # To access static members, use types or metaclasses # like this: `@class(java.lang.Byte).TYPE` macro class(name::Expr) @@ -1148,6 +1147,7 @@ canConvert(::Type{T1}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) canConvert(::Type{JProxy{T1}}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) canConvert(::Type{JProxy{T1}}, ::T2) where {T1, T2} = canConvertType(T1, T2) +canConvertType(::Type{java_lang_Object}, ::Type{<:Array}) = true canConvertType(::Type{T}, ::Type{T}) where T = true canConvertType(::Type{T1}, t::Type{T2}) where {T1 <: java_lang_Object, T2 <: java_lang_Object} = T2 <: T1 canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Object")}, java_lang_Object}}, ::Type{<:Union{AbstractString, JPrimitive}}) = true @@ -1287,15 +1287,15 @@ Base.show(io::IO, pxy::JProxy) = print(io, pxystatic(pxy) ? "static class $(lega JavaObject(pxy::JProxy{T, C}) where {T, C} = JavaObject{C}(pxyptr(pxy)) # ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG -function convert_arg(t::Type{<:Union{JObject, java_lang_Object}}, x::JPrimitive) +function convert_arg(t::Union{Type{<:JavaObject}, Type{<:JProxy{<:java_lang_Object}}}, x::JPrimitive) result = box(x) result, result end convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) -convert_arg(::Type{JProxy{Array{A,N}}}, array::JProxy{Array{<:A, N}}) where {A, N} = pxyptr(array) -function convert_arg(::Type{JProxy{Array{A, 1}}}, array::Array{A, 1}) where {A <: JPrimitive} +convert_arg(::Type{<:JProxy{<:Union{Array{A,N}, java_lang_Object}}}, array::JProxy{Array{<:A, N}}) where {A, N} = pxyptr(array) +function convert_arg(::Type{<:JProxy{<:Union{Array{A, 1}, java_lang_Object}}}, array::Array{A, 1}) where {A <: JPrimitive} typ = juliatojava[A] newarray = PtrBox(@jnicall(typ.newarray, Ptr{Nothing}, (jint, Ptr{Nothing}), @@ -1305,7 +1305,7 @@ function convert_arg(::Type{JProxy{Array{A, 1}}}, array::Array{A, 1}) where {A < newarray.ptr, 0, length(array), array) newarray, newarray.ptr end -function convert_arg(::Type{JProxy{Array{String, 1}}}, array::Array{String, 1}) +function convert_arg(::Type{JProxy{<:Union{Array{String, 1}, java_lang_Object}}}, array::Array{String, 1}) newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, (jint, Ptr{Nothing}, Ptr{Nothing}), length(array), stringClass, C_NULL)) @@ -1316,17 +1316,6 @@ function convert_arg(::Type{JProxy{Array{String, 1}}}, array::Array{String, 1}) end newarray, newarray.ptr end -function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{String, 1}) - newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, - (jint, Ptr{Nothing}, Ptr{Nothing}), - length(array), objectClass, C_NULL)) - for i in 1:length(array) - @jnicall(jnifunc.SetObjectArrayElement, Nothing, - (Ptr{Nothing}, Int32, Ptr{Nothing}), - newarray.ptr, i - 1, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), array[i])) - end - newarray, newarray.ptr -end function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{<:Union{JPrimitive, JProxy}, 1}) newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, (jint, Ptr{Nothing}, Ptr{Nothing}), @@ -1356,6 +1345,13 @@ function Base.getindex(pxy::JProxy{Array}, i::Integer) (Ptr{Nothing}, jint), pxyptr(pxy), jint(i) - 1)) end +function Base.getindex(pxy::JProxy{T}, i) where T + if interfacehas(java_util_List, T) || interfacehas(java_util_Map, T) + pxy.get(i - 1) + else + throw(MethodError(getindex, (pxy, i))) + end +end Base.setindex!(pxy::JProxy{<:Array{T}}, v::T, i) where {T <: JPrimitive} = arrayset!(pxy, i - 1, v) Base.setindex!(pxy::JProxy{<:Array{<:Union{interface, java_lang}}}, v::JPrimitive, i) = Base.setindex!(pxy, JProxy(box(v)), i) @@ -1365,6 +1361,15 @@ function Base.setindex!(pxy::JProxy{<:Array{T}}, v::JProxy{U}, i) where {T <: in arrayset!(pxy, i - 1, v) end end +function Base.setindex!(pxy::JProxy{T}, value, i) where T + if interfacehas(java_util_List, T) + pxy.set(i - 1, value) + elseif interfacehas(java_util_Map, T) + pxy.put(i, value) + else + throw(MethodError(setindex!, (pxy, value, i))) + end +end Base.IteratorSize(::JProxy{<:Array}) = Base.HasLength() Base.iterate(array::JProxy{<:Array}) = Base.iterate(array, (1, length(array))) From 37d375e24f9b2d1655e238c488535d16c82b75eb Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Sun, 25 Nov 2018 20:46:12 +0200 Subject: [PATCH 33/43] fix convert_arg for objects supporting interfaces and typeNameFor --- src/proxy.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/proxy.jl b/src/proxy.jl index 850eaa3..e5a6ad3 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -483,7 +483,11 @@ function typeNameFor(className::AbstractString) t.juliaType else sn = Symbol(className) - get(types, sn, sn) + get(types, sn) do + className = replace(className, "_" => "___") + className = replace(className, "\$" => "__s") + Symbol(replace(className, "." => "_")) + end end end end @@ -1330,6 +1334,13 @@ end function convert_arg(::Type{<:JProxy{<:Union{java_lang_Object, java_lang_String}}}, str::AbstractString) str, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), string(str)) end +function convert_arg(int::Type{<:JProxy{I}}, pxy::JProxy{T}) where {I <: interface, T} + if interfacehas(I, T) + pxy, pxyptr(pxy) + else + convert(int, pxy) + end +end # Julia support Base.unsafe_convert(::Type{Ptr{Nothing}}, pxy::JProxy) = pxyptr(pxy) From 67f7106343126611c6ef70210eb4be19033b0042 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 26 Nov 2018 11:45:17 +0200 Subject: [PATCH 34/43] constructors, moved JProxy's static into the type, REPL completion @class(java.util.ArrayList) creates a static proxy you can use as a constructor --- src/JavaCall.jl | 2 +- src/proxy.jl | 248 ++++++++++++++++++++++++++++++----------------- test/runtests.jl | 2 +- 3 files changed, 162 insertions(+), 90 deletions(-) diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 0af8bc7..4a032ad 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -4,7 +4,7 @@ export JavaObject, JavaMetaClass, JObject, JClass, JMethod, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, - narrow, JProxy, @class, instance + narrow, JProxy, @class, interfacehas # using Compat, Compat.Dates diff --git a/src/proxy.jl b/src/proxy.jl index e5a6ad3..c997941 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -34,7 +34,7 @@ classnamefor(t::Type{<:java_lang}) = classnamefor(nameof(t)) classnamefor(s::Symbol) = classnamefor(string(s)) function classnamefor(s::AbstractString) s = replace(s, "___" => "_") - s = replace(s, "_s_" => "\$") + s = replace(s, "__S" => "\$") replace(s, "_" => ".") end @@ -155,19 +155,10 @@ struct JClassInfo class::JClass fields::Dict{Symbol, Union{JFieldInfo, JReadonlyField}} methods::Dict{Symbol, Set{JMethodInfo}} + constructors::Set{JMethodInfo} classtype::Type end -struct JMethodProxy{N, T} - pxy # hold onto this so long-held method proxies don't have dead ptr references - obj::Ptr{Nothing} - methods::Set - static::Bool - function JMethodProxy(N::Symbol, T::Type, pxy, methods) - new{N, T}(pxy, pxyptr(pxy), methods, pxystatic(pxy)) - end -end - struct Boxing info::JavaTypeInfo boxType::Type @@ -194,22 +185,34 @@ mutable struct PtrBox end """ - JProxy(s::AbstractString) - JProxy(::JavaMetaClass) - JProxy(::Type{JavaObject}; static=false) - JProxy(obj::JavaObject; static=false) + JProxy{classType, static} -Create a proxy for a Java object that you can use like a Java object. Field and method syntax is like in Java. Primitive types and strings are converted to Julia objects on field accesses and method returns and converted back to Java types when sent as arguments to Java methods. +A proxy for a Java object that you can use like a Java object. Field and method syntax is like in Java. Primitive types and strings are converted to Julia objects on field accesses and method returns and converted back to Java types when sent as arguments to Java methods. *NOTE: Because of this, if you need to call Java methods on a string that you got from Java, you'll have to use `JProxy(str)` to convert the Julia string to a proxied Java string* +The classType parameter will either hold a java_lang subtype, which keeps a hierarchy parallel to Java's class tree, or it will hold an array of a java_lang subtype. java_lang subtypes are named like the Java classes with _ substituted for ., ___ substituted for _, and __S substituted for \$. + +Interfaces are direct subtypes of the interface type (which is a subtype of java_lang). To test for interface extension, use interfacehas(interface, candidateType) or extends(candidateType, interface). + +Java arrays and collections support iteration and indexing so you can use them as-is or you can use collect() to convert them to Juila arrays (or use generators to convert them to other collection types). + To invoke static methods, set static to true. To get a JProxy's Java object, use `JavaObject(proxy)` #Example ```jldoctest -julia> a=JProxy(@jimport(java.util.ArrayList)(())) +# A proxy on a Java string +julia> JProxy("hello") +hello + +# a static proxy on Integer +julia> @class(java.lang.Integer) +static class java.lang.Integer + +# a proxy on an ArrayList +julia> a = @class(java.util.ArrayList)() [] julia> a.size() @@ -239,23 +242,33 @@ false julia> b == b true -julia> JProxy(@jimport(java.lang.System)).getName() -"java.lang.System" - -julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") +julia> @class(java.lang.System).out.println("hello") hello + +# a proxy on the ArrayList class (not a static proxy) +julia> a.getClass() +class java.util.ArrayList ``` """ # mutable because it can have a finalizer -mutable struct JProxy{T, C} +mutable struct JProxy{T, STATIC} ptr::Ptr{Nothing} info::JClassInfo - static::Bool - function JProxy{T, C}(obj::JavaObject, info, static) where {T, C} - finalizer(finalizeproxy, new{T, C}(newglobalref(obj.ptr), info, static)) + function JProxy{T, STATIC}(obj::JavaObject, info) where {T, STATIC} + finalizer(finalizeproxy, new{T, STATIC}(newglobalref(obj.ptr), info)) end - function JProxy{T, C}(obj::PtrBox, info, static) where {T, C} - finalizer(finalizeproxy, new{T, C}(newglobalref(obj.ptr), info, static)) + function JProxy{T, STATIC}(obj::PtrBox, info) where {T, STATIC} + finalizer(finalizeproxy, new{T, STATIC}(newglobalref(obj.ptr), info)) + end +end + +struct JMethodProxy{N, T} + pxy # hold onto this so long-held method proxies don't have dead ptr references + obj::Ptr{Nothing} + methods::Set + static::Bool + function JMethodProxy(N::Symbol, T::Type, pxy::JProxy{A, STATIC}, methods) where {A, STATIC} + new{N, T}(pxy, pxyptr(pxy), methods, STATIC) end end @@ -361,8 +374,20 @@ isVoid(meth::JMethodInfo) = meth.typeInfo.convertType == Nothing classtypename(ptr::Ptr{Nothing}) = typeNameFor(getclassname(getclass(ptr))) classtypename(obj::JavaObject{T}) where T = string(T) -# To access static members, use types or metaclasses -# like this: `@class(java.lang.Byte).TYPE` +""" + @class(CLASSNAME) -> JProxy + +Create a static proxy that allows you to invoke constructors and access static members. + +# Examples +```jldoctest +julia> @class(java.lang.Integer).MAX_VALUE +2147483647 + +julia> a = @class(java.util.ArrayList)() +[] +``` +""" macro class(name::Expr) :(JProxy(Symbol($(replace(sprint(Base.show_unquoted, name), r"[ ()]"=>""))))) end @@ -381,13 +406,13 @@ function staticproxy(classname) c = Symbol(legalClassName(classname)) obj = classforname(string(c)) info = infoFor(obj) - JProxy{typeFor(c), c}(obj, info, true) + JProxy{typeFor(c), true}(obj, info) end # Proxies on classes are on the class objects, they don't get you static members -# To access static members, use types or metaclasses -# like this: `JProxy(JavaObject{Symbol("java.lang.Byte")}).TYPE` +# To access static members, use the @class(NAME) macro +# like this: `@class(java.lang.Byte).TYPE` JProxy(s::AbstractString) = JProxy(JString(s)) -JProxy{T, C}(ptr::PtrBox) where {T, C} = JProxy{T, C}(ptr, infoFor(JClass(getclass(ptr))), false) +JProxy{T}(ptr::PtrBox) where {T} = JProxy{T, false}(ptr, infoFor(JClass(getclass(ptr)))) JProxy(obj::JavaObject) = JProxy(PtrBox(obj)) JProxy(ptr::Ptr{Nothing}) = JProxy(PtrBox(ptr)) function JProxy(obj::PtrBox) @@ -402,10 +427,11 @@ function JProxy(obj::PtrBox) @verbose("JPROXY INFO FOR ", n, ", ", getname(cls)) info = infoFor(cls) aType, dim = arrayinfo(n) + typ = info.classtype if dim != 0 - typeFor(Symbol(aType)) + typ = typeFor(Symbol(aType)) end - JProxy{info.classtype, c}(obj, info, false) + JProxy{info.classtype, false}(obj, info) end function JavaTypeInfo(class, signature, juliaType, convertType, accessorName, boxType, getter, staticGetter, setter, staticSetter, newarray, arrayregionsetter) @@ -435,7 +461,7 @@ end gettypeinfo(class::Symbol) = gettypeinfo(string(class)) gettypeinfo(class::AbstractString) = get(typeInfo, class, genericFieldInfo) -legalClassName(pxy::JProxy) = legalClassName(getclassname(pxystatic(pxy) ? pxyptr(pxy) : getclass(pxyptr(pxy)))) +legalClassName(pxy::JProxy{T,S}) where {T, S} = legalClassName(getclassname(S ? pxyptr(pxy) : getclass(pxyptr(pxy)))) legalClassName(cls::JavaObject) = legalClassName(getname(cls)) legalClassName(cls::Symbol) = legalClassName(string(cls)) function legalClassName(name::AbstractString) @@ -485,7 +511,7 @@ function typeNameFor(className::AbstractString) sn = Symbol(className) get(types, sn) do className = replace(className, "_" => "___") - className = replace(className, "\$" => "__s") + className = replace(className, "\$" => "__S") Symbol(replace(className, "." => "_")) end end @@ -494,7 +520,7 @@ function typeNameFor(className::AbstractString) end macro jp(s) - :(JProxy{$s, Symbol($(classnamefor(s)))}) + :(JProxy{$s, false}) end isArray(class::JClass) = jcall(class, "isArray", jboolean, ()) != 0 @@ -529,7 +555,7 @@ function JClassInfo(class::JClass) end end definterfacecvt(jtype, interfaces) - classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), jtype) + classes[n] = JClassInfo(parentinfo, class, fielddict(class), methoddict(class), getConstructors(class), jtype) end function ensureclasstypes(class::JClass) @@ -553,9 +579,9 @@ typeFor(str::String) = typeFor(Symbol(str)) function typeFor(sym::Symbol) aType, dims = arrayinfo(string(sym)) if dims == 1 && haskey(typeInfo, aType) - JProxy{Array{get(typeInfo, aType, java_lang_Object).convertType, 1}} + Array{get(typeInfo, aType, java_lang_Object).convertType, 1} elseif dims != 0 - JProxy{Array{get(types, Symbol(aType), java_lang_Object), dims}} + Array{get(types, Symbol(aType), java_lang_Object), dims} else get(types, sym, java_lang_Object) end @@ -599,7 +625,6 @@ end pxyptr(p::JProxy) = getfield(p, :ptr) pxyinfo(p::JProxy) = getfield(p, :info) -pxystatic(p::JProxy) = getfield(p, :static) ==(j1::JProxy, j2::JProxy) = isSame(pxyptr(j1), pxyptr(j2)) @@ -616,7 +641,10 @@ function getConstructor(class::Type, argTypes...) jcall(classfortype(class), "getConstructor", JConstructor, (Vector{JClass},), collect(argTypes)) end -getConstructors(class::Type) = jcall(classfortype(class), "getConstructors", Array{JConstructor}, ()) +getConstructors(class::Type) = getConstructors(classfortype(class)) +function getConstructors(class::JClass) + Set([methodInfo(c) for c in jcall(class, "getConstructors", Array{JConstructor, 1}, ())]) +end function argtypefor(class::JClass) cln = getclassname(class.ptr) @@ -624,10 +652,10 @@ function argtypefor(class::JClass) if tinfo.primitive tinfo.convertType elseif cln == "java.lang.String" - JProxy{java_lang_String, Symbol("java.lang.String")} + JProxy{java_lang_String, false} else t = ensureclasstypes(class) - t <: Union{Array, java_lang} ? JProxy{t} : t + t <: Union{Array, java_lang} ? JProxy{t, false} : t end end @@ -681,7 +709,7 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) type_ctyp = getfield(JavaCall, ctyp) type_jtyp = getfield(Core, jtyp) quote - function arrayget(pxy::JProxy{<:Array{$ctyp}}, index) + function arrayget(pxy::JProxy{<:Array{$ctyp}, false}, index) result = $type_jtyp[$(type_jtyp(0))] @jnicall($(j("Get" * Typ * "ArrayRegion")), Nothing, (Ptr{Nothing}, Csize_t, Csize_t, Ptr{$(jtyp)}), @@ -689,7 +717,7 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) result == C_NULL && geterror() $(type_jtyp == Bool ? :(result[1] != 0) : :(result[1])) end - function arrayset!(pxy::JProxy{<:Array{$ctyp}}, index, value::$ctyp) + function arrayset!(pxy::JProxy{<:Array{$ctyp}, false}, index, value::$ctyp) valuebuf = $type_jtyp[$type_jtyp(value)] @jnicall($(j("Set" * Typ * "ArrayRegion")), Nothing, (Ptr{Nothing}, Csize_t, Csize_t, Ptr{$(jtyp)}), @@ -706,7 +734,7 @@ function _typeInf(jclass, ctyp, sig, jtyp, Typ, object, accessor, boxType) end end -function arrayget(pxy::JProxy{<:Array}, index) +function arrayget(pxy::JProxy{<:Array, false}, index) result = @jnicall(jnifunc.GetObjectArrayElement, Ptr{Nothing}, (Ptr{Nothing}, Csize_t), pxyptr(pxy), index) @@ -719,7 +747,7 @@ function arrayget(pxy::JProxy{<:Array}, index) end result end -function arrayset!(pxy::JProxy{<:Array}, index, value::JProxy) +function arrayset!(pxy::JProxy{<:Array, false}, index, value::JProxy) @jnicall(jnifunc.SetObjectArrayElement, Nothing, (Ptr{Nothing}, Csize_t, Ptr{Nothing}), pxyptr(pxy), index, pxyptr(value)) @@ -938,6 +966,7 @@ function juliaTypeFor(name::AbstractString) info != nothing ? info.juliaType : JavaObject{Symbol(name)} end +infoFor(typ::Type{T}) where {T <: java_lang} = infoFor(classforname(classnamefor(nameof(T)))) function infoFor(class::JClass) result = _infoFor(class) deletelocals() @@ -1008,16 +1037,16 @@ isNull(ptr::Ptr{Nothing}) = Int64(ptr) == 0 superclass(obj::JavaObject) = jcall(obj, "getSuperclass", @jimport(java.lang.Class), ()) -function getField(p::JProxy, field::JFieldInfo) +function getField(p::JProxy{T,S}, field::JFieldInfo) where {T,S} asJulia(field.typeInfo.juliaType, @jnicallregistered(static ? field.typeInfo.staticGetter : field.typeInfo.getter, Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}), - pxystatic(p) ? getclass(obj) : pxyptr(p), field.id)) + S ? getclass(obj) : pxyptr(p), field.id)) end -function Base.getproperty(p::JProxy{T}, name::Symbol) where T +function Base.getproperty(p::JProxy{T, STATIC}, name::Symbol) where {T, STATIC} info = pxyinfo(p) if haskey(info.methods, name) - m = pxystatic(p) ? filter(m->m.static, info.methods[name]) : info.methods[name] + m = STATIC ? filter(m->m.static, info.methods[name]) : info.methods[name] isempty(m) && throw(KeyError("key: $name not found")) JMethodProxy(name, T, p, m) else @@ -1031,7 +1060,6 @@ setter(field::JFieldInfo) = field.static ? field.typeInfo.staticSetter : field.t getproxyfield(p::JProxy, field::JReadonlyField) = field.get(pxyptr(p)) function getproxyfield(p::JProxy, field::JFieldInfo) - static = pxystatic(p) result = _getproxyfield(field.static ? C_NULL : pxyptr(p), field) geterror() @verbose("FIELD CONVERT RESULT ", repr(result), " TO ", field.typeInfo.convertType) @@ -1068,8 +1096,8 @@ function setproxyfield(p::JProxy, field::JFieldInfo{String}, value::AbstractStri primsetproxyfield(p, field, str.ptr) end -function primsetproxyfield(p::JProxy, field::JFieldInfo, value) - _setproxyfield(pxystatic(p) ? C_NULL : pxyptr(p), field, value) +function primsetproxyfield(p::JProxy{T,S}, field::JFieldInfo, value) where {T,S} + _setproxyfield(S ? C_NULL : pxyptr(p), field, value) geterror() end @@ -1102,7 +1130,6 @@ end function Base.setproperty!(p::JProxy, name::Symbol, value) info = pxyinfo(p) meths = get(info.methods, name, nothing) - static = pxystatic(p) result = if meths != nothing throw(JavaCallError("Attempt to set a method")) else @@ -1112,6 +1139,28 @@ function Base.setproperty!(p::JProxy, name::Symbol, value) isa(result, JavaObject) ? JProxy(result) : result end +function (pxy::JProxy{T, STATIC})(args...) where {T, STATIC} + if STATIC || T <: java_lang_Class + info = STATIC ? pxyinfo(pxy) : infoFor(JavaObject(pxy)) + targets = Set(m for m in info.constructors if fits(m, args)) + if !isempty(targets) + # Find the most specific constructor + argTypes = typeof(args).parameters + meth = reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) + savedargs, convertedargs = convert_args(meth.argTypes, args...) + result = ccall(jnifunc.NewObjectA, Ptr{Nothing}, + (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), + penv, info.class.ptr, meth.id, convertedargs) + result == C_NULL && geterror() + JProxy(result) + else + throw(ArgumentError("No constructor for argument types $(typeof.(args))")) + end + else + throw(MethodError(pxy, args)) + end +end + function (pxy::JMethodProxy{N})(args...) where N targets = Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)) if !isempty(targets) @@ -1130,7 +1179,9 @@ function (pxy::JMethodProxy{N})(args...) where N end function findmethod(pxy::JMethodProxy, args...) - targets = Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)) + findmethod(Set(m for m in filterStatic(pxy, pxy.methods) if fits(m, args)), args...) +end +function findmethod(targets::Set, args...) if !isempty(targets) argTypes = typeof(args).parameters reduce(((x, y)-> specificity(argTypes, x) > specificity(argTypes, y) ? x : y), targets) @@ -1147,9 +1198,9 @@ fits(method::JMethodInfo, args::Tuple) = length(method.dynArgTypes) == length(ar canConvert(::Type{T}, ::T) where T = true canConvert(t::Type, ::T) where T = canConvertType(t, T) canConvert(::Type{Array{T1,D}}, ::Array{T2,D}) where {T1, T2, D} = canConvertType(T1, T2) -canConvert(::Type{T1}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) -canConvert(::Type{JProxy{T1}}, ::JProxy{T2}) where {T1, T2} = canConvertType(T1, T2) -canConvert(::Type{JProxy{T1}}, ::T2) where {T1, T2} = canConvertType(T1, T2) +canConvert(::Type{T1}, ::JProxy{T2, false}) where {T1, T2} = canConvertType(T1, T2) +canConvert(::Type{JProxy{T1, false}}, ::JProxy{T2, false}) where {T1, T2} = canConvertType(T1, T2) +canConvert(::Type{JProxy{T1, false}}, ::T2) where {T1, T2} = canConvertType(T1, T2) canConvertType(::Type{java_lang_Object}, ::Type{<:Array}) = true canConvertType(::Type{T}, ::Type{T}) where T = true @@ -1163,15 +1214,28 @@ canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Short")}, java_lang_S canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Byte")}, java_lang_Byte}}, ::Type{Int8}) = true canConvertType(::Type{<:Union{JavaObject{Symbol("java.lang.Character")}, java_lang_Character}}, ::Type{<:Union{Int8, Char}}) = true canConvertType(::Type{<:AbstractString}, ::Type{<:AbstractString}) = true -canConvertType(::Type{<:JProxy{<:Union{java_lang_String, java_lang_Object}}}, ::Type{<:AbstractString}) = true +canConvertType(::Type{<:JProxy{<:Union{java_lang_String, java_lang_Object}, false}}, ::Type{<:AbstractString}) = true canConvertType(::Type{JString}, ::Type{<:AbstractString}) = true canConvertType(::Type{<: Real}, ::Type{<:Real}) = true canConvertType(::Type{jboolean}, ::Type{Bool}) = true canConvertType(::Type{jchar}, ::Type{Char}) = true canConvertType(x, y) = interfacehas(x, y) +""" + interfacehas(interface, classOrInterfaceType) + +Return whether interface has classOrInterfaceType as one of its subtypes, i.e. whether classOrInterfaceType extends interface. +""" interfacehas(x, y) = false +""" + extends(a, b) + +Return whether a extends or implements b +""" +extends(::Type{<:T}, ::Type{T}) where {T <: java_lang} = true +extends(t1, t2) = interfacehas(t2, t1) + # score specificity of a method function specificity(argTypes, mi::JMethodInfo) where T g = 0 @@ -1190,8 +1254,8 @@ const specificityinherit = 10000 # score relative generality of corresponding arguments in two methods # higher means c1 is more general than c2 (i.e. c2 is the more specific one) -specificity(::Type{JProxy{T1}}, ::Type{JProxy{T2}}) where {T1, T2} = specificity(T1, T2) -specificity(::Type{JProxy{T1}}, T2) where {T1} = specificity(T1, T2) +specificity(::Type{JProxy{T1, false}}, ::Type{JProxy{T2, false}}) where {T1, T2} = specificity(T1, T2) +specificity(::Type{JProxy{T1, false}}, T2) where {T1} = specificity(T1, T2) specificity(::Type{<:Union{JBoxTypes,JPrimitive}}, t1::Type{<:JPrimitive}) = specificitybest specificity(::Type{<:JBoxTypes}, ::Type{<:JBoxTypes}) = specificitybest specificity(::Type{<:JPrimitive}, ::Type{<:JBoxTypes}) = specificitybox @@ -1286,20 +1350,20 @@ _staticcall(::Type, class, mId, args) = ccall(jnifunc.CallStaticObjectMethodA, P @defstaticcall(jdouble, Double, jdouble) @defstaticcall(Nothing, Void, Nothing) -Base.show(io::IO, pxy::JProxy) = print(io, pxystatic(pxy) ? "static class $(legalClassName(pxy))" : pxy.toString()) +Base.show(io::IO, pxy::JProxy{T,S}) where {T,S} = print(io, S ? "static class $(legalClassName(pxy))" : pxy.toString()) -JavaObject(pxy::JProxy{T, C}) where {T, C} = JavaObject{C}(pxyptr(pxy)) +JavaObject(pxy::JProxy{T, false}) where {T} = JavaObject{Symbol(classnamefor(nameof(T)))}(pxyptr(pxy)) # ARG MUST BE CONVERTABLE IN ORDER TO USE CONVERT_ARG -function convert_arg(t::Union{Type{<:JavaObject}, Type{<:JProxy{<:java_lang_Object}}}, x::JPrimitive) +function convert_arg(t::Union{Type{<:JavaObject}, Type{<:JProxy{<:java_lang_Object, false}}}, x::JPrimitive) result = box(x) result, result end convert_arg(t::Type{JavaObject}, x::JProxy) = convert_arg(t, JavaObject(x)) convert_arg(::Type{T1}, x::JProxy) where {T1 <: java_lang} = x, pxyptr(x) convert_arg(::Type{T}, x) where {T <: java_lang} = convert_arg(JavaObject{Symbol(classnamefor(T))}, x) -convert_arg(::Type{<:JProxy{<:Union{Array{A,N}, java_lang_Object}}}, array::JProxy{Array{<:A, N}}) where {A, N} = pxyptr(array) -function convert_arg(::Type{<:JProxy{<:Union{Array{A, 1}, java_lang_Object}}}, array::Array{A, 1}) where {A <: JPrimitive} +convert_arg(::Type{<:JProxy{<:Union{Array{A,N}, java_lang_Object}, false}}, array::JProxy{Array{<:A, N}, false}) where {A, N} = pxyptr(array) +function convert_arg(::Type{<:JProxy{<:Union{Array{A, 1}, java_lang_Object}, false}}, array::Array{A, 1}) where {A <: JPrimitive} typ = juliatojava[A] newarray = PtrBox(@jnicall(typ.newarray, Ptr{Nothing}, (jint, Ptr{Nothing}), @@ -1309,7 +1373,7 @@ function convert_arg(::Type{<:JProxy{<:Union{Array{A, 1}, java_lang_Object}}}, a newarray.ptr, 0, length(array), array) newarray, newarray.ptr end -function convert_arg(::Type{JProxy{<:Union{Array{String, 1}, java_lang_Object}}}, array::Array{String, 1}) +function convert_arg(::Type{JProxy{<:Union{Array{String, 1}, java_lang_Object}, false}}, array::Array{String, 1}) newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, (jint, Ptr{Nothing}, Ptr{Nothing}), length(array), stringClass, C_NULL)) @@ -1320,7 +1384,7 @@ function convert_arg(::Type{JProxy{<:Union{Array{String, 1}, java_lang_Object}}} end newarray, newarray.ptr end -function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{<:Union{JPrimitive, JProxy}, 1}) +function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}, false}}, array::Array{<:Union{JPrimitive, JProxy}, 1}) newarray = PtrBox(@jnicall(jnifunc.NewObjectArray, Ptr{Nothing}, (jint, Ptr{Nothing}, Ptr{Nothing}), length(array), objectClass, C_NULL)) @@ -1331,10 +1395,10 @@ function convert_arg(::Type{JProxy{Array{java_lang_Object, 1}}}, array::Array{<: end newarray, newarray.ptr end -function convert_arg(::Type{<:JProxy{<:Union{java_lang_Object, java_lang_String}}}, str::AbstractString) +function convert_arg(::Type{<:JProxy{<:Union{java_lang_Object, java_lang_String}, false}}, str::AbstractString) str, @jnicall(jnifunc.NewStringUTF, Ptr{Nothing}, (Ptr{UInt8},), string(str)) end -function convert_arg(int::Type{<:JProxy{I}}, pxy::JProxy{T}) where {I <: interface, T} +function convert_arg(int::Type{<:JProxy{I, false}}, pxy::JProxy{T, false}) where {I <: interface, T} if interfacehas(I, T) pxy, pxyptr(pxy) else @@ -1347,16 +1411,16 @@ Base.unsafe_convert(::Type{Ptr{Nothing}}, pxy::JProxy) = pxyptr(pxy) # iteration and indexing support Base.length(obj::JavaObject) = Base.length(JProxy(obj)) -Base.length(pxy::JProxy{<:Array}) = arraylength(pxyptr(pxy)) -Base.length(col::JProxy{T}) where T = interfacehas(java_util_Collection, T) ? col.size() : 0 +Base.length(pxy::JProxy{<:Array, false}) = arraylength(pxyptr(pxy)) +Base.length(col::JProxy{T, false}) where T = interfacehas(java_util_Collection, T) ? col.size() : 0 -Base.getindex(pxy::JProxy{<:Array}, i) = arrayget(pxy, i - 1) -function Base.getindex(pxy::JProxy{Array}, i::Integer) +Base.getindex(pxy::JProxy{<:Array, false}, i) = arrayget(pxy, i - 1) +function Base.getindex(pxy::JProxy{Array, false}, i::Integer) JProxy(@jnicallregistered(jnifunc.GetObjectArrayElement, Ptr{Nothing}, (Ptr{Nothing}, jint), pxyptr(pxy), jint(i) - 1)) end -function Base.getindex(pxy::JProxy{T}, i) where T +function Base.getindex(pxy::JProxy{T, false}, i) where T if interfacehas(java_util_List, T) || interfacehas(java_util_Map, T) pxy.get(i - 1) else @@ -1364,15 +1428,15 @@ function Base.getindex(pxy::JProxy{T}, i) where T end end -Base.setindex!(pxy::JProxy{<:Array{T}}, v::T, i) where {T <: JPrimitive} = arrayset!(pxy, i - 1, v) -Base.setindex!(pxy::JProxy{<:Array{<:Union{interface, java_lang}}}, v::JPrimitive, i) = Base.setindex!(pxy, JProxy(box(v)), i) -Base.setindex!(pxy::JProxy{<:Array{T}}, v::JProxy{U}, i) where {T <: java_lang_Object, U <: T} = arrayset!(pxy, i - 1, v) -function Base.setindex!(pxy::JProxy{<:Array{T}}, v::JProxy{U}, i) where {T <: interface, U <: java_lang} +Base.setindex!(pxy::JProxy{<:Array{T}, false}, v::T, i) where {T <: JPrimitive} = arrayset!(pxy, i - 1, v) +Base.setindex!(pxy::JProxy{<:Array{<:Union{interface, java_lang}}, false}, v::JPrimitive, i) = Base.setindex!(pxy, JProxy(box(v)), i) +Base.setindex!(pxy::JProxy{<:Array{T}, false}, v::JProxy{U, false}, i) where {T <: java_lang_Object, U <: T} = arrayset!(pxy, i - 1, v) +function Base.setindex!(pxy::JProxy{<:Array{T}, false}, v::JProxy{U, false}, i) where {T <: interface, U <: java_lang} if interfacehas(T, U) arrayset!(pxy, i - 1, v) end end -function Base.setindex!(pxy::JProxy{T}, value, i) where T +function Base.setindex!(pxy::JProxy{T, false}, value, i) where T if interfacehas(java_util_List, T) pxy.set(i - 1, value) elseif interfacehas(java_util_Map, T) @@ -1382,12 +1446,12 @@ function Base.setindex!(pxy::JProxy{T}, value, i) where T end end -Base.IteratorSize(::JProxy{<:Array}) = Base.HasLength() -Base.iterate(array::JProxy{<:Array}) = Base.iterate(array, (1, length(array))) -Base.iterate(array::JProxy{<:Array}, (next, len)) = next > len ? nothing : (array[next], (next + 1, len)) +Base.IteratorSize(::JProxy{<:Array, false}) = Base.HasLength() +Base.iterate(array::JProxy{<:Array, false}) = Base.iterate(array, (1, length(array))) +Base.iterate(array::JProxy{<:Array, false}, (next, len)) = next > len ? nothing : (array[next], (next + 1, len)) -Base.IteratorSize(::JProxy{T}) where T = interfacehas(java_util_Collection, T) ? Base.HasLength() : Base.SizeUnknown() -function Base.iterate(col::JProxy{T}) where T +Base.IteratorSize(::JProxy{T, false}) where T = interfacehas(java_util_Collection, T) ? Base.HasLength() : Base.SizeUnknown() +function Base.iterate(col::JProxy{T, false}) where T if interfacehas(java_lang_Iterable, T) i = col.iterator() nextGetter(col, i)() @@ -1408,3 +1472,11 @@ function nextGetter(col::JProxy, iter) end end end + +#Base.fieldnames(::JProxy{<:Array, false}) = (:length,) +Base.fieldnames(::Type{JProxy{<:Array, false}}) = (:length,) +Base.fieldnames(::Type{JProxy{<:Array{T}, true}}) where {T, STATIC} = fieldnames(JProxy{java_lang_Object, true}) +function Base.fieldnames(::Type{JProxy{T, STATIC}}) where {T <: java_lang, STATIC} + i = infoFor(T) + Tuple(union(keys(filter((p)-> p[2].static == STATIC, i.fields)), keys(filter((p)-> any((m)-> m.static == STATIC, p[2]), i.methods)))) +end diff --git a/test/runtests.jl b/test/runtests.jl index 213bede..d3ac217 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -279,7 +279,7 @@ end end @testset "proxy_array" begin - s,ptr=JavaCall.convert_arg(JProxy{Array{Int,1}}, [1,2]) # convert Julia array to an unwrapped java array + s,ptr=JavaCall.convert_arg(JProxy{Array{Int,1}, false}, [1,2]) # convert Julia array to an unwrapped java array p=JProxy(ptr) # wrap it @test(length(p) == 2) @test(p[1] == 1) From 680cee6d426c831827b63e05b07ddc93e509ada6 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 3 Dec 2018 21:45:50 +0200 Subject: [PATCH 35/43] getinterfaces uses a PtrBox to hold temporarily onto classes --- src/jvm.jl | 1 + src/proxy.jl | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/jvm.jl b/src/jvm.jl index 01a049d..be3bb89 100644 --- a/src/jvm.jl +++ b/src/jvm.jl @@ -91,6 +91,7 @@ function findjvm() Libdl.dlopen(joinpath(bindir,m[1])) end global libjvm = Libdl.dlopen(libpath) + println("LOADED $libpath") @debug("Loaded $libpath") return end diff --git a/src/proxy.jl b/src/proxy.jl index c997941..73f14d7 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -846,6 +846,7 @@ function initProxy() global methodId_class_getInterfaces = getmethodid("java.lang.Class", "getInterfaces", "[Ljava.lang.Class;") global methodId_class_isInterface = getmethodid("java.lang.Class", "isInterface", "boolean") global methodId_system_gc = getmethodid(true, "java.lang.System", "gc", "void", String[]) + global methodId_method_getParameterTypes = getmethodid("java.lang.reflect.Method", "getParameterTypes", "[Ljava.lang.Class;") global initialized = true @defbox(boolean, java_lang_Boolean, Bool, jboolean, Boolean) @defbox(char, java_lang_Character, Char, jchar, Character) @@ -875,8 +876,15 @@ isinterface(class::Ptr{Nothing}) = @message(class, jboolean, methodId_class_isIn return JClass objects for the declared and inherited interfaces of a class """ function getinterfaces(class::JClass) - array = @message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces) - [JClass(arrayat(array, i)) for i in 1:arraylength(array)] + #array = @message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces) + #[JClass(arrayat(array, i)) for i in 1:arraylength(array)] + classesFor(@message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces)) +end + +function classesFor(array) + # ptrbox array first so they don't get collected + ap = PtrBox(array) + [JClass(arrayat(ap.ptr, i)) for i in 1:arraylength(ap.ptr)] end jarray(array::Ptr{Nothing}) = [arrayat(array, i) for i in 1:arraylength(array)] From 4ddde24947321f975e673a462ed5ca1bdc2b23c6 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Mon, 3 Dec 2018 21:47:03 +0200 Subject: [PATCH 36/43] minor cleanup --- src/proxy.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 73f14d7..69a00d2 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -876,8 +876,6 @@ isinterface(class::Ptr{Nothing}) = @message(class, jboolean, methodId_class_isIn return JClass objects for the declared and inherited interfaces of a class """ function getinterfaces(class::JClass) - #array = @message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces) - #[JClass(arrayat(array, i)) for i in 1:arraylength(array)] classesFor(@message(class.ptr, Ptr{Nothing}, methodId_class_getInterfaces)) end From 5bcfc7013bb638b600497140a0c88e2f74d45e88 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Tue, 4 Dec 2018 11:51:18 +0200 Subject: [PATCH 37/43] added staticproxy(JClass) to help with using different class loaders --- src/proxy.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/proxy.jl b/src/proxy.jl index 69a00d2..2f142a1 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -402,9 +402,12 @@ class(sym::Symbol) = JProxy(sym) JProxy(::JavaMetaClass{C}) where C = staticproxy(string(C)) JProxy(::Type{JavaObject{C}}) where C = staticproxy(string(C)) JProxy(s::Symbol) = staticproxy(string(s)) -function staticproxy(classname) +function staticproxy(classname::AbstractString) c = Symbol(legalClassName(classname)) - obj = classforname(string(c)) + staticproxy(c, classforname(string(c))) +end +staticproxy(obj::JClass) = staticproxy(Symbol(legalClassName(getname(obj))), obj) +function staticproxy(c::Symbol, obj) info = infoFor(obj) JProxy{typeFor(c), true}(obj, info) end From 4b3c348423d74ea6e8d373d726fc21db956982b2 Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Tue, 4 Dec 2018 12:07:16 +0200 Subject: [PATCH 38/43] adding staticproxy(JProxy{java_lang_Class, false}) --- src/proxy.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/proxy.jl b/src/proxy.jl index 2f142a1..2ab89e6 100644 --- a/src/proxy.jl +++ b/src/proxy.jl @@ -61,6 +61,7 @@ end const types = Dict() @defjtype java_lang_Object <: java_lang +@defjtype java_lang_Class <: java_lang_Object @defjtype java_lang_String <: java_lang_Object @defjtype java_util_AbstractCollection <: java_lang_Object @defjtype java_lang_Number <: java_lang_Object @@ -407,6 +408,8 @@ function staticproxy(classname::AbstractString) staticproxy(c, classforname(string(c))) end staticproxy(obj::JClass) = staticproxy(Symbol(legalClassName(getname(obj))), obj) +staticproxy(obj::JProxy{java_lang_Class, true}) = obj +staticproxy(obj::JProxy{java_lang_Class, false}) = staticproxy(JavaObject(obj)) function staticproxy(c::Symbol, obj) info = infoFor(obj) JProxy{typeFor(c), true}(obj, info) From e7e8700eca9a48b7e47f55ee57a25a479de1ad5b Mon Sep 17 00:00:00 2001 From: Bill Burdick Date: Tue, 4 Dec 2018 12:14:43 +0200 Subject: [PATCH 39/43] exporting staticproxy --- src/JavaCall.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 4a032ad..697b269 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -4,7 +4,7 @@ export JavaObject, JavaMetaClass, JObject, JClass, JMethod, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, - narrow, JProxy, @class, interfacehas + narrow, JProxy, @class, interfacehas, staticproxy # using Compat, Compat.Dates From 2c8ede81b60443708fe150314831d4ec5f760102 Mon Sep 17 00:00:00 2001 From: "markkitt@gmail.com" Date: Mon, 13 Apr 2020 11:47:39 -0500 Subject: [PATCH 40/43] Add zot branch to testing --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ebd5194..0c60fd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,7 @@ jobs: branches: only: - master + - zot - /release-.*/ notifications: From 13aa29d619d1e92b28e270806d582522ceff446a Mon Sep 17 00:00:00 2001 From: "markkitt@gmail.com" Date: Sat, 9 May 2020 21:50:29 -0500 Subject: [PATCH 41/43] Test with new appveyor.yml for x86 --- appveyor.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 81cca65..e7ad0a3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,13 +1,14 @@ environment: matrix: + - julia_version: 1.0 + platform: x64 # 64-bit - julia_version: 1 - - julia_version: 1.3 - - julia_version: 1.4 + platform: x64 # 64-bit + - julia_version: 1 + JAVA_HOME: C:\Program Files (x86)\Java\jdk1.8.0 + platform: x86 # 32-bit - julia_version: nightly - -platform: - - x86 # 32-bit - - x64 # 64-bit + platform: x64 # 64-bit # # Uncomment the following lines to allow failures on nightly julia # # (tests will run but not make your overall status red) From 001d4ee297a803853cd03cea725990d652e01765 Mon Sep 17 00:00:00 2001 From: "markkitt@gmail.com" Date: Mon, 13 Jul 2020 22:35:21 -0500 Subject: [PATCH 42/43] Expose @class macro for static method references of classes --- JProxies/doc/index.md | 12 ++++++------ JProxies/src/JProxies.jl | 2 +- JProxies/src/proxy.jl | 2 +- src/convert.jl | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/JProxies/doc/index.md b/JProxies/doc/index.md index b6ccbb9..a6de0c7 100644 --- a/JProxies/doc/index.md +++ b/JProxies/doc/index.md @@ -1,16 +1,16 @@ -## JProxy -JProxy lets you use Java-like syntax to access fields and methods in Java. You can: +## JProxies +JProxies lets you use Java-like syntax to access fields and methods in Java. You can: * Get field values * Set field values * Call methods * Create static proxies to access static members Primitive types and strings are converted to Julia objects on field accesses and method returns and converted back to Java types when sent as arguments to Java methods. *NOTE: Because of this, if you need to call Java methods on a string that you got from Java, you'll have to use `JProxy(str)` to convert the Julia string to a proxied Java string* -To invoke static methods, set static to true (see below). +To invoke static methods, use @class (see below). To get a JProxy's Java object, use `JavaObject(proxy)` ### Examples ```jldoctest -julia> a=JProxy(@jimport(java.util.ArrayList)(())) +julia> a=JProxy(@jimport(java.util.ArrayList))() [] julia> a.size() 0 @@ -36,9 +36,9 @@ false julia> b == b true -julia> JProxy(@jimport(java.lang.System)).getName() +julia> @class(java.lang.System).getName() "java.lang.System" -julia> JProxy(@jimport(java.lang.System);static=true).out.println("hello") +julia> @class(java.lang.System;static=true).out.println("hello") hello ``` diff --git a/JProxies/src/JProxies.jl b/JProxies/src/JProxies.jl index 4814bb1..65ca6fb 100644 --- a/JProxies/src/JProxies.jl +++ b/JProxies/src/JProxies.jl @@ -11,7 +11,7 @@ module JProxies import Base: convert - export JProxy, @class, interfacehas, staticproxy + export JProxy, @class, interfacehas, staticproxy, @jimport include("proxy.jl") end diff --git a/JProxies/src/proxy.jl b/JProxies/src/proxy.jl index f192ae3..ebafffa 100644 --- a/JProxies/src/proxy.jl +++ b/JProxies/src/proxy.jl @@ -1210,7 +1210,7 @@ function (pxy::JProxy{T, STATIC})(args...) where {T, STATIC} #result = ccall(jnifunc.NewObjectA, Ptr{Nothing}, # (Ptr{JNIEnv}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), # penv, info.class.ptr, meth.id, convertedargs) - result = JNI.NewObjectA(Ptr(info.class), meth.id, convertedargs) + result = JNI.NewObjectA(Ptr(info.class), meth.id, JNI.jvalue.(convertedargs)) result == C_NULL && geterror() JProxy(result) else diff --git a/src/convert.jl b/src/convert.jl index 33b4724..ea84ab9 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -4,7 +4,7 @@ convert(::Type{JString}, str::AbstractString) = JString(str) convert(::Type{JObject}, str::AbstractString) = convert(JObject, JString(str)) # From JProxy -convert(::AbstractString, str::Type{JString}) = unsafe_string(str) +convert(::Type{AbstractString}, str::JString) = unsafe_string(str) convert(::Type{JavaObject{Symbol("java.lang.Double")}}, n::Real) = jnew(Symbol("java.lang.Double"), (jdouble,), Float64(n)) convert(::Type{JavaObject{Symbol("java.lang.Float")}}, n::Real) = jnew(Symbol("java.lang.Float"), (jfloat,), Float32(n)) convert(::Type{JavaObject{Symbol("java.lang.Long")}}, n::Real) = jnew(Symbol("java.lang.Long"), (jlong,), Int64(n)) From 9801641290348ebcb48742a74eb24faac00606df Mon Sep 17 00:00:00 2001 From: "markkitt@gmail.com" Date: Tue, 14 Jul 2020 00:14:21 -0500 Subject: [PATCH 43/43] Transfer listfields from JProxies to JavaCall --- JProxies/src/JProxies.jl | 1 + JProxies/src/proxy.jl | 4 ---- src/JavaCall.jl | 1 + src/reflect.jl | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/JProxies/src/JProxies.jl b/JProxies/src/JProxies.jl index 65ca6fb..e3bba06 100644 --- a/JProxies/src/JProxies.jl +++ b/JProxies/src/JProxies.jl @@ -6,6 +6,7 @@ module JProxies JObject, JClass, JMethod, JConstructor, JField, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, + listfields, gettype, narrow diff --git a/JProxies/src/proxy.jl b/JProxies/src/proxy.jl index ebafffa..87de5c3 100644 --- a/JProxies/src/proxy.jl +++ b/JProxies/src/proxy.jl @@ -1051,10 +1051,6 @@ end classfortype(t::Type{JavaObject{T}}) where T = classforname(string(T)) classfortype(t::Type{T}) where {T <: java_lang} = classforname(classnamefor(nameof(T))) -listfields(cls::AbstractString) = listfields(classforname(cls)) -listfields(cls::Type{JavaObject{C}}) where C = listfields(classforname(string(C))) -listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) - function fielddict(class::JClass) if isArray(class) Dict([:length => JReadonlyField((ptr)->arraylength(ptr))]) diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 2c0d09e..3da1a42 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -12,6 +12,7 @@ export JavaObject, JavaMetaClass, JObject, JClass, JMethod, JConstructor, JField, JString, @jimport, jcall, jfield, isnull, getname, getclass, listmethods, getreturntype, getparametertypes, classforname, + listfields, gettype, narrow # using Compat, Compat.Dates diff --git a/src/reflect.jl b/src/reflect.jl index d697975..5f3ae0c 100644 --- a/src/reflect.jl +++ b/src/reflect.jl @@ -62,6 +62,10 @@ function getname(method::JMethod) jcall(method, "getName", JString, ()) end +""" +""" +getname(field::JField) = jcall(field, "getName", JString, ()) + """ ``` listmethods(obj::JavaObject) @@ -123,6 +127,29 @@ function getreturntype(method::JMethod) jcall(method, "getReturnType", JClass, ()) end +""" +gettype(field::JField) + +Get type of field +""" +gettype(field::JField) = jcall(field, "getType", JClass, ()) + +""" +``` +listfields(obj::JavaObject) +``` +List the fields that are available on the java object passed. +""" +listfields(cls::AbstractString) = listfields(classforname(cls)) +listfields(cls::Type{JavaObject{C}}) where C = listfields(classforname(string(C))) +listfields(cls::JClass) = jcall(cls, "getFields", Vector{JField}, ()) +listfields(obj::JavaObject) = listfields(getclass(obj)) + +function listfields(cls::Union{JavaObject{C}, Type{JavaObject{C}}}, name::AbstractString) where C + allfields = listfields(cls) + filter(f -> getname(f) == name, allfields) +end + """ ``` getparametertypes(method::JMethod) @@ -147,6 +174,12 @@ function Base.show(io::IO, method::JMethod) print(io, "$rettype $name($argtypestr)") end +function Base.show(io::IO, field::JField) + name = getname(field) + fieldtype = getname(gettype(field)) + print(io, "$fieldtype $name") +end + """ ```