diff --git a/ChangeLog b/ChangeLog index 08b159cb2..246e0ace3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2024-06-12 Vedant Tewari + + * configure.ac: Add support for JNI (Java Native Interface) 2023-02-25 Ron Norman diff --git a/DEPENDENCIES b/DEPENDENCIES index 2a1bde415..a29c6d797 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -137,6 +137,19 @@ The following libraries ARE required WHEN : JSON-C is distributed under Expat License. +5) JNI (Java Native Interface) support is used + + BOTH runtime AND development components required. + + One of the following: + + o Java Development Kit (JDK) 8 or later + https://openjdk.java.net/ + + The JDK is distributed under various open-source licenses depending on the vendor and version. Common licenses include the GNU General Public License (GPL) and the Oracle Binary Code License Agreement. + + To enable JNI support, ensure that the JDK is installed on your system, and set the appropriate environment variables (e.g., JAVA_HOME) to point to the JDK installation directory. + See HACKING if you wish to hack the GnuCOBOL source or build directly from version control as this includes the list of additional tools diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 266459ff7..27e8b1450 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -119,3 +119,15 @@ Support for GENERATE JSON is provided by *one* of the following: JSON-C is distributed under Expat License. +JNI Support +------------ + +Support for JNI (Java Native Interface) is provided by: + +* [Java Development Kit (JDK)](https://openjdk.java.net/) 8 or later. + + The JDK is distributed under various open-source licenses depending on the vendor and version. Common licenses include the GNU General Public License (GPL) and the Oracle Binary Code License Agreement. + +To enable JNI support, ensure that the JDK is installed on your system, and set the appropriate environment variables (e.g., JAVA_HOME) to point to the JDK installation directory. + + diff --git a/cobc/codegen.c b/cobc/codegen.c index 20f576571..d79676065 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -146,6 +146,8 @@ static struct literal_list *literal_cache = NULL; static struct field_list *field_cache = NULL; static struct field_list *local_field_cache = NULL; static struct call_list *call_cache = NULL; +extern JavaVM *jvm; +extern JNIEnv *env; static struct call_list *func_call_cache = NULL; static struct static_call_list *static_call_cache = NULL; static struct base_list *base_cache = NULL; @@ -387,6 +389,21 @@ lookup_source (const char *p) return source_id++; } +static void lookup_java_call(const char *p) { + struct call_list *clp; + + for (clp = call_cache; clp; clp = clp->next) { + if (strcmp(p, clp->call_name) == 0) { + return; + } + } + + clp = (struct call_list *)cob_malloc(sizeof(struct call_list)); + clp->call_name = p; + clp->next = call_cache; + call_cache = clp; +} + static void lookup_call (const char *p) { @@ -419,6 +436,24 @@ lookup_func_call (const char *p) func_call_cache = clp; } +static void +lookup_java_call (const char *p) +{ + struct static_call_list *sclp; + + for (sclp = static_call_cache; sclp; sclp = sclp->next) { + if (strcmp (p, sclp->call_name) == 0) { + return; + } + } + sclp = cobc_parse_malloc (sizeof (struct static_call_list)); + sclp->call_name = p; + sclp->convention = convention; + sclp->return_type = return_type; + sclp->next = static_call_cache; + static_call_cache = sclp; +} + static void lookup_static_call (const char *p, int convention, int return_type) { @@ -7070,7 +7105,7 @@ output_call (struct cb_call *p) ret_ptr = 1; } system_call = NULL; - + // check the name where it is translated, check if the string begins with java then encode the string differently #ifdef _WIN32 if (p->convention & CB_CONV_STDCALL) { convention = "_std"; @@ -7532,27 +7567,39 @@ output_call (struct cb_call *p) if (name_is_literal_or_prototype) { s = get_program_id_str (p->name); name_str = cb_encode_program_id (s, 1, cb_fold_call); - lookup_call (name_str); - callname = s; +#ifdef HAVE_JNI + /* Distinguishing lookup from call*/ + if(strncmp("Java.", name, 6) == 0) { + void static_java_method = lookup_static_call(s + 6, p->argv[0], COB_RETURN_NULL); + cob_call_java(static_java_method); + } else { + // rest +#endif + // we need to use lookup_java_call instead (implement) + lookup_java_call (name_str); + callname = s; - output_line ("if (call_%s.funcvoid == NULL || cob_glob_ptr->cob_physical_cancel)", name_str); - output_block_open (); - output_prefix (); + output_line ("if (call_%s.funcvoid == NULL || cob_glob_ptr->cob_physical_cancel)", name_str); + output_block_open (); + output_prefix (); - nlp = find_nested_prog_with_id (name_str); - if (nlp) { - output ("call_%s.funcint = %s_%d__;", - name_str, name_str, - nlp->nested_prog->toplev_count); - } else { - output ("call_%s.funcvoid = ", name_str); - output ("cob_resolve_cobol ("); - output_string ((const unsigned char *)s, - (int)strlen (s), 0); - output (", %d, %d);", cb_fold_call, !p->stmt1); + nlp = find_nested_prog_with_id (name_str); + if (nlp) { + output ("call_%s.funcint = %s_%d__;", + name_str, name_str, + nlp->nested_prog->toplev_count); + } else { + output ("call_%s.funcvoid = ", name_str); + output ("cob_resolve_cobol ("); + output_string ((const unsigned char *)s, + (int)strlen (s), 0); + output (", %d, %d);", cb_fold_call, !p->stmt1); + } + output_newline (); + output_block_close (); +#ifdef HAVE_JNI } - output_newline (); - output_block_close (); +#endif } else { name_str = NULL; needs_unifunc = 1; diff --git a/configure.ac b/configure.ac index c490cfecc..7c10c5fac 100644 --- a/configure.ac +++ b/configure.ac @@ -468,6 +468,22 @@ AC_DEFUN([AC_PROG_CXX], []) AC_PROG_LN_S AC_PROG_INSTALL +PRINT_VAR(JAVA_HOME) +PRINT_VAR(JAVA) +PRINT_VAR(JAVAC) +PRINT_VAR(JAVAH) +PRINT_VAR(JAR) +PRINT_VAR(JNI_INCLUDES) + +AC_PATH_PROG(javahome,java, "/usr/bin") +javahome=`dirname ${javahome}` +echo "JAVAHOME=${javahome}" +for each in `ls ${javahome}/include` ;do + if [ test -d ${javahome}/include/${each} ] ;then + javaos=${each} + fi; +done; + AC_PROG_MAKE_SET AC_LIB_RPATH @@ -480,9 +496,34 @@ dnl AC_CHECK_HEADERS([stdint.h whcar.h malloc.h]) # mandatory: AC_CHECK_HEADERS([sys/types.h signal.h stddef.h], [], [AC_MSG_ERROR([mandatory header could not be found or included])]) +AC_CHECK_HEADERS([jni.h], [have_jni=yes], + [AC_MSG_ERROR([jni.h is required but could not be found])]) +AC_CHECK_HEADERS([jni_md.h], [], + [AC_MSG_ERROR([jni_md.h is required but could not be found])]) # optional: AC_CHECK_HEADERS([sys/time.h locale.h fcntl.h dlfcn.h sys/wait.h sys/sysmacros.h]) - +AC_ARG_VAR([JAVA_HOME], [Java Runtime Environment (JRE) location]) +AC_ARG_ENABLE([java], + [AC_HELP_STRING([--disable-java], + [disable Java Interoperatibility])]) +case $target_cpu in + x86_64) JVM_ARCH=amd64 ;; + i?86) JVM_ARCH=i386 ;; + *) JVM_ARCH=$target_cpu ;; +esac +AC_SUBST([JVM_ARCH]) +AS_IF([test X$enable_java_feature != Xno], + [AS_IF([test X$have_jni != Xyes], + [AC_MSG_FAILURE([The Java Native Interface is required for Java feature.])]) + AS_IF([test -z "$JAVA_HOME"], + [AC_MSG_WARN([JAVA_HOME has not been set. JAVA_HOME must be set at run time to locate libjvm.])], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="-L$JAVA_HOME/lib/$JVM_ARCH/client -L$JAVA_HOME/lib/$JVM_ARCH/server $LDFLAGS" + AC_CHECK_LIB([jvm], [JNI_CreateJavaVM], [LIBS=$LIBS], + [AC_MSG_WARN([no libjvm found at JAVA_HOME])]) + AC_DEFINE([WITH_JNI], [1]) + LDFLAGS=$save_LDFLAGS +])]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST diff --git a/libcob/ChangeLog b/libcob/ChangeLog index b5bb0ab8c..5f094c17f 100644 --- a/libcob/ChangeLog +++ b/libcob/ChangeLog @@ -1,3 +1,8 @@ +2024-06-12 Vedant Tewari + + * Makefile.am: Updated to include JNI-related files. + * coblocal.h: Added internal state for JNI support. + * java.c: Implemented JNI support for GnuCOBOL. 2023-06-02 Simon Sobisch diff --git a/libcob/Makefile.am b/libcob/Makefile.am index 272481a4c..baee7d6ee 100644 --- a/libcob/Makefile.am +++ b/libcob/Makefile.am @@ -19,6 +19,12 @@ # You should have received a copy of the GNU General Public License # along with GnuCOBOL. If not, see . +lib_LTLIBRARIES = libcob.la +libcob_la_SOURCES = common.c move.c numeric.c strings.c \ + fileio.c call.c intrinsic.c termio.c screenio.c reportio.c cobgetopt.c \ + java.c \ + mlio.c coblocal.h cconv.c system.def profiling.c + if LOCAL_CJSON nodist_libcob_la_SOURCES = cJSON.c DISTCLEANFILES = cJSON.c cJSON.h diff --git a/libcob/coblocal.h b/libcob/coblocal.h index c8e49f088..d6c0ecc69 100644 --- a/libcob/coblocal.h +++ b/libcob/coblocal.h @@ -1,6 +1,6 @@ /* Copyright (C) 2007-2012, 2014-2022 Free Software Foundation, Inc. - Written by Roger While, Simon Sobisch, Ron Norman + Written by Roger While, Simon Sobisch, Ron Norman, Vedant Tewari This file is part of GnuCOBOL. @@ -449,6 +449,18 @@ COB_HIDDEN const char *cob_get_last_exception_name (void); COB_EXPIMP void cob_field_to_string (const cob_field *, void *, const size_t); COB_HIDDEN void cob_parameter_check (const char *, const int); +COB_HIDDEN char* cob_get_strerror (void); + +enum cob_case_modifier { + CCM_NONE, + CCM_LOWER, + CCM_UPPER, + CCM_LOWER_LOCALE, + CCM_UPPER_LOCALE +}; + +COB_HIDDEN int cob_field_to_string (const cob_field *, void *, + const size_t, const enum cob_case_modifier target_case); COB_HIDDEN cob_settings *cob_get_settings_ptr (void); diff --git a/libcob/common.h b/libcob/common.h index 2ae5954ee..4a70bd22f 100644 --- a/libcob/common.h +++ b/libcob/common.h @@ -1294,6 +1294,17 @@ struct cob_call_struct { cob_call_union cob_cstr_cancel; /* Cancel entry */ }; +#ifdef HAVE_JNI +typedef struct __cob_java_static_method { + jclass cls; + jmethodID mid; +} cob_java_handle; + +COB_EXPIMP cob_java_handle* cob_resolve_java (const char* class_name, const char* method_name, +const char *type_signature); +COB_EXPIMP int cob_call_java (const cob_java_handle* nargs); +#endif + /* Screen structure */ typedef struct __cob_screen { struct __cob_screen *next; /* Pointer to next */ diff --git a/libcob/java.c b/libcob/java.c new file mode 100644 index 000000000..e35b6f97a --- /dev/null +++ b/libcob/java.c @@ -0,0 +1,218 @@ +/* + Copyright (C) 2024 Free Software Foundation, Inc. + Written by Vedant Tewari + + This file is part of GnuCOBOL. + + The GnuCOBOL runtime library is free software: you can redistribute it + and/or modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + GnuCOBOL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with GnuCOBOL. If not, see . +*/ + +#include +#include +#include +#include +#include + +#define HAVE_JNI + +/* For caching which is an optimization but can we internally implement it as well? */ + +static JavaVM *jvm = NULL; +/* pointer to native method interface */ +static JNIEnv *env = NULL; + +static void +cob_java_initialize() { + /* JDK/JRE 6 VM initialization arguments */ + JavaVMInitArgs args; + JavaVMOption* options = (JavaVMOption*)cob_malloc(sizeof(JavaVMOption) * 1); + args.version = JNI_VERSION_1_6; + /* inline */ + args.nOptions = 1; + options[0].optionString = "-Djava.class.path=/usr/lib/java"; + args.options = &options; + args.ignoreUnrecognized = 1; + int rv; + /* loading and initializing a Java VM, returning as JNI interface */ + rv = JNI_CreateJavaVM(jvm, (void**)&env, &args); +} + +static void cob_handle_error(const char* method_sig) { + if (method_sig != NULL) { + free(method_sig); + } + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + cob_cleanup(); +} + +void cob_delete_java_object(jobject obj) { + if (obj != NULL) { + (*env)->DeleteGlobalRef(env, obj); + } +} + +static void +cob_static_method(jclass cls, jmethodID mid) { + (*env)->CallStaticVoidMethod(env, cls, mid, NULL); +} + +static void +JNICALL cob_call_java_static_method(jclass cls, char *class_name, const char* method_name, const char *return_type, jobject obj, jstring input) { + char* paramTypes = cob_get_method_parameter_types(env, cls, method_name); + char* methodSig = cob_gen_method_sig(paramTypes, 1, return_type); + + jclass cls = (*env)->FindClass(env, class_name); + if (cls == NULL) { + cob_handle_error(methodSig); + return; + } + + jmethodID mid = (*env)->GetStaticMethodID(env, cls, method_name, methodSig); + if (mid == NULL) { + cob_handle_error(methodSig); + return; + } + + const char *nativeInput = (*env)->GetStringUTFChars(env, input, NULL); + if (nativeInput == NULL) { + cob_handle_error(methodSig); + return; + } + jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, (*env)->NewStringUTF(env, nativeInput)); + + const char *nativeResult = (*env)->GetStringUTFChars(env, result, 0); + if (nativeResult == NULL) { + cob_handle_error(NULL); + return; + } + + (*env)->ReleaseStringUTFChars(env, input, nativeInput); + (*env)->ReleaseStringUTFChars(env, result, nativeResult); + + if ((*env)->ExceptionCheck(env)) { + cob_handle_error(NULL); + return; + } + + free(methodSig); +} + +jobject +cob_create_java_object(const char *class_name, const char *constructor_sig, jvalue *args) { + jclass cls = (*env)->FindClass(env, class_name); + if (cls == NULL) { + cob_handle_error(NULL); + return NULL; + } + + jmethodID constructor = (*env)->GetMethodID(env, cls, "", constructor_sig); + if (constructor == NULL) { + cob_handle_error(NULL); + return NULL; + } + + jobject obj = (*env)->NewObjectA(env, cls, constructor, args); + if (obj == NULL) { + cob_handle_error(NULL); + return NULL; + } + jobject global_obj = (*env)->NewGlobalRef(env, obj); + (*env)->DeleteLocalRef(env, obj); + return global_obj; +} + +int +cob_call_java(const cob_java_handle *method_handle) { + JNIEnv *env; + va_list args; + jobject result; + + if (method_handle == NULL) { + return -1; + } + + env = cob_get_jni_env(); + if (env == NULL) { + cob_fatal_error("Failed to get JNI environment"); + return -1; + } + + va_start(args, method_handle); + result = (*env)->CallStaticObjectMethodV(env, method_handle->cls, method_handle->mid, args); + va_end(args); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env);; + return -1; + } + + return (result != NULL) ? 0 : -1; +} + +cob_java_handle* cob_resolve_java(const char *class_name, const char* method_name, const char *type_signature) { + if (jvm == NULL) { + // Initializes JVM variables and env variables + // TODO: check return status; if error, return NULL + cob_java_initialize(); + } + + jclass cls = (*env)->FindClass(env, class_name); + if (!cls) { + cob_handle_error("Class not found"); + return NULL; + } + + jmethodID mid = (*env)->GetStaticMethodID(env, cls, method_name, type_signature); + if (!mid) { + (*env)->DeleteLocalRef(env, cls); + cob_handle_error("Method not found"); + return NULL; + } + + cob_java_handle *handle = (cob_java_handle*)malloc(sizeof(cob_java_handle)); + if (!handle) { + (*env)->DeleteLocalRef(env, cls); + cob_handle_error("Memory allocation failed"); + return NULL; + } + + handle->cls = (*env)->NewGlobalRef(env, cls); + handle->mid = mid; + (*env)->DeleteLocalRef(env, cls); + + return handle; +} + +static void +cob_lookup_static_method(const char *className, const char *methodName, +const char *methodSig, const char *returnType, const char** paramTypes, int paramCount) { + jclass cls = (*env)->FindClass(env, className); + jmethodID mid = get_from_cache(cls, methodName, methodSig); + if (mid == NULL) { + char* signature = cob_gen_method_sig(paramTypes, paramCount, returnType); + mid = (*env)->GetStaticMethodID(env, cls, methodName, signature); + if (mid == NULL) { + free(signature); + (*jvm)->DestroyJavaVM(jvm); + return; + } + free(signature); + } + + cob_static_method(env, jvm, cls, mid); +}