blob: 065c6210cdd57e669fbd4a173f61da2a3661e0a0 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include "log.h"
#include "thread.h"
#include "system.h"
#include "system_java.h"
static JavaVM *javaVirtualMachine = NULL;
JNIEXPORT jint
JNI_OnLoad (JavaVM *vm, void *reserved) {
javaVirtualMachine = vm;
return JAVA_JNI_VERSION;
}
JNIEXPORT void
JNI_OnUnload (JavaVM *vm, void *reserved) {
javaVirtualMachine = NULL;
}
JavaVM *
getJavaInvocationInterface (void) {
return javaVirtualMachine;
}
typedef struct {
JavaVM *virtualMachine;
JNIEnv *nativeInterface;
char *threadName;
} ThreadSpecificData;
static THREAD_SPECIFIC_DATA_NEW(tsdJavaNativeThread) {
ThreadSpecificData *tsd;
if ((tsd = malloc(sizeof(*tsd)))) {
memset(tsd, 0, sizeof(*tsd));
return tsd;
} else {
logMallocError();
}
return NULL;
}
static THREAD_SPECIFIC_DATA_DESTROY(tsdJavaNativeThread) {
ThreadSpecificData *tsd = data;
{
JavaVM *vm = tsd->virtualMachine;
(*vm)->DetachCurrentThread(vm);
}
logMessage(LOG_DEBUG, "thread detached from Java VM: %s", tsd->threadName);
free(tsd->threadName);
free(tsd);
}
THREAD_SPECIFIC_DATA_CONTROL(tsdJavaNativeThread);
static char *
getJavaThreadName (JNIEnv *env) {
char *name = NULL;
static jclass Thread_class = NULL;
if (findJavaClass(env, &Thread_class, JAVA_OBJ_THREAD)) {
static jmethodID Thread_currentThread = 0;
if (findJavaStaticMethod(env, &Thread_currentThread, Thread_class, "currentThread",
JAVA_SIG_METHOD(JAVA_SIG_THREAD,
))) {
jobject thread = (*env)->CallStaticObjectMethod(env, Thread_class, Thread_currentThread);
if (!clearJavaException(env, 1)) {
static jmethodID Thread_getName = 0;
if (findJavaInstanceMethod(env, &Thread_getName, Thread_class, "getName",
JAVA_SIG_METHOD(JAVA_SIG_STRING,
))) {
jstring jName = (*env)->CallObjectMethod(env, thread, Thread_getName);
if (!clearJavaException(env, 1)) {
jboolean isCopy;
const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy);
if (!(name = strdup(cName))) {
logMallocError();
}
(*env)->ReleaseStringUTFChars(env, jName, cName);
(*env)->DeleteLocalRef(env, jName);
}
}
(*env)->DeleteLocalRef(env, thread);
}
}
}
return name;
}
JNIEnv *
getJavaNativeInterface (void) {
JavaVM *vm = getJavaInvocationInterface();
#ifdef __ANDROID__
JNIEnv *env = NULL;
#else /* __ANDROID__ */
void *env = NULL;
#endif /* __ANDROID__ */
if (vm) {
jint result = (*vm)->GetEnv(vm, (void **)&env, JAVA_JNI_VERSION);
if (result != JNI_OK) {
if (result == JNI_EDETACHED) {
JavaVMAttachArgs args = {
.version = JAVA_JNI_VERSION,
.name = NULL,
.group = NULL
};
if ((result = (*vm)->AttachCurrentThread(vm, &env, &args)) < 0) {
logMessage(LOG_WARNING, "Java AttachCurrentThread error: %d", result);
} else {
ThreadSpecificData *tsd = getThreadSpecificData(&tsdJavaNativeThread);
tsd->virtualMachine = vm;
tsd->nativeInterface = env;
tsd->threadName = getJavaThreadName(env);
logMessage(LOG_DEBUG, "thread attached to Java VM: %s", tsd->threadName);
}
} else {
logMessage(LOG_WARNING, "Java GetEnv error: %d", result);
}
}
}
return env;
}
int
clearJavaException (JNIEnv *env, int describe) {
int exceptionOccurred = (*env)->ExceptionCheck(env);
if (exceptionOccurred) {
if (describe) (*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
return exceptionOccurred;
}
static jobject javaClassLoaderInstance = NULL;
static jclass javaClassLoaderClass = NULL;
static jmethodID loadClassMethod = 0;
int
setJavaClassLoader (JNIEnv *env, jobject instance) {
if (instance) {
javaClassLoaderInstance = (*env)->NewGlobalRef(env, instance);
if (javaClassLoaderInstance) {
jclass class = (*env)->GetObjectClass(env, instance);
if (class) {
javaClassLoaderClass = (*env)->NewGlobalRef(env, class);
(*env)->DeleteLocalRef(env, class);
class = NULL;
if (javaClassLoaderClass) {
jmethodID method = (*env)->GetMethodID(env, javaClassLoaderClass, "loadClass",
JAVA_SIG_METHOD(JAVA_SIG_CLASS,
JAVA_SIG_STRING // className
));
if (method) {
loadClassMethod = method;
return 1;
}
(*env)->DeleteGlobalRef(env, javaClassLoaderClass);
}
}
(*env)->DeleteGlobalRef(env, javaClassLoaderInstance);
}
}
javaClassLoaderInstance = NULL;
javaClassLoaderClass = NULL;
loadClassMethod = 0;
return 0;
}
static jclass
loadJavaClass (JNIEnv *env, const char *path) {
size_t size = strlen(path) + 1;
char cName[size];
jclass class = NULL;
jobject jName;
{
const char *p = path;
char *n = cName;
char c;
do {
c = *p++;
if (c == '/') c = '.';
*n++ = c;
} while (c);
}
if ((jName = (*env)->NewStringUTF(env, cName))) {
jclass result = (*env)->CallObjectMethod(env, javaClassLoaderInstance, loadClassMethod, jName);
if (clearJavaException(env, 1)) {
(*env)->DeleteLocalRef(env, result);
} else {
class = result;
}
(*env)->DeleteLocalRef(env, jName);
} else {
logMallocError();
}
return class;
}
int
findJavaClass (JNIEnv *env, jclass *class, const char *path) {
if (*class) return 1;
{
jclass localReference = loadClassMethod?
loadJavaClass(env, path):
(*env)->FindClass(env, path);
if (localReference) {
jclass globalReference = (*env)->NewGlobalRef(env, localReference);
(*env)->DeleteLocalRef(env, localReference);
localReference = NULL;
if (globalReference) {
logMessage(LOG_DEBUG, "java class found: %s", path);
*class = globalReference;
return 1;
} else {
logMallocError();
clearJavaException(env, 0);
}
} else {
logMessage(LOG_ERR, "java class not found: %s", path);
clearJavaException(env, 1);
}
}
return 0;
}
int
findJavaInstanceMethod (
JNIEnv *env, jmethodID *method,
jclass class, const char *name, const char *signature
) {
if (!*method) {
if (!(*method = (*env)->GetMethodID(env, class, name, signature))) {
logMessage(LOG_ERR, "java instance method not found: %s: %s", name, signature);
clearJavaException(env, 0);
return 0;
}
logMessage(LOG_DEBUG, "java instance method found: %s: %s", name, signature);
}
return 1;
}
int
findJavaStaticMethod (
JNIEnv *env, jmethodID *method,
jclass class, const char *name, const char *signature
) {
if (!*method) {
if (!(*method = (*env)->GetStaticMethodID(env, class, name, signature))) {
logMessage(LOG_ERR, "java static method not found: %s: %s", name, signature);
clearJavaException(env, 0);
return 0;
}
logMessage(LOG_DEBUG, "java static method found: %s: %s", name, signature);
}
return 1;
}
int
findJavaConstructor (
JNIEnv *env, jmethodID *constructor,
jclass class, const char *signature
) {
return findJavaInstanceMethod(env, constructor, class, JAVA_CONSTRUCTOR_NAME, signature);
}
int
findJavaInstanceField (
JNIEnv *env, jfieldID *field,
jclass class, const char *name, const char *signature
) {
if (!*field) {
if (!(*field = (*env)->GetFieldID(env, class, name, signature))) {
logMessage(LOG_ERR, "java instance field not found: %s: %s", name, signature);
clearJavaException(env, 0);
return 0;
}
logMessage(LOG_DEBUG, "java instance field found: %s: %s", name, signature);
}
return 1;
}
int
findJavaStaticField (
JNIEnv *env, jfieldID *field,
jclass class, const char *name, const char *signature
) {
if (!*field) {
if (!(*field = (*env)->GetStaticFieldID(env, class, name, signature))) {
logMessage(LOG_ERR, "java static field not found: %s: %s", name, signature);
clearJavaException(env, 0);
return 0;
}
logMessage(LOG_DEBUG, "java static field found: %s: %s", name, signature);
}
return 1;
}
char *
getJavaLocaleName (void) {
char *name = NULL;
JNIEnv *env;
if ((env = getJavaNativeInterface())) {
jclass Locale_class = NULL;
if (findJavaClass(env, &Locale_class, JAVA_OBJ_LOCALE)) {
jmethodID Locale_getDefault = 0;
if (findJavaStaticMethod(env, &Locale_getDefault, Locale_class, "getDefault",
JAVA_SIG_METHOD(JAVA_SIG_LOCALE,
))) {
jobject locale = (*env)->CallStaticObjectMethod(env, Locale_class, Locale_getDefault);
if (!clearJavaException(env, 1)) {
jmethodID Locale_toString = 0;
if (findJavaInstanceMethod(env, &Locale_toString, Locale_class, "toString",
JAVA_SIG_METHOD(JAVA_SIG_STRING,
))) {
jstring jName = (*env)->CallObjectMethod(env, locale, Locale_toString);
if (!clearJavaException(env, 1)) {
jboolean isCopy;
const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy);
if (!(name = strdup(cName))) {
logMallocError();
}
(*env)->ReleaseStringUTFChars(env, jName, cName);
(*env)->DeleteLocalRef(env, jName);
}
}
(*env)->DeleteLocalRef(env, locale);
}
}
}
}
return name;
}
#if defined(__ANDROID__)
#include <locale.h>
#include "messages.h"
static void
initializeAndroidEnvironment (JNIEnv *env) {
{
static jclass class = NULL;
if (findJavaClass(env, &class, JAVA_OBJ_BRLTTY("BrailleApplication"))) {
static jmethodID method = 0;
if (findJavaStaticMethod(env, &method, class, "getCurrentLocale",
JAVA_SIG_METHOD(JAVA_SIG_STRING,
))) {
jstring jLocale = (*env)->CallStaticObjectMethod(env, class, method);
if (!clearJavaException(env, 1)) {
if (jLocale) {
jboolean isCopy;
const char *cLocale = (*env)->GetStringUTFChars(env, jLocale, &isCopy);
if (cLocale) {
if (setMessagesLocale(cLocale)) {
}
(*env)->ReleaseStringUTFChars(env, jLocale, cLocale);
}
(*env)->DeleteLocalRef(env, jLocale);
}
}
}
}
}
}
#endif /* platform-speciofic initialization */
void
initializeSystemObject (void) {
#if defined(__ANDROID__)
initializeAndroidEnvironment(getJavaNativeInterface());
#endif /* platform-speciofic initialization */
}