diff --git a/R/jfirst.R b/R/jfirst.R index faa8dbd..3e74ca7 100644 --- a/R/jfirst.R +++ b/R/jfirst.R @@ -22,7 +22,7 @@ "RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames", "RisAssignableFrom", "RpollException", "RsetField", "RthrowException", "javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols", - "RgetJVMstate", + "RgetJVMstate", "RloadJVM", "RunloadJVM", # .External "RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod", # .C diff --git a/R/jinit.R b/R/jinit.R index e622edc..05c6b01 100644 --- a/R/jinit.R +++ b/R/jinit.R @@ -9,8 +9,41 @@ .need.init <- function() .Call(RJava_needs_init) +.jloadJVM <- function(path, silent=FALSE) + .Call(RloadJVM, path.expand(path), silent) + +.junloadJVM <- function() + .Call(RunloadJVM) + +.jfindAndLoadJVM <- function() { + home <- Sys.getenv("JAVA_HOME") + if (!nzchar(home) || !isTRUE(dir.exists(home))) { + ## try java_home first if present + if (file.exists("/usr/libexec/java_home")) { + home <- tryCatch(system("/usr/libexec/java_home", intern=TRUE, ignore.stderr=TRUE), + error=function(...) "") + } + ## otherwise rely on java in PATH + if (!nzchar(home) || !isTRUE(dir.exists(home))) { + home <- tryCatch(system(paste("java", "-cp", + shQuote(system.file("java", package="rJava")), "getsp", "java.home"), + intern=TRUE, ignore.stderr=TRUE), error=function(...) "") + } + } + if (!nzchar(home) || !isTRUE(dir.exists(home))) + stop("Cannot find Java. Either make sure java executable is on the PATH or set the JAVA_HOME environment variable.") + libjvm <- list.files(home, "^libjvm[.]", recursive=TRUE, full.names=TRUE) + if (!length(libjvm)) + stop("Cannot find libjvm in ", home) + message("Loading JVM from ", home) + .jloadJVM(libjvm) +} + ## initialization .jinit <- function(classpath=NULL, parameters=getOption("java.parameters"), ..., silent=FALSE, force.init=FALSE) { + st <- .jvmState() + if (st$state == "not-loaded") + .jfindAndLoadJVM() running.classpath <- character() if (!.need.init()) { running.classpath <- .jclassPath() diff --git a/mkdist b/mkdist index 6da35ff..03bc17f 100644 --- a/mkdist +++ b/mkdist @@ -166,6 +166,7 @@ echo "Copy compiled Java classes ..." # copy all complied Java classes and sources cp src/java/*.class inst/java cp src/java/*.java inst/java +cp tools/getsp* inst/java # move RJavaClassLoader into boot area since it will be loaded by the system class loader mv inst/java/RJavaClassLoader* inst/java/boot # move javadoc directory diff --git a/src/Rglue.c b/src/Rglue.c index 45bbcd4..5c3b862 100644 --- a/src/Rglue.c +++ b/src/Rglue.c @@ -1135,8 +1135,11 @@ REPC SEXP RgetJVMstate(void) { const char *st = "unknown"; switch (rJava_JVM_state) { case JVM_STATE_NONE: /* could be detached */ - st = (existingJVMs() > 0) ? "detached" : "none"; - break; + { + int evm = existingJVMs(); + st = (evm == -1) ? "not-loaded" : ((evm > 0) ? "detached" : "none"); + break; + } case JVM_STATE_CREATED: st = "created"; break; case JVM_STATE_ATTACHED: st = "attached"; break; case JVM_STATE_DEAD: st = "dead"; break; @@ -1147,3 +1150,31 @@ REPC SEXP RgetJVMstate(void) { UNPROTECT(1); return res; } + +REPC SEXP RloadJVM(SEXP sPath, SEXP sSilent) { + const char *path; + int res, silent = Rf_asInteger(sSilent); + if (TYPEOF(sPath) != STRSXP || LENGTH(sPath) != 1) + Rf_error("Invalid path to JVM library"); + /* FIXME: worry about encoding? */ + path = CHAR(STRING_ELT(sPath, 0)); + res = djni_load(path); + if (silent) + return Rf_ScalarInteger(res); + switch(res) { + case -1: Rf_error("JVM run-time is already loaded"); + case -2: Rf_error("Cannot load JVM run-time (%s)", djni_last_error() ? djni_last_error() : "unsuccessful"); + case -3: Rf_error("JVM run-time does not have required entry points"); + } + return Rf_ScalarLogical(1); +} + +REPC SEXP RunloadJVM(void) { + int res; + if (rJava_JVM_state == JVM_STATE_CREATED || + rJava_JVM_state == JVM_STATE_ATTACHED) { + destroyJVM(); + } + res = djni_unload(); + return Rf_ScalarLogical((res < -1) ? 0 : 1); +} diff --git a/src/djni.c b/src/djni.c index 0675868..c35655f 100644 --- a/src/djni.c +++ b/src/djni.c @@ -38,6 +38,8 @@ int djni_load(const char *path) { JNI_GetDefaultJavaVMInitArgs_fn = 0; JNI_CreateJavaVM_fn = 0; JNI_GetCreatedJavaVMs_fn = 0; + dlclose(jni_dl); + jni_dl = 0; return -3; } return 0; @@ -54,6 +56,14 @@ int djni_unload(void) { return -1; } +int djni_loaded(void) { + return jni_dl ? 1 : 0; +} + +const char* djni_last_error(void) { + return last_error; +} + /* The following are the "normal" JNI API calls which are routed to libjvm JNI API. If no JNI was loaded, they return -99 to distinguish it from JNI error codes. */ diff --git a/src/djni.h b/src/djni.h index a1ba697..8abfa7d 100644 --- a/src/djni.h +++ b/src/djni.h @@ -9,8 +9,13 @@ #include "jni.h" +/* returned if no run-time is loaded */ +#define JNI_NO_DJNI -99 + int djni_load(const char *path); int djni_unload(void); +const char* djni_last_error(void); +int djni_loaded(void); jint JNI_GetDefaultJavaVMInitArgs(void *args); jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args); diff --git a/src/init.c b/src/init.c index d44d92d..8f76143 100644 --- a/src/init.c +++ b/src/init.c @@ -89,10 +89,12 @@ static void JNICALL exit_hook(int status) { exit(status); } +/* -1 if no run-time is loaded, otherwise number of active VMs */ int existingJVMs(void) { jsize vms = 0; JavaVM *jvms[32]; - return (JNI_GetCreatedJavaVMs(jvms, 32, &vms) >= 0) ? vms : 0; + jint res = JNI_GetCreatedJavaVMs(jvms, 32, &vms); + return (res == JNI_NO_DJNI) ? -1 : ((res >= 0) ? vms : 0); } /* in reality WIN64 implies WIN32 but to make sure ... */ @@ -118,7 +120,9 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks, if(!user_classpath) user_classpath = ""; vm_args.version = JNI_VERSION_1_2; - if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) { + if((res = JNI_GetDefaultJavaVMInitArgs(&vm_args)) != JNI_OK) { + if (res == JNI_NO_DJNI) /* DJNI has not loaded a run-time yet */ + error("No Java run-time is not loaded."); error("JNI 1.2 or higher is required"); return -1; } @@ -193,8 +197,11 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks, if (disableGuardPages && (res != 0 || !eenv)) return -2; /* perhaps this VM does not allow disabling guard pages */ - if (res != 0) - error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %ld)", (long int) res); + if (res != 0) { + if (rJava_JVM_state != JVM_STATE_NONE) + Rf_warning("Attempt to re-create a second JVM in a process - most known Java implementations do not support it."); + Rf_error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)", (int) res); + } if (!eenv) error("Cannot obtain JVM environment"); @@ -760,21 +767,31 @@ static SEXP RinitJVM_jsw(SEXP par) { /** RinitJVM(classpath) initializes JVM with the specified class path */ REP SEXP RinitJVM(SEXP par) { - + if (!djni_loaded()) + Rf_error("No Java run-time is loaded."); #ifndef JVM_STACK_WORKAROUND - return RinitJVM_real(par, 0); + return RinitJVM_real(par, 0); #else - return RinitJVM_jsw(par); + return RinitJVM_jsw(par); #endif } -REP void doneJVM(void) { - (*jvm)->DestroyJavaVM(jvm); - jvm = 0; - eenv = 0; - rJava_JVM_state = JVM_STATE_DESTROYED; +/* detaches (if only attached) or destroys (if created) JVM */ +HIDE void destroyJVM(void) { + if (!jvm) + return; + if (rJava_JVM_state == JVM_STATE_CREATED || + rJava_JVM_state == JVM_STATE_ATTACHED) { + (*jvm)->DetachCurrentThread(jvm); + if (rJava_JVM_state == JVM_STATE_CREATED) + (*jvm)->DestroyJavaVM(jvm); + jvm = 0; + eenv = 0; + rJava_JVM_state = (rJava_JVM_state == JVM_STATE_CREATED) ? JVM_STATE_DESTROYED : JVM_STATE_DETACHED; + } } + /** * Initializes the cached values of classes and methods used internally * These classes and methods are the ones that are in rJava (RJavaTools, ...) diff --git a/src/rJava.h b/src/rJava.h index 6f731f3..23f0759 100644 --- a/src/rJava.h +++ b/src/rJava.h @@ -1,7 +1,7 @@ #ifndef __RJAVA_H__ #define __RJAVA_H__ -#define RJAVA_VER 0x01000b /* rJava v1.0-11 */ +#define RJAVA_VER 0x010010 /* rJava v1.1-0 */ /* important changes between versions: 3.0 - adds compiler @@ -19,7 +19,7 @@ 0.2 - uses S4 classes 0.1 - first public release */ -#include +#include "djni.h" #include #include #include @@ -140,7 +140,8 @@ extern int rJava_initialized; #define JVM_STATE_CREATED 1 /* JVM was created by us */ #define JVM_STATE_ATTACHED 2 /* we attached to another JVM */ #define JVM_STATE_DEAD 4 /* set when Java exit handler was called */ -#define JVM_STATE_DESTROYED 8 /* JVM was destroyed */ +#define JVM_STATE_DETACHED 8 /* detached from existing JVM */ +#define JVM_STATE_DESTROYED 9 /* JVM was destroyed */ extern int rJava_JVM_state; @@ -168,6 +169,7 @@ extern jmethodID mid_RJavaImport_lookup ; extern jmethodID mid_RJavaImport_exists ; HIDE void init_rJava(void); +HIDE void destroyJVM(void); /* in otables.c */ // turn this for debugging in otables.c