diff --git a/.gitignore b/.gitignore index a635799..8e309e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *~ .juliahistory -.DS_Store +.DS_Store \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d67b621..72af786 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: julia os: -- osx -- linux + - linux julia: -- release -- nightly + - 0.4 + - nightly notifications: -email: false + email: false diff --git a/README.md b/README.md index 03b0d22..8dd00e2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,8 @@ # JavaCall -Call Java programs from Julia. - -Documentation is available at http://aviks.github.io/JavaCall.jl - -[![Build Status](https://travis-ci.org/aviks/JavaCall.jl.png)](https://travis-ci.org/aviks/JavaCall.jl) +[![Build Status](https://travis-ci.org/aviks/JavaCall.jl.png)](https://travis-ci.org/aviks/JavaCall.jl) [![Build status](https://ci.appveyor.com/api/projects/status/xdkfd0t8r3oqeio9?svg=true)](https://ci.appveyor.com/project/aviks/javacall-jl) [![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.3.svg)](http://pkg.julialang.org/?pkg=JavaCall) [![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.4.svg)](http://pkg.julialang.org/?pkg=JavaCall) [![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.5.svg)](http://pkg.julialang.org/?pkg=JavaCall) -[![Build status](https://ci.appveyor.com/api/projects/status/xdkfd0t8r3oqeio9?svg=true)](https://ci.appveyor.com/project/aviks/javacall-jl) - -[![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.3.svg)](http://pkg.julialang.org/?pkg=JavaCall&ver=release) - -[![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.4.svg)](http://pkg.julialang.org/?pkg=JavaCall&ver=nightly) +Call Java programs from Julia. +Documentation is available at http://aviks.github.io/JavaCall.jl diff --git a/REQUIRE b/REQUIRE index a79cd4b..2e03691 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,2 @@ -julia 0.3 -Memoize -Compat -Dates \ No newline at end of file +julia 0.4 +Compat 0.8.4 diff --git a/appveyor.yml b/appveyor.yml index e94600f..eaa4416 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,11 @@ environment: matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.3/julia-0.3-latest-win32.exe" - JAVA_HOME: C:\Program Files (x86)\Java\jdk1.7.0\bin - - JULIAVERSION: "julialang/bin/winnt/x64/0.3/julia-0.3-latest-win64.exe" + - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" + JAVA_HOME: C:\Program Files (x86)\Java\jdk1.7.0\ + - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" JAVA_HOME: C:\Program Files\Java\jdk1.7.0\ - JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe" - JAVA_HOME: C:\Program Files (x86)\Java\jdk1.7.0\bin + JAVA_HOME: C:\Program Files (x86)\Java\jdk1.7.0\ - JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe" JAVA_HOME: C:\Program Files\Java\jdk1.7.0\ diff --git a/doc/faq.md b/doc/faq.md index 364b08f..50424af 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -6,9 +6,22 @@ layout: default ### ERROR: jnifunc not defined -You are attempting to call Java methods without initialising the JVM. Call `JavaCall.init(...)` first. +You are attempting to call Java methods without initialising the JVM. Call `JavaCall.init(...)` first. -### Cannot find libjvm in: +### Cannot find libjvm in: -The JVM dynamic library cannot be found. It is called `libjvm.so`, `libjvm.dylib` or `jvm.dll` depending on your system. Check that you have `JAVA_HOME` environment variable defined. If you do, verify the search logic in the code, and please file a pull request with an amemded version that can find your library. As a workaround, you can set the `JAVA_LIB` environment variable to point to the location of the library file. \ No newline at end of file +The JVM dynamic library cannot be found. It is called `libjvm.so`, `libjvm.dylib` or `jvm.dll` depending on your system. Check that you have `JAVA_HOME` environment variable defined. If you do, verify the search logic in the code, and please file a pull request with an amemded version that can find your library. As a workaround, you can set the `JAVA_LIB` environment variable to point to the location of the library file. + +### Segault on OSX + +Upon initialising JavaCall on OSX, you get an immediate segfault message, while execution carries on normally. +``` +signal (11): Segmentation fault: 11 +unknown function (ip: 0x31960c2b4) +``` +This message is usually benign. It is due to the way the JVM uses signals for its regular operations. Julia on OSX installs a signal handler, which generates the above message. However, since the signal is eventually caught and handled by the JVM, execution carries on normally. If the message troubles you for any reason, please start julia with the `--handle-signals=no` option. In this case, Julia's signal handler will be disabled, and you will no longer see the above message. However, this may cause issues with handling `^C` in Julia programs. + +_References:_ +http://stackoverflow.com/questions/27241575/why-does-java-app-crash-in-gdb-but-runs-normally-in-real-life +http://www.oracle.com/technetwork/java/javase/signals-139944.html#gbzbl diff --git a/src/JavaCall.jl b/src/JavaCall.jl index 10e37c6..21f5dfc 100644 --- a/src/JavaCall.jl +++ b/src/JavaCall.jl @@ -2,31 +2,25 @@ module JavaCall export JavaObject, JavaMetaClass, jint, jlong, jbyte, jboolean, jchar, jshort, jfloat, jdouble, JObject, JClass, JMethod, JString, - @jimport, jcall, isnull, + @jimport, jcall, jfield, isnull, getname, listmethods, getreturntype, getparametertypes -# using Debug -using Memoize using Compat +using WinReg +import Compat.string -import Base.bytestring, Base.convert +import Base.convert, Base.isnull, Base.unsafe_convert -if VERSION < v"0.4.0-dev+656" - import Compat.isnull -else - import Base.isnull -end - -if VERSION < v"0.4-" - using Dates -else - using Base.Dates -end +# if VERSION < v"0.5.0-dev+4612" +# const unsafe_string = Base.bytestring +# else +# const unsafe_string = Base.unsafe_string +# end -if VERSION < v"0.4-" - const unsafe_convert = Base.convert +if VERSION < v"0.5.0-dev+4612" + import Compat.unsafe_string else - const unsafe_convert = Base.unsafe_convert + import Base.unsafe_string end diff --git a/src/convert.jl b/src/convert.jl index 1a0daa0..e4cfd67 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -1,20 +1,24 @@ -convert{T<:String}(::Type{JString}, str::T) = JString(str) -convert{T<:String}(::Type{JObject}, str::T) = convert(JObject, JString(str)) +convert{T<:AbstractString}(::Type{JString}, str::T) = JString(str) +convert{T<:AbstractString}(::Type{JObject}, str::T) = convert(JObject, JString(str)) #Cast java object from S to T . Needed for polymorphic calls -function convert{T,S}(::Type{JavaObject{T}}, obj::JavaObject{S}) +function convert{T,S}(::Type{JavaObject{T}}, obj::JavaObject{S}) if isConvertible(T, S) #Safe static cast - return JavaObject{T}(obj.ptr) - end + ptr = ccall(jnifunc.NewLocalRef, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}), penv, obj.ptr) + if ptr === C_NULL geterror() end + return JavaObject{T}(ptr) + end if isnull(obj) ; error("Cannot convert NULL"); end realClass = ccall(jnifunc.GetObjectClass, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void} ), penv, obj.ptr) if isConvertible(T, realClass) #dynamic cast - return JavaObject{T}(obj.ptr) - end + ptr = ccall(jnifunc.NewLocalRef, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}), penv, obj.ptr) + if ptr === C_NULL geterror() end + return JavaObject{T}(ptr) + end error("Cannot cast java object from $S to $T") end -#Is java type convertible from S to T. +#Is java type convertible from S to T. isConvertible(T, S) = (ccall(jnifunc.IsAssignableFrom, jboolean, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Void}), penv, metaclass(S), metaclass(T) ) == JNI_TRUE) isConvertible(T, S::Ptr{Void} ) = (ccall(jnifunc.IsAssignableFrom, jboolean, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Void}), penv, S, metaclass(T) ) == JNI_TRUE) @@ -30,7 +34,6 @@ function real_jtype(rettype) return jnitype end - function convert_args(argtypes::Tuple, args...) convertedArgs = Array(Int64, length(args)) savedArgs = Array(Any, length(args)) @@ -87,7 +90,7 @@ function convert_arg{T<:JavaObject}(argtype::Type{Array{T,1}}, arg) return carg, arrayptr end -convert_result{T<:JString}(rettype::Type{T}, result) = bytestring(JString(result)) +convert_result{T<:JString}(rettype::Type{T}, result) = unsafe_string(JString(result)) convert_result{T<:JavaObject}(rettype::Type{T}, result) = T(result) convert_result(rettype, result) = result @@ -103,7 +106,7 @@ for (x, y, z) in [ (:jboolean, :(jnifunc.GetBooleanArrayElements), :(jnifunc.Rel function convert_result(rettype::Type{Array{$(x),1}}, result) sz = ccall(jnifunc.GetArrayLength, jint, (Ptr{JNIEnv}, Ptr{Void}), penv, result) arr = ccall($(y), Ptr{$(x)}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{jboolean} ), penv, result, C_NULL ) - jl_arr::Array = pointer_to_array(arr, (@compat Int(sz)), false) + jl_arr::Array = unsafe_wrap(Array, arr, (@compat Int(sz)), false) jl_arr = deepcopy(jl_arr) ccall($(z), Void, (Ptr{JNIEnv},Ptr{Void}, Ptr{$(x)}, jint), penv, result, arr, 0) return jl_arr @@ -114,9 +117,9 @@ end function convert_result{T}(rettype::Type{Array{JavaObject{T},1}}, result) sz = ccall(jnifunc.GetArrayLength, jint, (Ptr{JNIEnv}, Ptr{Void}), penv, result) - + ret = Array(JavaObject{T}, sz) - + for i=1:sz a=ccall(jnifunc.GetObjectArrayElement, Ptr{Void}, (Ptr{JNIEnv},Ptr{Void}, jint), penv, result, i-1) ret[i] = JavaObject{T}(a) @@ -161,11 +164,57 @@ convert{X,Y}(::Type{@jimport(java.util.Map)}, K::Type{JavaObject{X}}, V::Type{Ja # Convert a reference to a java.lang.String into a Julia string. Copies the underlying byte buffer -function bytestring(jstr::JString) #jstr must be a jstring obtained via a JNI call +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 pIsCopy = Array(jboolean, 1) - buf::Ptr{Uint8} = ccall(jnifunc.GetStringUTFChars, Ptr{Uint8}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{jboolean}), penv, jstr.ptr, pIsCopy) - s=bytestring(buf) - ccall(jnifunc.ReleaseStringUTFChars, Void, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Uint8}), penv, jstr.ptr, buf) + buf::Ptr{UInt8} = ccall(jnifunc.GetStringUTFChars, Ptr{UInt8}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{jboolean}), penv, jstr.ptr, pIsCopy) + s=unsafe_string(buf) + ccall(jnifunc.ReleaseStringUTFChars, Void, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}), penv, jstr.ptr, buf) return s end + +#This is necessary to properly deprecate bytestring in 0.5, while ensuring +# callers don't need to change for 0.4. +function Base.bytestring(jstr::JString) #jstr must be a jstring obtained via a JNI call + if VERSION >= v"0.5.0-dev+4612" + Base.depwarn("bytestring(jstr::JString) is deprecated. Use unsafe_string(jstr) instead", :bytestring ) + end + return JavaCall.unsafe_string(jstr) +end + + + + +for (x, y, z) in [ (:jboolean, :(jnifunc.GetBooleanArrayElements), :(jnifunc.ReleaseBooleanArrayElements)), + (:jchar, :(jnifunc.GetCharArrayElements), :(jnifunc.ReleaseCharArrayElements)), + (:jbyte, :(jnifunc.GetByteArrayElements), :(jnifunc.ReleaseByteArrayElements)), + (:jshort, :(jnifunc.GetShortArrayElements), :(jnifunc.ReleaseShortArrayElements)), + (:jint, :(jnifunc.GetIntArrayElements), :(jnifunc.ReleaseIntArrayElements)), + (:jlong, :(jnifunc.GetLongArrayElements), :(jnifunc.ReleaseLongArrayElements)), + (:jfloat, :(jnifunc.GetFloatArrayElements), :(jnifunc.ReleaseFloatArrayElements)), + (:jdouble, :(jnifunc.GetDoubleArrayElements), :(jnifunc.ReleaseDoubleArrayElements)) ] + m=quote + function convert(::Type{Array{$(x),1}}, obj::JObject) + sz = ccall(jnifunc.GetArrayLength, jint, (Ptr{JNIEnv}, Ptr{Void}), penv, obj.ptr) + arr = ccall($(y), Ptr{$(x)}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{jboolean} ), penv, obj.ptr, C_NULL ) + jl_arr::Array = unsafe_wrap(Array, arr, (@compat Int(sz)), false) + jl_arr = deepcopy(jl_arr) + ccall($(z), Void, (Ptr{JNIEnv},Ptr{Void}, Ptr{$(x)}, jint), penv, obj.ptr, arr, 0) + return jl_arr + end + end + eval(m) +end + + +function convert{T}(::Type{Array{T, 1}}, obj::JObject) + sz = ccall(jnifunc.GetArrayLength, jint, + (Ptr{JNIEnv}, Ptr{Void}), penv, obj.ptr) + ret = Array(T, sz) + for i=1:sz + ptr = ccall(jnifunc.GetObjectArrayElement, Ptr{Void}, + (Ptr{JNIEnv}, Ptr{Void}, jint), penv, obj.ptr, i-1) + ret[i] = convert(T, JObject(ptr)) + end + return ret +end diff --git a/src/core.jl b/src/core.jl index 612f387..3d25faa 100644 --- a/src/core.jl +++ b/src/core.jl @@ -5,7 +5,7 @@ typealias jint Cint # typedef long jlong; typealias jlong Clonglong typealias jbyte Cchar - + # jni.h typealias jboolean Cuchar @@ -14,23 +14,23 @@ typealias jshort Cshort typealias jfloat Cfloat typealias jdouble Cdouble typealias jsize jint -jprimitive = Union(jboolean, jchar, jshort, jfloat, jdouble, jint, jlong) +jprimitive = @compat Union{jboolean, jchar, jshort, jfloat, jdouble, jint, jlong} immutable JavaMetaClass{T} ptr::Ptr{Void} end -#The metaclass, sort of equivalent to a the +#The metaclass, sort of equivalent to a the JavaMetaClass(T, ptr) = JavaMetaClass{T}(ptr) type JavaObject{T} ptr::Ptr{Void} - + function JavaObject(ptr) j=new(ptr) finalizer(j, deleteref) return j - end + end JavaObject(argtypes::Tuple, args...) = jnew(T, argtypes, args...) @@ -48,19 +48,44 @@ function deleteref(x::JavaObject) end +""" +``` +isnull(obj::JavaObject) +``` +Checks if the passed JavaObject is null or not + +### Args +* obj: The object of type JavaObject + +### Returns +true if the passed object is null else false +""" isnull(obj::JavaObject) = obj.ptr == C_NULL + +""" +``` +isnull(obj::JavaMetaClass) +``` +Checks if the passed JavaMetaClass is null or not + +### Args +* obj: The object of type JavaMetaClass + +### Returns +true if the passed object is null else false +""" isnull(obj::JavaMetaClass) = obj.ptr == C_NULL -typealias JClass JavaObject{symbol("java.lang.Class")} -typealias JObject JavaObject{symbol("java.lang.Object")} -typealias JMethod JavaObject{symbol("java.lang.reflect.Method")} -typealias JString JavaObject{symbol("java.lang.String")} +typealias JClass JavaObject{Symbol("java.lang.Class")} +typealias JObject JavaObject{Symbol("java.lang.Object")} +typealias JMethod JavaObject{Symbol("java.lang.reflect.Method")} +typealias JString JavaObject{Symbol("java.lang.String")} -function JString(str::String) - jstring = ccall(jnifunc.NewStringUTF, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Uint8}), penv, utf8(str)) +function JString(str::AbstractString) + jstring = ccall(jnifunc.NewStringUTF, Ptr{Void}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, String(str)) if jstring == C_NULL geterror() - else + else return JString(jstring) end end @@ -78,20 +103,20 @@ macro jimport(class) juliaclass=sprint(Base.show_unquoted, class) elseif isa(class, Symbol) juliaclass=string(class) - elseif isa(class, String) + elseif isa(class, AbstractString) juliaclass=class else error("Macro parameter is of type $(typeof(class))!\nShould be Expr, Symbol or String") end juliaclass = replace(juliaclass, " ", "") #handle $ for innerclass quote - JavaObject{(Base.symbol($juliaclass))} + JavaObject{(Base.Symbol($juliaclass))} end end function jnew(T::Symbol, argtypes::Tuple, args...) sig = method_signature(Void, argtypes...) - jmethodId = ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Uint8}, Ptr{Uint8}), penv, metaclass(T), utf8(""), sig) + jmethodId = ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T), String(""), sig) if (jmethodId == C_NULL) error("No constructor for $T with signature $sig") end @@ -99,27 +124,27 @@ function jnew(T::Symbol, argtypes::Tuple, args...) end # Call static methods -function jcall{T}(typ::Type{JavaObject{T}}, method::String, rettype::Type, argtypes::Tuple, args... ) +function jcall{T}(typ::Type{JavaObject{T}}, method::AbstractString, rettype::Type, argtypes::Tuple, args... ) try gc_enable(false) sig = method_signature(rettype, argtypes...) - - jmethodId = ccall(jnifunc.GetStaticMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Uint8}, Ptr{Uint8}), penv, metaclass(T), utf8(method), sig) + + jmethodId = ccall(jnifunc.GetStaticMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T), String(method), sig) if jmethodId==C_NULL; geterror(true); end - + _jcall(metaclass(T), jmethodId, C_NULL, rettype, argtypes, args...) finally gc_enable(true) end - + end # Call instance methods -function jcall(obj::JavaObject, method::String, rettype::Type, argtypes::Tuple, args... ) +function jcall(obj::JavaObject, method::AbstractString, rettype::Type, argtypes::Tuple, args... ) try gc_enable(false) sig = method_signature(rettype, argtypes...) - jmethodId = ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Uint8}, Ptr{Uint8}), penv, metaclass(obj), utf8(method), sig) + jmethodId = ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(obj), String(method), sig) if jmethodId==C_NULL; geterror(true); end _jcall(obj, jmethodId, C_NULL, rettype, argtypes, args...) finally @@ -127,7 +152,44 @@ function jcall(obj::JavaObject, method::String, rettype::Type, argtypes::Tuple, end end +function jfield{T}(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) + jfieldID = ccall(jnifunc.GetStaticFieldID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T), String(field), signature(fieldType)) + if jfieldID==C_NULL; geterror(true); end + _jfield(metaclass(T), jfieldID, fieldType) +end + +function jfield(obj::JavaObject, field::AbstractString, fieldType::Type) + jfieldID = ccall(jnifunc.GetFieldID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(obj), String(field), signature(fieldType)) + if jfieldID==C_NULL; geterror(true); end + _jfield(obj, jfieldID, fieldType) +end +for (x, y, z) in [ (:jboolean, :(jnifunc.GetBooleanField), :(jnifunc.GetStaticBooleanField)), + (:jchar, :(jnifunc.GetCharField), :(jnifunc.GetStaticCharField)), + (:jbyte, :(jnifunc.GetByteField), :(jnifunc.GetStaticBypeField)), + (:jshort, :(jnifunc.GetShortField), :(jnifunc.GetStaticShortField)), + (:jint, :(jnifunc.GetIntField), :(jnifunc.GetStaticIntField)), + (:jlong, :(jnifunc.GetLongField), :(jnifunc.GetStaticLongField)), + (:jfloat, :(jnifunc.GetFloatField), :(jnifunc.GetStaticFloatField)), + (:jdouble, :(jnifunc.GetDoubleField), :(jnifunc.GetStaticDoubleField)) ] + + m = quote + function _jfield(obj, jfieldID::Ptr{Void}, fieldType::Type{$(x)}) + callmethod = ifelse( typeof(obj)<:JavaObject, $y , $z ) + result = ccall(callmethod, $x, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Void}), penv, obj.ptr, jfieldID) + if result==C_NULL; geterror(); end + return convert_result(fieldType, result) + end + end + eval(m) +end + +function _jfield(obj, jfieldID::Ptr{Void}, fieldType::Type) + callmethod = ifelse( typeof(obj)<:JavaObject, jnifunc.GetObjectField , jnifunc.GetStaticObjectField ) + result = ccall(callmethod, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Void}), penv, obj.ptr, jfieldID) + if result==C_NULL; geterror(); end + return convert_result(fieldType, result) +end #Generate these methods to satisfy ccall's compile time constant requirement #_jcall for primitive and Void return types @@ -175,33 +237,43 @@ function _jcall(obj, jmethodId::Ptr{Void}, callmethod::Ptr{Void}, rettype::Type return convert_result(rettype, result) end -@memoize function metaclass(class::Symbol) + +global const _jmc_cache = Dict{Symbol, JavaMetaClass}() + +function _metaclass(class::Symbol) jclass=javaclassname(class) - jclassptr = ccall(jnifunc.FindClass, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Uint8}), penv, jclass) + jclassptr = ccall(jnifunc.FindClass, Ptr{Void}, (Ptr{JNIEnv}, Ptr{UInt8}), penv, jclass) if jclassptr == C_NULL; error("Class Not Found $jclass"); end return JavaMetaClass(class, jclassptr) end +function metaclass(class::Symbol) + if !haskey(_jmc_cache, class) + _jmc_cache[class] = _metaclass(class) + end + return _jmc_cache[class] +end + metaclass{T}(::Type{JavaObject{T}}) = metaclass(T) metaclass{T}(::JavaObject{T}) = metaclass(T) -javaclassname(class::Symbol) = utf8(replace(string(class), '.', '/')) +javaclassname(class::Symbol) = replace(string(class), '.', '/') function geterror(allow=false) isexception = ccall(jnifunc.ExceptionCheck, jboolean, (Ptr{JNIEnv},), penv ) - + if isexception == JNI_TRUE jthrow = ccall(jnifunc.ExceptionOccurred, Ptr{Void}, (Ptr{JNIEnv},), penv) if jthrow==C_NULL ; error("Java Exception thrown, but no details could be retrieved from the JVM"); end ccall(jnifunc.ExceptionDescribe, Void, (Ptr{JNIEnv},), penv ) #Print java stackstrace to stdout ccall(jnifunc.ExceptionClear, Void, (Ptr{JNIEnv},), penv ) - jclass = ccall(jnifunc.FindClass, Ptr{Void}, (Ptr{JNIEnv},Ptr{Uint8}), penv, "java/lang/Throwable") + jclass = ccall(jnifunc.FindClass, Ptr{Void}, (Ptr{JNIEnv},Ptr{UInt8}), penv, "java/lang/Throwable") if jclass==C_NULL; error("Java Exception thrown, but no details could be retrieved from the JVM"); end - jmethodId=ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Uint8}, Ptr{Uint8}), penv, jclass, "toString", "()Ljava/lang/String;") + jmethodId=ccall(jnifunc.GetMethodID, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}), penv, jclass, "toString", "()Ljava/lang/String;") if jmethodId==C_NULL; error("Java Exception thrown, but no details could be retrieved from the JVM"); end res = ccall(jnifunc.CallObjectMethodA, Ptr{Void}, (Ptr{JNIEnv}, Ptr{Void}, Ptr{Void}, Ptr{Void}), penv, jthrow, jmethodId,C_NULL) if res==C_NULL; error("Java Exception thrown, but no details could be retrieved from the JVM"); end - msg = bytestring(JString(res)) + msg = unsafe_string(JString(res)) ccall(jnifunc.DeleteLocalRef, Void, (Ptr{JNIEnv}, Ptr{Void}), penv, jthrow) error(string("Error calling Java: ",msg)) else diff --git a/src/jvm.jl b/src/jvm.jl index 0cda063..cb279c4 100644 --- a/src/jvm.jl +++ b/src/jvm.jl @@ -16,18 +16,28 @@ const JNI_ENOMEM = convert(Cint, -4) #/* not enough memory */ const JNI_EEXIST = convert(Cint, -5) #/* VM already created */ const JNI_EINVAL = convert(Cint, -6) #/* invalid arguments */ +function javahome_winreg() + keypath = "SOFTWARE\\JavaSoft\\Java Development Kit" + value = querykey(WinReg.HKEY_LOCAL_MACHINE, keypath, "CurrentVersion") + keypath *= "\\"*value + return querykey(WinReg.HKEY_LOCAL_MACHINE, keypath, "JavaHome") +end + +@static is_unix() ? (global const libname = "libjvm") : (global const libname = "jvm") -@unix_only global const libname = "libjvm" -@windows_only global const libname = "jvm" function findjvm() javahomes = Any[] libpaths = Any[] - + if haskey(ENV,"JAVA_HOME") push!(javahomes,ENV["JAVA_HOME"]) + else + @static is_windows() ? ENV["JAVA_HOME"] = javahome_winreg() : nothing + @static is_windows() ? push!(javahomes, ENV["JAVA_HOME"]) : nothing end - if isexecutable("/usr/libexec/java_home") - push!(javahomes,chomp(readall(`/usr/libexec/java_home`))) + + if isfile("/usr/libexec/java_home") + push!(javahomes,chomp(readstring(`/usr/libexec/java_home`))) end if isdir("/usr/lib/jvm/default-java/") @@ -36,17 +46,27 @@ function findjvm() push!(libpaths,pwd()) for n in javahomes - @windows_only push!(libpaths, joinpath(n, "jre", "bin", "server")) - @linux_only if WORD_SIZE==64; push!(libpaths, joinpath(n, "jre", "lib", "amd64", "server")); end - @linux_only if WORD_SIZE==32; push!(libpaths, joinpath(n, "jre", "lib", "i386", "server")); end + @static if is_windows() + push!(libpaths, joinpath(n, "jre", "bin", "server")) + push!(libpaths, joinpath(n, "bin", "client")) + end + @static if is_linux() + if WORD_SIZE==64 + push!(libpaths, joinpath(n, "jre", "lib", "amd64", "server")) + elseif WORD_SIZE==32 + push!(libpaths, joinpath(n, "jre", "lib", "i386", "server")) + end + end push!(libpaths, joinpath(n, "jre", "lib", "server")) end - - ext = "."*@windows? "dll":@osx? "dylib":"so" - try + + ext = @static is_windows()?"dll":(@static is_apple()?"dylib":"so") + ext = "."*ext + + try for n in libpaths libpath = joinpath(n,libname*ext); - if isreadable(libpath) + if isfile(libpath) global libjvm = Libdl.dlopen(libpath) println("Loaded $libpath") return @@ -55,7 +75,7 @@ function findjvm() end errorMsg = - [ + [ "Cannot find java library $libname$ext\n", "Search Path:" ]; @@ -69,8 +89,8 @@ end -immutable JavaVMOption - optionString::Ptr{Uint8} +immutable JavaVMOption + optionString::Ptr{UInt8} extraInfo::Ptr{Void} end @@ -82,8 +102,8 @@ immutable JavaVMInitArgs end -@unix_only const sep = ":" -@windows_only const sep = ";" +@static is_unix() ? (const sep = ":") : nothing +@static is_windows() ? (const sep = ";") : nothing cp=Array(String, 0) opts=Array(String, 0) addClassPath(s::String) = isloaded()?warn("JVM already initialised. This call has no effect"): push!(cp, s) @@ -97,7 +117,7 @@ assertloaded() = isloaded()?nothing:error("JVM not initialised. Please run init( assertnotloaded() = isloaded()?error("JVM already initialised"):nothing # Pointer to pointer to pointer to pointer alert! Hurrah for unsafe load -function init{T<:String}(opts::Array{T, 1}) +function init{T<:AbstractString}(opts::Array{T, 1}) assertnotloaded() opt = Array(JavaVMOption, length(opts)) for i in 1:length(opts) @@ -106,7 +126,7 @@ function init{T<:String}(opts::Array{T, 1}) ppjvm=Array(Ptr{JavaVM},1) ppenv=Array(Ptr{JNIEnv},1) vm_args = JavaVMInitArgs(JNI_VERSION_1_6, convert(Cint, length(opts)), convert(Ptr{JavaVMOption}, pointer(opt)), JNI_TRUE) - + res = ccall(create, Cint, (Ptr{Ptr{JavaVM}}, Ptr{Ptr{JNIEnv}}, Ptr{JavaVMInitArgs}), ppjvm, ppenv, &vm_args) if res < 0; error("Unable to initialise Java VM: $(res)"); end global penv = ppenv[1] diff --git a/src/reflect.jl b/src/reflect.jl index d69c120..652dde3 100644 --- a/src/reflect.jl +++ b/src/reflect.jl @@ -3,7 +3,7 @@ function getclass(obj::JavaObject) jcall(obj, "getClass", JClass, ()) end -function conventional_name(name::String) +function conventional_name(name::AbstractString) if @compat startswith(name, "[") return conventional_name(name[2:end]) * "[]" elseif name == "Z" @@ -29,29 +29,102 @@ function conventional_name(name::String) end end +""" +``` +getname(cls::JClass) +``` +Returns the fully qualified name of the java class + +### Args +* cls: The JClass object + +### Returns +The fully qualified name of the java class +""" function getname(cls::JClass) rawname = jcall(cls, "getName", JString, ()) conventional_name(rawname) end +""" +``` +getname(method::JMethod) +``` +Returns the fully qualified name of the java method + +### Args +* cls: The JClass method + +### Returns +The fully qualified name of the method +""" function getname(method::JMethod) jcall(method, "getName", JString, ()) end +""" +``` +listmethods(obj::JavaObject) +``` +Lists the methods that are available on the java object passed + +### Args +* obj: The java object + +### Returns +List of methods +""" function listmethods(obj::JavaObject) cls = getclass(obj) jcall(cls, "getMethods", Vector{JMethod}, ()) end -function listmethods(obj::JavaObject, name::String) +""" +``` +listmethods(obj::JavaObject, name::AbstractString) +``` +Lists the methods that are available on the java object passed. The methods are filtered based on the name passed + +### Args +* obj: The java object +* name: The filter (e.g. method name) + +### Returns +List of methods available on the java object and matching the name passed +""" +function listmethods(obj::JavaObject, name::AbstractString) allmethods = listmethods(obj) filter(m -> getname(m) == name, allmethods) end +""" +``` +getreturntype(method::JMethod) +``` +Returns the return type of the java method + +### Args +* method: The java method object + +### Returns +Returns the type of the return object as a JClass +""" function getreturntype(method::JMethod) jcall(method, "getReturnType", JClass, ()) end +""" +``` +getparametertypes(method::JMethod) +``` +Returns the parameter types of the java method + +### Args +* method: The java method object + +### Returns +Vector the parametertypes +""" function getparametertypes(method::JMethod) jcall(method, "getParameterTypes", Vector{JClass}, ()) end diff --git a/test/Test$TestInner.class b/test/Test$TestInner.class index bbc42c0..d2aff04 100644 Binary files a/test/Test$TestInner.class and b/test/Test$TestInner.class differ diff --git a/test/Test.class b/test/Test.class index 1a092b7..28feee3 100644 Binary files a/test/Test.class and b/test/Test.class differ diff --git a/test/Test.java b/test/Test.java index ba03e58..9c6b6ab 100644 --- a/test/Test.java +++ b/test/Test.java @@ -1,7 +1,11 @@ +import java.io.UnsupportedEncodingException; import java.util.ArrayList; public class Test { + public int integerField = 100; + public String stringField = "A STRING"; + public static short testShort(short i) { System.out.print("In Java, recd: ");System.out.println(i); return i; @@ -36,6 +40,13 @@ public static java.util.HashMap testNull() { return null; } + public static Object testArrayAsObject() throws UnsupportedEncodingException { + byte[][] res = new byte[2][]; + res[0] = "Hello".getBytes("UTF8"); + res[1] = "World".getBytes("UTF8"); + return res; + } + public static ArrayList testArrayList() { ArrayList res=new ArrayList(); diff --git a/test/runtests.jl b/test/runtests.jl index 85ffb50..9253f8f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,13 +6,13 @@ using Compat versioninfo(); # JavaCall.init(["-Djava.class.path=$(joinpath(Pkg.dir(), "JavaCall", "test"))"]) -JavaCall.init(["-verbose:jni", "-verbose:gc","-Djava.class.path=$(joinpath(Pkg.dir(), "JavaCall", "test"))"]) +JavaCall.init(["-verbose:gc","-Djava.class.path=$(joinpath(Pkg.dir(), "JavaCall", "test"))"]) a=JString("how are you") @test a.ptr != C_NULL @test 11==ccall(JavaCall.jnifunc.GetStringUTFLength, jint, (Ptr{JavaCall.JNIEnv}, Ptr{Void}), JavaCall.penv, a.ptr) -b=ccall(JavaCall.jnifunc.GetStringUTFChars, Ptr{Uint8}, (Ptr{JavaCall.JNIEnv}, Ptr{Void}, Ptr{Void}), JavaCall.penv, a.ptr, C_NULL) -@test bytestring(b) == "how are you" +b=ccall(JavaCall.jnifunc.GetStringUTFChars, Ptr{UInt8}, (Ptr{JavaCall.JNIEnv}, Ptr{Void}, Ptr{Void}), JavaCall.penv, a.ptr, C_NULL) +@test unsafe_string(b) == "how are you" # Test parameter passing T = @jimport Test @@ -68,25 +68,25 @@ a=jcall(j_u_arrays, "copyOf", Array{jint, 1}, (Array{jint, 1}, jint), [1,2,3], 3 a=jcall(j_u_arrays, "copyOf", Array{JObject, 1}, (Array{JObject, 1}, jint), ["a","b","c"], 3) @test 3==length(a) -@test "a"==bytestring(convert(JString, a[1])) -@test "b"==bytestring(convert(JString, a[2])) -@test "c"==bytestring(convert(JString, a[3])) +@test "a"==unsafe_string(convert(JString, a[1])) +@test "b"==unsafe_string(convert(JString, a[2])) +@test "c"==unsafe_string(convert(JString, a[3])) #Test for Dates jd = @jimport(java.util.Date)(()) jcal = @jimport(java.util.GregorianCalendar)(()) -jsd = @jimport(java.sql.Date)((jlong,),int(time())) - +jsd = @jimport(java.sql.Date)((jlong,),round(jlong, time())) @assert typeof(convert(Dates.DateTime, jd)) == Dates.DateTime @assert typeof(convert(Dates.DateTime, jcal)) == Dates.DateTime @assert typeof(convert(Dates.DateTime, jsd)) == Dates.DateTime nulldate = @jimport(java.util.Date)(C_NULL) -Dates.year(convert(Dates.DateTime, nulldate)) == 1970 +@assert Dates.year(convert(Dates.DateTime, nulldate)) == 1970 nullcal = @jimport(java.util.GregorianCalendar)(C_NULL) -Dates.year(convert(Dates.DateTime, nullcal)) == 1970 +@assert Dates.year(convert(Dates.DateTime, nullcal)) == 1970 +Dates.year(convert(Dates.DateTime, nullcal)) == 1970 #Test for Map conversion JHashMap = @jimport(java.util.HashMap) @@ -102,6 +102,18 @@ t=Test(()) inner = TestInner((Test,), t) @assert jcall(inner, "innerString", JString, ()) == "from inner" +#Static Field Access +@assert jfield(@jimport(java.lang.Math), "E", jdouble) == 2.718281828459045 +@assert jfield(@jimport(java.lang.Math), "PI", jdouble) == 3.141592653589793 +@assert jfield(@jimport(java.text.NumberFormat), "INTEGER_FIELD", jint) == 0 +Locale = @jimport java.util.Locale +lc = jfield(@jimport(java.util.Locale), "CANADA", @jimport(java.util.Locale)) +#Instance field access +#Disabled for now. Need to verify stability +@assert jfield(@jimport(java.util.logging.Logger), "GLOBAL_LOGGER_NAME", JString ) == "global" +@assert jcall(lc, "getCountry", JString, ()) == "CA" +@assert jfield(t, "integerField", jint) == 100 +@assert jfield(t, "stringField", JString) == "A STRING" # Test Memory allocation and de-allocatios # the following loop fails with an OutOfMemoryException in the absence of de-allocation @@ -116,12 +128,26 @@ end @test_throws ErrorException jcall(jlm, "sinx", jdouble, (jdouble,), 1.0) @test_throws ErrorException jcall(jlm, "sinx", jdouble, (jdouble,), 1.0) -@test length(listmethods(JString("test"))) == 72 -@test length(listmethods(JString("test"), "indexOf")) == 4 +@test length(listmethods(JString("test"))) >= 72 +@test length(listmethods(JString("test"), "indexOf")) >= 3 m = listmethods(JString("test"), "indexOf")[1] @test getname(getreturntype(m)) == "int" @test [getname(typ) for typ in getparametertypes(m)] == ["java.lang.String", "int"] +#Test for double free bug, #20 +#Fix in #28. The following lines will segfault without the fix +JHashtable = @jimport java.util.Hashtable +JProperties = @jimport java.util.Properties +ta_20=Any[] +for i=1:100; push!(ta_20, convert(JHashtable, JProperties((),))); end +gc(); gc() +for i=1:100; @test jcall(ta_20[i], "size", jint, ()) == 0; end + +# Test array conversions +jobj = jcall(T, "testArrayAsObject", JObject, ()) +arr = convert(Array{Array{UInt8, 1}, 1}, jobj) +@test ["Hello", "World"] == map(Compat.String, arr) + # At the end, unload the JVM before exiting JavaCall.destroy()