diff --git a/ChangeLog b/ChangeLog index 8af2a8eae..fe73d596b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ + +2024-07-12 Vedant Tewari -2023-02-25 Ron Norman + * m4/ax_prog_java.m4, m4/ax_jni_include_dir.m4: Added macros for JNI checks + * configure.ac: Added support for Java interoperability through JNI, extending GnuCOBOL ability to interact with external Java libraries + * NEWS, DEPENDENCIES: Updated to reflect support for JNI + +2023-02-25 Ron Norman * configure.ac: Add check for sys/time.h diff --git a/DEPENDENCIES b/DEPENDENCIES index 2a1bde415..5a96a03ef 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -137,6 +137,21 @@ 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. + + Java Development Kit (JDK) 8 or later + + https://openjdk.org/ + + 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..db892996f 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -119,3 +119,13 @@ 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. \ No newline at end of file diff --git a/NEWS b/NEWS index 72f3e9ed6..1ef4ce7b7 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,8 @@ NEWS - user visible changes -*- outline -*- * New GnuCOBOL features +** Initial support for Java interoperability through JNI (optional dependency JDK): Java Interoperability: Added initial support for calling + Java methods from COBOL programs via the Java Native Interface (JNI). This feature currently supports static Java methods with no parameters, while handling parameters and return values will be introduced in future updates. The system detects missing Java classes and malformed method names and provides proper error handling. COBOL developers can now use ON EXCEPTION and NOT ON EXCEPTION clauses with Java calls. JNI support is an optional feature that requires the Java Development Kit (JDK) to be installed on the system. Use the --with-java configure option to enable it, and ensure that the JAVA_HOME environment variable is set at runtime to locate the JDK. Without JNI, the runtime will function as usual but Java-related COBOL calls (e.g., CALL "Java..") will not be available ** file handling: added backends for ODBC (so far PostgrSQL, MySQL, SQLite, MSSQL) and OCI, along with new directory COB_SCHEMA_DIR containing the diff --git a/cobc/ChangeLog b/cobc/ChangeLog index 9aa4a85e9..cfd8c7967 100644 --- a/cobc/ChangeLog +++ b/cobc/ChangeLog @@ -1,4 +1,10 @@ +2024-08-14 Nicolas Berthier + + * cobc.c (cobc_print_info): added note for Java interoperability + * typeck.c, tree.h (cb_check_conformance): pass call convention to deal + with calls of Java methods + 2024-08-04 David Declerck Adjustments to merge 2022-12-21: diff --git a/cobc/cobc.c b/cobc/cobc.c index 88d357929..9c5369589 100644 --- a/cobc/cobc.c +++ b/cobc/cobc.c @@ -2620,6 +2620,12 @@ cobc_print_info (void) cobc_var_print (_("JSON library"), _(WITH_JSON), 0); +#ifdef WITH_JNI + cobc_var_print (_("Java interoperability"), _("enabled"), 0); +#else + cobc_var_print (_("Java interoperability"), _("disabled"), 0); +#endif + #ifdef COB_DEBUG_LOG cobc_var_print ("DEBUG_LOG", _("enabled"), 0); #endif diff --git a/cobc/codegen.c b/cobc/codegen.c index 61bbe9300..d8cefe356 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -1,7 +1,7 @@ /* Copyright (C) 2003-2024 Free Software Foundation, Inc. Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, - Edward Hart + Edward Hart, Vedant Tewari This file is part of GnuCOBOL. @@ -147,6 +147,7 @@ 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; +static struct call_list *call_java_cache = NULL; static struct call_list *func_call_cache = NULL; static struct static_call_list *static_call_cache = NULL; static struct base_list *base_cache = NULL; @@ -395,6 +396,22 @@ lookup_source (const char *p) return source_id++; } +static void +lookup_java_call (const char *p) +{ + struct call_list *clp; + + for (clp = call_java_cache; clp; clp = clp->next) { + if (strcmp (p, clp->call_name) == 0) { + return; + } + } + clp = cobc_parse_malloc (sizeof (struct call_list)); + clp->call_name = cobc_parse_strdup (p); + clp->next = call_java_cache; + call_java_cache = clp; +} + static void lookup_call (const char *p) { @@ -1978,6 +1995,11 @@ output_call_cache (void) output_local ("static cob_call_union\tcall_%s;\n", call->call_name); } + call_java_cache = call_list_reverse (call_java_cache); + for (call = call_java_cache; call; call = call->next) { + output_local ("static cob_java_handle*\tcall_java_%s;\n", + call->call_name); + } func_call_cache = call_list_reverse (func_call_cache); for (call = func_call_cache; call; call = call->next) { output_local ("static cob_call_union\tfunc_%s;\n", @@ -7068,6 +7090,61 @@ output_field_constant (cb_tree x, int n, const char *flagname) output_newline (); } +static void +output_exception_handling (struct cb_call *p) +{ + if (p->stmt1) { + output_line ("cob_glob_ptr->cob_stmt_exception = 1;"); + output_line ("COB_RESET_EXCEPTION(0);"); + } else { + output_line ("cob_glob_ptr->cob_stmt_exception = 0;"); + } + + output_line ("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) "); + output_block_open (); + + if (p->stmt1) { + output_stmt (p->stmt1); + } else if (p->stmt2) { + output_stmt (p->stmt2); + } + output_block_close (); + output_line ("COB_RESET_EXCEPTION(0);"); +} + +static void +output_java_call (struct cb_call *p) +{ + char *class_and_method_name, *class_name, *method_name, *last_dot, *c; + char mangled[COB_NORMAL_BUFF]; + + /* Assume "Java." prefix (enforced in `parser.y`, rule `call_body`) */ + class_and_method_name = (char*)CB_LITERAL(p->name)->data + 5; + + strncpy (mangled, class_and_method_name, COB_NORMAL_MAX); + for (c = mangled; *c; c++) { + if (*c == '.') *c = '_'; + } + lookup_java_call (mangled); + + last_dot = strrchr (class_and_method_name, '.'); + *last_dot = '\0'; + method_name = last_dot + 1; + class_name = class_and_method_name; + for (c = class_name; *c; c++) { + if (*c == '.') *c = '/'; + } + + output_line ("if (call_java_%s == NULL)", mangled); + output_block_open (); + output_line ("call_java_%s = cob_resolve_java (\"%s\", \"%s\", \"()V\");", + mangled, class_name, method_name); + output_line ("cob_call_java (call_java_%s);", mangled); + output_block_close (); + + output_exception_handling (p); +} + static void output_call (struct cb_call *p) { @@ -7097,6 +7174,12 @@ output_call (struct cb_call *p) } system_call = NULL; + if (p->convention & CB_CONV_JAVA) { + output_java_call(p); + output_exception_handling(p); + return; + } + #ifdef _WIN32 if (p->convention & CB_CONV_STDCALL) { convention = "_std"; diff --git a/cobc/parser.y b/cobc/parser.y index b86ff0288..4153e47bd 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12253,13 +12253,17 @@ call_body: /* Check parameter conformance, if we can work out what is being called. */ if (CB_LITERAL_P ($3)) { - cb_check_conformance ($3, $7, $8); + /* Check for "Java." prefix and set call convention */ + if (strncasecmp("Java.", (char *)CB_LITERAL ($3)->data, 5) == 0) { + call_conv = CB_CONV_JAVA; + } + cb_check_conformance ($3, $7, $8, call_conv); } else if (CB_REFERENCE_P ($3)) { cb_tree ref = cb_ref ($3); if ((CB_FIELD_P (ref) && CB_FIELD (ref)->flag_item_78) || CB_PROGRAM_P (ref) || CB_PROTOTYPE_P (ref)) { - cb_check_conformance ($3, $7, $8); + cb_check_conformance ($3, $7, $8, call_conv); } } diff --git a/cobc/tree.h b/cobc/tree.h index 55278955d..43e399137 100644 --- a/cobc/tree.h +++ b/cobc/tree.h @@ -199,6 +199,7 @@ enum cb_tag { #define CB_CONV_THUNK_16 (1 << 5) #define CB_CONV_STDCALL (1 << 6) #define CB_CONV_COBOL (1 << 15) +#define CB_CONV_JAVA (1 << 16) #define CB_CONV_C (0) #define CB_CONV_PASCAL (CB_CONV_L_TO_R | CB_CONV_CALLEE_STACK) @@ -2591,7 +2592,7 @@ extern cb_tree cb_build_write_advancing_lines (cb_tree, cb_tree); extern cb_tree cb_build_write_advancing_mnemonic (cb_tree, cb_tree); extern cb_tree cb_build_write_advancing_page (cb_tree); extern cb_tree cb_check_sum_field (cb_tree x); -extern void cb_check_conformance (cb_tree, cb_tree, cb_tree); +extern void cb_check_conformance (cb_tree, cb_tree, cb_tree, int); extern void cb_emit_initiate (cb_tree rep); extern void cb_emit_terminate (cb_tree rep); extern void cb_emit_generate (cb_tree rep); diff --git a/cobc/typeck.c b/cobc/typeck.c index d147d211a..9ea4d5d08 100644 --- a/cobc/typeck.c +++ b/cobc/typeck.c @@ -3921,7 +3921,7 @@ check_argument_conformance (struct cb_program *program, cb_tree argument_tripple void cb_check_conformance (cb_tree prog_ref, cb_tree using_list, - cb_tree returning) + cb_tree returning, int call_conv) { struct cb_program *program = NULL; cb_tree l; @@ -3932,6 +3932,24 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, const struct cb_field *prog_returning_field; const struct cb_field *call_returning_field; + if (call_conv == CB_CONV_JAVA && CB_LITERAL_P (prog_ref)) { + char *full_name, *class_and_method_name, *dot; + full_name = (char *)CB_LITERAL(prog_ref)->data; + class_and_method_name = full_name + 5; + dot = strchr (class_and_method_name, '.'); + if (dot == NULL) { + cb_error_x (prog_ref, _("malformed Java method name '%s', " + "expected format 'Java.ClassName.methodName'"), + full_name); + return; + } + if (using_list != NULL || returning != NULL) { + CB_PENDING ("Java method call with parameters or return values"); + COBC_ABORT (); + } + return; + } + /* Try to get the program referred to by prog_ref. */ program = try_get_program (prog_ref); if (!program) { diff --git a/configure.ac b/configure.ac index 5083447ba..190d5e8d2 100644 --- a/configure.ac +++ b/configure.ac @@ -113,6 +113,8 @@ AH_TEMPLATE([WITH_JSON], [JSON handler]) AH_TEMPLATE([WITH_CJSON], [Use cJSON library/source as JSON handler]) AH_TEMPLATE([WITH_JSON_C], [Use JSON-C library as JSON handler]) +AH_TEMPLATE([WITH_JNI], [Support for Java calls through JNI]) + AH_TEMPLATE([COB_EXPORT_DYN], [Compile/link option for exporting symbols]) AH_TEMPLATE([COB_PIC_FLAGS], [Compile/link option for PIC code]) AH_TEMPLATE([COB_DEBUG_FLAGS], [Compile/link option for debugging]) @@ -150,6 +152,8 @@ dnl done via AC_CHECK_FUNCS: AH_TEMPLATE([HAVE_RAISE], [Has raise function]) AH_TEMPLATE([HAVE_FINITE_IEEEFP_H], [Declaration of finite function in ieeefp.h instead of math.h]) +AH_TEMPLATE([COB_JAVA_ARCH], [Java platform architecture]) + dnl preparation for cross-compilation AC_ARG_PROGRAM @@ -483,7 +487,6 @@ AC_CHECK_HEADERS([sys/types.h signal.h stddef.h], [], # optional: AC_CHECK_HEADERS([sys/time.h locale.h fcntl.h dlfcn.h sys/wait.h sys/sysmacros.h]) - # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_BIGENDIAN @@ -933,7 +936,81 @@ AS_IF([test "$with_xml2" = "yes" -o "$with_xml2" = "check"], [ LIBS="$curr_libs"; CPPFLAGS="$curr_cppflags" ]) +# Check for JNI +AC_ARG_WITH([java], + [AS_HELP_STRING([--without-java], + [disable Java Interoperability])]) +cob_has_jni=no +AS_IF([test "x$with_java" != "xno"], [ + dnl Find `java` and $JAVA_HOME (we need the latter to locate libjvm). + dnl AX_CHECK_JAVA_HOME dnl (<- does not appear to work properly) + AX_PROG_JAVA + if test "x$JAVA_HOME" = x; then + JAVA_HOME="$( $JAVA -XshowSettings:properties -version 2>&1 >/dev/null | \ + $SED -e '/^[ ]*java.home/!d' -e 's/.*=[ ]*//' )" + AC_MSG_NOTICE([Found Java home: ${JAVA_HOME}]) + else + AC_MSG_NOTICE([Given Java home: ${JAVA_HOME}]) + fi + dnl Note: One more hack needed to properly locate libjvm with + dnl early versions of openjdk (1.8 at least). + JAVA_ARCH="$( $JAVA -XshowSettings:properties -version 2>&1 >/dev/null | \ + $SED -e '/^[ ]*os.arch/!d' -e 's/.*=[ ]*//' )" + + dnl Note: AX_PROG_JAVAC might find a `javac` binary that does + dnl not match the version of `$JAVA` found above, so we set + dnl its path manually. + JAVAC="$JAVA_HOME/bin/javac" + AX_PROG_JAVAC_WORKS + + AX_JNI_INCLUDE_DIR + AS_IF([test "$JNI_INCLUDE_DIRS" != ""], [ + for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS; do + JNI_CPPFLAGS="$JNI_CPPFLAGS -I$JNI_INCLUDE_DIR" + done + for _dir in "${JAVA_HOME}/jre/lib" \ + "${JAVA_HOME}/jre/lib/${JAVA_ARCH}" \ + "${JAVA_HOME}/lib"; do + if test -d "$_dir"; then + JNI_LIBS="$JNI_LIBS -L$_dir" + if test -d "$_dir/server"; then + JNI_LIBS="$JNI_LIBS -L$_dir/server" + fi + if test -d "$_dir/client"; then + JNI_LIBS="$JNI_LIBS -L$_dir/client" + fi + fi + done + curr_LIBS="$LIBS" + curr_CPPFLAGS="$CPPFLAGS" + LIBS="$LIBS $JNI_LIBS -ljvm" + CPPFLAGS="$CPPFLAGS $JNI_CPPFLAGS" + AC_MSG_CHECKING([if -ljvm brings JNI symbols]) + AC_LINK_IFELSE([ + AC_LANG_SOURCE([[ + #include + void main (void) { + (void) JNI_CreateJavaVM (NULL, NULL, NULL); + } + ]]) + ], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([WITH_JNI], [1]) + AC_DEFINE_UNQUOTED([COB_JAVA_ARCH], ["$JAVA_ARCH"]) + JNI_LDFLAGS="$JNI_LIBS" + JNI_LIBS="-ljvm" + cob_has_jni=yes + ], [ + AC_MSG_RESULT([no]) + ]) + LIBS="$curr_LIBS" + CPPFLAGS="$curr_CPPFLAGS" + ]) +]) +AS_IF([test "x$with_java" = "xyes" -a "x$cob_has_jni" != "xyes"], [ + AC_MSG_ERROR([Java interoperability requested, but JNI was not found]) +]) # Checks for cjson/json-c. AC_MSG_NOTICE([Checks for JSON handler]) @@ -2570,6 +2647,8 @@ AM_CONDITIONAL([COB_MAKE_LMDB_LIB], [test "$cob_gen_lmdb" = "yes"]) AM_CONDITIONAL([LOCAL_CJSON],[test "$USE_JSON" = "local"]) +AM_CONDITIONAL([COB_HAS_JNI], [test "$cob_has_jni" = "yes"]) + unset COB_USES_GCC unset COB_USES_GCC_NO_ICC unset COB_USES_ICC_ONLY @@ -2660,6 +2739,11 @@ AC_SUBST([OCI_CFLAGS]) AC_SUBST([BDB_CFLAGS]) AC_SUBST([LMDB_CFLAGS]) +AC_SUBST([JAVAC]) +AC_SUBST([JNI_LIBS]) +AC_SUBST([JNI_LDFLAGS]) +AC_SUBST([JNI_CPPFLAGS]) + dnl was used in bin/Makefile.am - seems not to be needed dnl AC_SUBST([COB_EXPORT_DYN]) @@ -2683,6 +2767,7 @@ AC_SUBST([COB_HAS_OCEXTFH]) AC_SUBST([COB_HAS_CURSES]) AC_SUBST([COB_HAS_XML2]) AC_SUBST([COB_HAS_JSON]) +AC_SUBST([COB_HAS_JNI]) AC_SUBST([COB_HAS_64_BIT_POINTER]) AC_SUBST([COB_PATCH_LEVEL], [$with_patch_level]) # needed for bin/cob-config @@ -2804,4 +2889,6 @@ case "$USE_JSON" in ;; esac +AC_MSG_NOTICE([ Build with Java interoperability: ${cob_has_jni}]) + unset DEFINE_DL diff --git a/doc/gnucobol.texi b/doc/gnucobol.texi index e724e5d73..fc85d4b27 100644 --- a/doc/gnucobol.texi +++ b/doc/gnucobol.texi @@ -2211,6 +2211,7 @@ For a complete list of supported system routines, * CBL_GC_NANOSLEEP:: Sleep for nanoseconds * CBL_GC_FORK:: Fork the current COBOL process to a new one * CBL_GC_WAITPID:: Wait for a system process to end +* Java Integration:: Interfacing with Java through JNI @end menu @node CBL_GC_GETOPT @@ -2575,6 +2576,45 @@ is not available on the current system. END-DISPLAY @end example +@node Java Integration +@section Java Integration +@cindex Java, JNI, method calls + +GnuCOBOL now supports integration with Java through the Java Native Interface (JNI). This allows COBOL programs to call Java methods directly. + +@subsection Supported Method Calls +Currently, only void Java methods with no parameters are supported. Method calls that do not conform to this restriction will result in an error at compile-time. + +@subsection Setting Up Java Integration +To use Java integration, you need to set the `JAVA_HOME` environment variable to point to your Java installation. Additionally, ensure that your `PATH` includes the Java binaries. + +@subsection Error Handling +Any attempt to call Java methods with parameters or expecting return values will result in a compile-time error. The GnuCOBOL compiler will issue a warning if the method name does not follow the `Java.ClassName.methodName` format. + +@subsection Known Limitations +Currently, GnuCOBOL supports only void methods without parameters. Future updates will address these limitations to provide broader Java integration capabilities. + +@subsection Example Usage +Here is an example of a COBOL program calling a Java method: + +@example + IDENTIFICATION DIVISION. + PROGRAM-ID. SampleJavaCall. + PROCEDURE DIVISION. + CALL "Java.com.example.HelloWorld.printMessage". + STOP RUN. +@end example + +Ensure the class `com.example.HelloWorld` is in your classpath and that `printMessage` is a static void method with no parameters. + +@subsection Environment Variables +Set the following environment variables to enable Java integration: + +@example +export JAVA_HOME=/path/to/java +export PATH=$JAVA_HOME/bin:$PATH +@end example + @node Appendices @menu diff --git a/libcob/ChangeLog b/libcob/ChangeLog index 70a953966..a9d220e3c 100644 --- a/libcob/ChangeLog +++ b/libcob/ChangeLog @@ -1,4 +1,19 @@ +2024-08-22 Vedant Tewari + * cobc/codegen.c Added error handling to restrict Java method calls to void methods without parameters or return values + * cobc/parser.y Implemented detection of malformed Java method names during parsing. Added checks for unsupported method calls with parameters or return values + * java.c Enhanced error handling with `cob_runtime_error` for missing classes + +2024-08-14 Nicolas Berthier + + * common.c (print_info_detailed): added note for Java interoperability + +2024-06-12 Vedant Tewari + + * Makefile.am Updated to include JNI-related files + * common.h Added internal state for JNI support + * java.c Implemented JNI support for GnuCOBOL + 2024-08-04 David Declerck Adjustments to merge 2022-12-21: diff --git a/libcob/Makefile.am b/libcob/Makefile.am index 272481a4c..1fe6d084b 100644 --- a/libcob/Makefile.am +++ b/libcob/Makefile.am @@ -1,8 +1,8 @@ # # Makefile gnucobol/libcob # -# Copyright (C) 2003-2012, 2014, 2017-2023 Free Software Foundation, Inc. -# Written by Keisuke Nishida, Roger While, Simon Sobisch +# Copyright (C) 2003-2012, 2014, 2017-2024 Free Software Foundation, Inc. +# Written by Keisuke Nishida, Roger While, Simon Sobisch, Vedant Tewari # # This file is part of GnuCOBOL. # @@ -125,8 +125,18 @@ else lib_lm = endif +if COB_HAS_JNI +lib_jni = libcobjni.la +libcobjni_la_LIBADD = libcob.la $(JNI_LIBS) +libcobjni_la_SOURCES = java.c +libcobjni_la_LDFLAGS = $(AM_LDFLAGS) $(JNI_LDFLAGS) -version-info 1:0:0 +libcobjni_la_CFLAGS = $(AM_CFLAGS) $(JNI_CPPFLAGS) +else +lib_jni = +endif + lib_LTLIBRARIES = libcob.la $(lib_ci) $(lib_di) $(lib_vb) $(lib_vc) \ - $(lib_od) $(lib_oc) $(lib_db) $(lib_lm) + $(lib_od) $(lib_oc) $(lib_db) $(lib_lm) $(lib_jni) EXTRA_DIST = fisam.c pkgincludedir = $(includedir)/libcob diff --git a/libcob/call.c b/libcob/call.c index 61fdf3d51..040a7beb9 100644 --- a/libcob/call.c +++ b/libcob/call.c @@ -123,6 +123,14 @@ lt_dlerror (void) #endif +#if defined (_WIN32) || defined (USE_LIBDL) +/* Try pre-loading libjvm/jvm.dll if JAVA_HOME is set. */ +# define JVM_PRELOAD 1 +static lt_dlhandle jvm_handle = NULL; +#else +/* Using libltdl, no need to preload. */ +#endif + #include "sysdefines.h" /* Force symbol exports */ @@ -785,7 +793,7 @@ cob_encode_program_id (const unsigned char *const name, default: break; } - + return pos; } @@ -1160,6 +1168,7 @@ cob_load_lib (const char *library, const char *entry, char *reason) } #endif + DEBUG_LOG ("call", ("lt_dlopenlcl '%s'\n", library)); p = lt_dlopenlcl (library); if (p) { p = lt_dlsym (p, entry); @@ -1275,7 +1284,7 @@ cob_module_clean (cob_module *m) { struct call_hash *p; struct call_hash **q; - + #ifndef COB_ALT_HASH const char *entry; @@ -1845,6 +1854,13 @@ cob_exit_call (void) } base_dynload_ptr = NULL; +#ifdef JVM_PRELOAD + if (jvm_handle) { + lt_dlclose (jvm_handle); + jvm_handle = NULL; + } +#endif + #if !defined(_WIN32) && !defined(USE_LIBDL) lt_dlexit (); #if 0 /* RXWRXW - ltdl leak */ @@ -1974,3 +1990,159 @@ cob_init_call (cob_global *lptr, cob_settings* sptr, const int check_mainhandle) call_lastsize = CALL_BUFF_SIZE; } +/* Java API handling */ + +#ifdef WITH_JNI + +/* "Standard" path suffixes to the dynamically loadable JVM library, from + "typical" JAVA_HOME. */ +const char* const path_to_jvm[] = { +#if defined(_WIN32) || defined(__CYGWIN__) +# define JVM_FILE "jvm.dll" + "\\jre\\bin\\server", + "\\jre\\bin\\client", +#else +# define JVM_FILE "libjvm." COB_MODULE_EXT + "/lib/server", + "/jre/lib/server", + "/jre/lib/" COB_JAVA_ARCH "/server", + "/lib/client", + "/jre/lib/client", + "/jre/lib/" COB_JAVA_ARCH "/client", +#endif + NULL, +}; + +static void +init_jvm_search_dirs (void) { + const char *java_home; + const char *path_suffix = NULL; + char jvm_path[COB_FILE_MAX]; + unsigned int i = 0; + + if ((java_home = getenv ("JAVA_HOME")) == NULL) { + DEBUG_LOG ("call", ("JAVA_HOME is not defined\n")); + return; + } + + DEBUG_LOG ("call", ("JAVA_HOME='%s'\n", java_home)); + + while ((path_suffix = path_to_jvm[i++]) != NULL) { +#if JVM_PRELOAD + /* Lookup libjvm.so/jvm.dll */ + if (snprintf (jvm_path, (size_t)COB_FILE_MAX, "%s%s%c%s", + java_home, path_suffix, + SLASH_CHAR, JVM_FILE) == 0) { + continue; + } + if (access (jvm_path, F_OK) != 0) { + DEBUG_LOG ("call", ("'%s': not found\n", jvm_path)); + continue; + } + DEBUG_LOG ("call", ("preloading '%s': ", jvm_path)); + jvm_handle = lt_dlopen (jvm_path); + DEBUG_LOG ("call", ("%s\n", jvm_handle != NULL ? "success" : "failed")); + break; +#else + /* Append to search path. */ + int success; +# warning On some systems, JAVA_HOME-based lookup via `libltdl` does not work + if (snprintf (jvm_path, (size_t)COB_FILE_MAX, "%s%s", + java_home, path_suffix) == 0) { + continue; + } + DEBUG_LOG ("call", ("appending '%s' to load path: ", jvm_path)); + success = lt_dladdsearchdir (jvm_path); + DEBUG_LOG ("call", ("%s\n", success == 0 ? "success" : "failed")); +#endif + } +} + +#define LIBCOBJNI_MODULE_NAME (LIB_PRF "cobjni" LIB_SUF) +#define LIBCOBJNI_ENTRY_NAME "cob_jni_init" + +typedef void (*java_init_func) (cob_java_api*); + +static cob_java_api *java_api; +static char module_errmsg[256]; + +static int +cob_init_java (void) { + java_init_func jinit; + + init_jvm_search_dirs (); + + java_api = cob_malloc (sizeof (cob_java_api)); + if (java_api == NULL) { + goto error; + } + + module_errmsg[0] = 0; + jinit = (java_init_func) cob_load_lib (LIBCOBJNI_MODULE_NAME, + LIBCOBJNI_ENTRY_NAME, + module_errmsg); + if (jinit == NULL) { + /* recheck with libcob */ + jinit = cob_load_lib ("libcob-5", + LIBCOBJNI_ENTRY_NAME, + NULL); + } + if (jinit == NULL) { + /* Error message will be reported in the `cob_call_java` that + should follow. */ + cob_free (java_api); + java_api = NULL; + goto error; + } + jinit (java_api); + return 0; + + error: + cob_runtime_error (_("Java interoperability module cannot be loaded: %s"), + module_errmsg); + return 1; +} + +#endif /* WITH_JNI */ + +cob_java_handle* +cob_resolve_java (const char *class_name, + const char *method_name, + const char *method_signature) { +#if WITH_JNI + if (java_api == NULL && cob_init_java ()) { + return NULL; + } + return java_api->cob_resolve (class_name, method_name, method_signature); +#else + return NULL; +#endif +} + +void +cob_call_java (const cob_java_handle *method_handle) { + if (method_handle == NULL) { + cob_runtime_error (_("Invalid Java method handle: NULL")); + cob_add_exception (COB_EC_ARGUMENT); + return; + } +#if WITH_JNI + if (java_api == NULL && cob_init_java ()) { + return; + } + java_api->cob_call (method_handle); +#else + { + static int first_java = 1; + if (first_java) { + first_java = 0; + cob_runtime_warning (_("runtime is not configured to support %s"), + "JNI"); + } +#if 0 /* TODO: if there is a register in Java-interop, then set it */ + set_json_exception (JSON_INTERNAL_ERROR); +#endif + cob_add_exception (COB_EC_IMP_FEATURE_DISABLED); + } +#endif +} diff --git a/libcob/common.c b/libcob/common.c index 8da537498..575ef4f42 100644 --- a/libcob/common.c +++ b/libcob/common.c @@ -9631,6 +9631,12 @@ print_info_detailed (const int verbose) var_print (_("JSON library"), _("disabled"), "", 0); #endif +#ifdef WITH_JNI + var_print (_("Java interoperability"), _("enabled"), "", 0); +#else + var_print (_("Java interoperability"), _("disabled"), "", 0); +#endif + var_print (_("extended screen I/O"), (char*)&screenio_info, "", 0); var_print (_("mouse support"), mouse_support, "", 0); diff --git a/libcob/common.h b/libcob/common.h index 7ae84641d..d477d2922 100644 --- a/libcob/common.h +++ b/libcob/common.h @@ -1287,6 +1287,18 @@ struct cob_call_struct { cob_call_union cob_cstr_cancel; /* Cancel entry */ }; +/* Java interoperability */ + +typedef struct __cob_java_static_method cob_java_handle; + +/* Indirect Java API struct */ +typedef struct __cob_java_api { + cob_java_handle* (*cob_resolve) (const char *class_name, + const char* method_name, + const char *method_signature); + void (*cob_call) (const cob_java_handle *method_handle); +} cob_java_api; + /* Screen structure */ typedef struct __cob_screen { struct __cob_screen *next; /* Pointer to next */ @@ -2277,6 +2289,11 @@ COB_EXPIMP int cob_get_name_line ( char *prog, int *line ); COB_EXPIMP void cob_runtime_warning_external (const char *, const int, const char *, ...) COB_A_FORMAT34; +COB_EXPIMP cob_java_handle* cob_resolve_java (const char *class_name, + const char* method_name, + const char *type_signature); +COB_EXPIMP void cob_call_java (const cob_java_handle *method_handle); + /*******************************/ /* Functions in screenio.c */ diff --git a/libcob/fileio.c b/libcob/fileio.c index 57ccb075d..31fbf04ba 100644 --- a/libcob/fileio.c +++ b/libcob/fileio.c @@ -314,18 +314,6 @@ static struct cob_fileio_funcs *fileio_funcs[COB_IO_MAX] = { ¬_available_funcs }; -#if defined (__CYGWIN__) -#define LIB_PRF "cyg" -#else -#define LIB_PRF "lib" -#endif - -#if defined(_WIN32) || defined(__CYGWIN__) -#define LIB_SUF "-1." COB_MODULE_EXT -#else -#define LIB_SUF "." COB_MODULE_EXT -#endif - static struct { int loaded; /* Module is loaded and ready */ int config; /* Module was configured into compiler */ @@ -9824,4 +9812,3 @@ cob_fork_fileio (cob_global *lptr, cob_settings *sptr) } } } - diff --git a/libcob/java.c b/libcob/java.c new file mode 100644 index 000000000..026d440cd --- /dev/null +++ b/libcob/java.c @@ -0,0 +1,162 @@ +/* + Copyright (C) 2024 Free Software Foundation, Inc. + Written by Vedant Tewari, Nicolas Berthier, + + 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 + +/* Force symbol exports */ +#define COB_LIB_EXPIMP +#include "config.h" +#include "common.h" +#include "coblocal.h" + +/* Declarations */ +static JavaVM *jvm = NULL; +static JNIEnv *env = NULL; + +typedef struct __cob_java_static_method { + jclass cls; + jmethodID mid; +} cob_java_handle; + +/* Only exported symbol: */ +int cob_jni_init (cob_java_api *api); + +static int /* non-zero means there's an error */ +jvm_load (void) { + /* JDK/JRE 6 VM initialization arguments */ + JavaVMInitArgs args; + JavaVMOption options[1]; + const char *classpath; + char cp_buffer[COB_MEDIUM_BUFF]; + + args.version = JNI_VERSION_1_6; + args.options = options; + args.nOptions = 0; + args.ignoreUnrecognized = JNI_FALSE; + + if ((classpath = getenv ("CLASSPATH")) != NULL) { + snprintf (cp_buffer, COB_MEDIUM_MAX, + "-Djava.class.path=%s", classpath); + options[args.nOptions++].optionString = cp_buffer; + } + + return JNI_CreateJavaVM (&jvm, (void**)&env, &args); +} + +static +cob_java_handle* +resolve_java (const char *class_name, + const char *method_name, + const char *method_signature) { + jclass cls; + jmethodID mid; + cob_java_handle *handle; + + char *jni_class_name = strdup(class_name); + cls = (*env)->FindClass(env, jni_class_name); + cob_free(jni_class_name); + if (!cls) { + cob_runtime_error (_("Java class '%s' not found"), class_name); + cob_set_exception (COB_EC_FUNCTION_NOT_FOUND); + return NULL; + } + + mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); + if (!mid) { + cob_runtime_error (_("Java method '%s' with signature '%s' not found in class '%s'"), + method_name, method_signature, class_name); + (*env)->DeleteLocalRef(env, cls); + cob_set_exception (COB_EC_OO_METHOD); + return NULL; + } + + handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); + if (!handle) { + cob_runtime_error (_("Memory allocation failed for Java method handle")); + (*env)->DeleteLocalRef (env, cls); + cob_set_exception (COB_EC_STORAGE_NOT_AVAIL); + return NULL; + } + + handle->cls = (*env)->NewGlobalRef(env, cls); + handle->mid = mid; + (*env)->DeleteLocalRef(env, cls); + + return handle; +} + +static void +call_java (const cob_java_handle *method_handle) +{ + if (method_handle == NULL) { + return; + } + (*env)->CallStaticVoidMethod(env, + method_handle->cls, + method_handle->mid, NULL); + jthrowable exception = (*env)->ExceptionOccurred(env); + if(exception) { + jclass throwable = (*env)->FindClass(env, "java/lang/Throwable"); + jmethodID getMessage = (*env)->GetMethodID(env, + throwable, "getMessage", "()Ljava/lang/String;"); + if(getMessage != NULL) { + jstring message = (jstring)(*env)->CallObjectMethod(env, exception, getMessage); + const char *messageChars = (*env)->GetStringUTFChars(env, message, NULL); + cob_runtime_error(_("Java exception: %s"), messageChars); + (*env)->ReleaseStringUTFChars(env, message, messageChars); + (*env)->DeleteLocalRef(env, message); + } + jclass stringWriter = (*env)->FindClass(env, "java/io/StringWriter"); + jclass printWriter = (*env)->FindClass(env, "java/io/PrintWriter"); + jobject stringWriterObj = (*env)->NewObject(env, + stringWriter, + (*env)->GetMethodID(env, stringWriter, "", "()V")); + jobject printWriterObj = (*env)->NewObject(env, + printWriter, + (*env)->GetMethodID(env, printWriter, "", "(Ljava/io/Writer;)V"), + stringWriterObj); + jmethodID printStackTrace = (*env)->GetMethodID(env, throwable, "printStackTrace", "(Ljava/io/PrintWriter;)V"); + (*env)->CallVoidMethod(env, exception, printStackTrace, printWriter); + jmethodID toString = (*env)->GetMethodID(env, stringWriter, "toString", "()Ljava/lang/String;"); + jstring stackTrace = (jstring)(*env)->CallObjectMethod(env, stringWriterObj, toString); + const char *stackTraceChars = (*env)->GetStringUTFChars(env, stackTrace, NULL); + cob_runtime_error(_("Java stack trace: %s"), stackTraceChars); + + (*env)->ReleaseStringUTFChars(env, stackTrace, stackTraceChars); + (*env)->DeleteLocalRef(env, stackTrace); + (*env)->DeleteLocalRef(env, stringWriterObj); + (*env)->DeleteLocalRef(env, printWriterObj); + (*env)->DeleteLocalRef(env, exception); + } +} + +/* Entry-point for the module: initializes a Java API structure. */ +int +cob_jni_init (cob_java_api *api) { + if (jvm_load ()) { + return 1; /* Unable to initialize/load the JVM */ + } + api->cob_resolve = resolve_java; + api->cob_call = call_java; + return 0; +} diff --git a/libcob/sysdefines.h b/libcob/sysdefines.h index cefd17c34..a5a0cd9b9 100644 --- a/libcob/sysdefines.h +++ b/libcob/sysdefines.h @@ -243,6 +243,18 @@ #define SLASH_STR "\\" #endif +#if defined (__CYGWIN__) +#define LIB_PRF "cyg" +#else +#define LIB_PRF "lib" +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#define LIB_SUF "-1." COB_MODULE_EXT +#else +#define LIB_SUF "." COB_MODULE_EXT +#endif + #ifdef __DJGPP__ #define HAVE_8DOT3_FILENAMES #endif diff --git a/m4/ax_jni_include_dir.m4 b/m4/ax_jni_include_dir.m4 new file mode 100644 index 000000000..071bb2862 --- /dev/null +++ b/m4/ax_jni_include_dir.m4 @@ -0,0 +1,163 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_jni_include_dir.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_JNI_INCLUDE_DIR +# +# DESCRIPTION +# +# AX_JNI_INCLUDE_DIR finds include directories needed for compiling +# programs using the JNI interface. +# +# JNI include directories are usually in the Java distribution. This is +# deduced from the value of $JAVA_HOME, $JAVAC, or the path to "javac", in +# that order. When this macro completes, a list of directories is left in +# the variable JNI_INCLUDE_DIRS. +# +# Example usage follows: +# +# AX_JNI_INCLUDE_DIR +# +# for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS +# do +# CPPFLAGS="$CPPFLAGS -I$JNI_INCLUDE_DIR" +# done +# +# If you want to force a specific compiler: +# +# - at the configure.in level, set JAVAC=yourcompiler before calling +# AX_JNI_INCLUDE_DIR +# +# - at the configure level, setenv JAVAC +# +# This macro depends on AC_CANONICAL_HOST which requires that config.guess +# and config.sub be distributed along with the source code. See autoconf +# manual for details. +# +# Note: This macro can work with the autoconf M4 macros for Java programs. +# This particular macro is not part of the original set of macros. +# +# LICENSE +# +# Copyright (c) 2008 Don Anderson +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 15 + +AU_ALIAS([AC_JNI_INCLUDE_DIR], [AX_JNI_INCLUDE_DIR]) +AC_DEFUN([AX_JNI_INCLUDE_DIR],[ + +AC_REQUIRE([AC_CANONICAL_HOST]) + +JNI_INCLUDE_DIRS="" + +if test "x$JAVA_HOME" != x; then + _JTOPDIR="$JAVA_HOME" +else + if test "x$JAVAC" = x; then + JAVAC=javac + fi + AC_PATH_PROG([_ACJNI_JAVAC], [$JAVAC], [no]) + if test "x$_ACJNI_JAVAC" = xno; then + AC_MSG_ERROR([cannot find JDK; try setting \$JAVAC or \$JAVA_HOME]) + fi + _ACJNI_FOLLOW_SYMLINKS("$_ACJNI_JAVAC") + _JTOPDIR=`echo "$_ACJNI_FOLLOWED" | sed -e 's://*:/:g' -e 's:/[[^/]]*$::'` +fi + +case "$host_os" in + darwin*) # Apple Java headers are inside the Xcode bundle. + major=$(sw_vers -productVersion | \ + sed -n -e 's/^\(@<:@0-9@:>@*\)..*/\1/p') + _AS_ECHO_LOG([MACOS_MAJOR=$major]) + if @<:@ "$major" -gt 10 @:>@; then + _JINC="$_JTOPDIR/include" + else + minor=$(sw_vers -productVersion | \ + sed -n -e 's/^@<:@0-9@:>@*.\(@<:@0-9@:>@*\).@<:@0-9@:>@*/\1/p') + _AS_ECHO_LOG([MACOS_MINOR=$minor]) + # if test "x$minor" = "x"; then # + # _JINC="$_JTOPDIR/include" + # el + if @<:@ "$minor" -gt "7" @:>@; then + _JTOPDIR="$(xcrun --show-sdk-path)/System/Library/Frameworks/JavaVM.framework" + _JINC="$_JTOPDIR/Headers" + else + _JTOPDIR="/System/Library/Frameworks/JavaVM.framework" + _JINC="$_JTOPDIR/Headers" + fi + fi + ;; + *) _JINC="$_JTOPDIR/include";; +esac +_AS_ECHO_LOG([_JTOPDIR=$_JTOPDIR]) +_AS_ECHO_LOG([_JINC=$_JINC]) + +# On Mac OS X 10.6.4, jni.h is a symlink: +# /System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/jni.h +# -> ../../CurrentJDK/Headers/jni.h. +AC_CACHE_CHECK(jni headers, ac_cv_jni_header_path, +[ + if test -f "$_JINC/jni.h"; then + ac_cv_jni_header_path="$_JINC" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" + else + _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` + if test -f "$_JTOPDIR/include/jni.h"; then + ac_cv_jni_header_path="$_JTOPDIR/include" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" + else + ac_cv_jni_header_path=none + fi + fi +]) + +# get the likely subdirectories for system specific java includes +case "$host_os" in +bsdi*) _JNI_INC_SUBDIRS="bsdos";; +freebsd*) _JNI_INC_SUBDIRS="freebsd";; +darwin*) _JNI_INC_SUBDIRS="darwin";; +linux*) _JNI_INC_SUBDIRS="linux genunix";; +osf*) _JNI_INC_SUBDIRS="alpha";; +solaris*) _JNI_INC_SUBDIRS="solaris";; +mingw*) _JNI_INC_SUBDIRS="win32";; +cygwin*) _JNI_INC_SUBDIRS="win32";; +*) _JNI_INC_SUBDIRS="genunix";; +esac + +if test "x$ac_cv_jni_header_path" != "xnone"; then + # add any subdirectories that are present + for JINCSUBDIR in $_JNI_INC_SUBDIRS + do + if test -d "$_JTOPDIR/include/$JINCSUBDIR"; then + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $_JTOPDIR/include/$JINCSUBDIR" + fi + done +fi +]) + +# _ACJNI_FOLLOW_SYMLINKS +# Follows symbolic links on , +# finally setting variable _ACJNI_FOLLOWED +# ---------------------------------------- +AC_DEFUN([_ACJNI_FOLLOW_SYMLINKS],[ +# find the include directory relative to the javac executable +_cur="$1" +while ls -ld "$_cur" 2>/dev/null | grep " -> " >/dev/null; do + AC_MSG_CHECKING([symlink for $_cur]) + _slink=`ls -ld "$_cur" | sed 's/.* -> //'` + case "$_slink" in + /*) _cur="$_slink";; + # 'X' avoids triggering unwanted echo options. + *) _cur=`echo "X$_cur" | sed -e 's/^X//' -e 's:[[^/]]*$::'`"$_slink";; + esac + AC_MSG_RESULT([$_cur]) +done +_ACJNI_FOLLOWED="$_cur" +])# _ACJNI diff --git a/m4/ax_prog.javac.m4 b/m4/ax_prog.javac.m4 new file mode 100644 index 000000000..8abb733fc --- /dev/null +++ b/m4/ax_prog.javac.m4 @@ -0,0 +1,79 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_javac.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVAC +# +# DESCRIPTION +# +# AX_PROG_JAVAC tests an existing Java compiler. It uses the environment +# variable JAVAC then tests in sequence various common Java compilers. For +# political reasons, it starts with the free ones. +# +# If you want to force a specific compiler: +# +# - at the configure.in level, set JAVAC=yourcompiler before calling +# AX_PROG_JAVAC +# +# - at the configure level, setenv JAVAC +# +# You can use the JAVAC variable in your Makefile.in, with @JAVAC@. +# +# *Warning*: its success or failure can depend on a proper setting of the +# CLASSPATH env. variable. +# +# TODO: allow to exclude compilers (rationale: most Java programs cannot +# compile with some compilers like guavac). +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AU_ALIAS([AC_PROG_JAVAC], [AX_PROG_JAVAC]) +AC_DEFUN([AX_PROG_JAVAC],[ +m4_define([m4_ax_prog_javac_list],["gcj -C" guavac jikes javac])dnl +AS_IF([test "x$JAVAPREFIX" = x], + [test "x$JAVAC" = x && AC_CHECK_PROGS([JAVAC], [m4_ax_prog_javac_list])], + [test "x$JAVAC" = x && AC_CHECK_PROGS([JAVAC], [m4_ax_prog_javac_list], [], [$JAVAPREFIX/bin])]) +m4_undefine([m4_ax_prog_javac_list])dnl +test "x$JAVAC" = x && AC_MSG_ERROR([no acceptable Java compiler found in \$PATH]) +AX_PROG_JAVAC_WORKS +AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_java.m4 b/m4/ax_prog_java.m4 new file mode 100644 index 000000000..c2e6964e2 --- /dev/null +++ b/m4/ax_prog_java.m4 @@ -0,0 +1,115 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_java.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVA +# +# DESCRIPTION +# +# Here is a summary of the main macros: +# +# AX_PROG_JAVAC: finds a Java compiler. +# +# AX_PROG_JAVA: finds a Java virtual machine. +# +# AX_CHECK_CLASS: finds if we have the given class (beware of CLASSPATH!). +# +# AX_CHECK_RQRD_CLASS: finds if we have the given class and stops +# otherwise. +# +# AX_TRY_COMPILE_JAVA: attempt to compile user given source. +# +# AX_TRY_RUN_JAVA: attempt to compile and run user given source. +# +# AX_JAVA_OPTIONS: adds Java configure options. +# +# AX_PROG_JAVA tests an existing Java virtual machine. It uses the +# environment variable JAVA then tests in sequence various common Java +# virtual machines. For political reasons, it starts with the free ones. +# You *must* call [AX_PROG_JAVAC] before. +# +# If you want to force a specific VM: +# +# - at the configure.in level, set JAVA=yourvm before calling AX_PROG_JAVA +# +# (but after AC_INIT) +# +# - at the configure level, setenv JAVA +# +# You can use the JAVA variable in your Makefile.in, with @JAVA@. +# +# *Warning*: its success or failure can depend on a proper setting of the +# CLASSPATH env. variable. +# +# TODO: allow to exclude virtual machines (rationale: most Java programs +# cannot run with some VM like kaffe). +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. +# +# A Web page, with a link to the latest CVS snapshot is at +# . +# +# This is a sample configure.in Process this file with autoconf to produce +# a configure script. +# +# AC_INIT(UnTag.java) +# +# dnl Checks for programs. +# AC_CHECK_CLASSPATH +# AX_PROG_JAVAC +# AX_PROG_JAVA +# +# dnl Checks for classes +# AX_CHECK_RQRD_CLASS(org.xml.sax.Parser) +# AX_CHECK_RQRD_CLASS(com.jclark.xml.sax.Driver) +# +# AC_OUTPUT(Makefile) +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 10 + +AU_ALIAS([AC_PROG_JAVA], [AX_PROG_JAVA]) +AC_DEFUN([AX_PROG_JAVA],[ +m4_define([m4_ax_prog_java_list], [kaffe java])dnl +AS_IF([test "x$JAVAPREFIX" = x], + [test x$JAVA = x && AC_CHECK_PROGS([JAVA], [m4_ax_prog_java_list])], + [test x$JAVA = x && AC_CHECK_PROGS([JAVA], [m4_ax_prog_java_list], [], [$JAVAPREFIX/bin])]) +test x$JAVA = x && AC_MSG_ERROR([no acceptable Java virtual machine found in \$PATH]) +m4_undefine([m4_ax_prog_java_list])dnl +AX_PROG_JAVA_WORKS +AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_java_works.m4 b/m4/ax_prog_java_works.m4 new file mode 100644 index 000000000..bc7052619 --- /dev/null +++ b/m4/ax_prog_java_works.m4 @@ -0,0 +1,91 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_java_works.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVA_WORKS +# +# DESCRIPTION +# +# Internal use ONLY. +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 11 + +AU_ALIAS([AC_PROG_JAVA_WORKS], [AX_PROG_JAVA_WORKS]) +AC_DEFUN([AX_PROG_JAVA_WORKS], [ + if test x$ac_cv_prog_javac_works = xno; then + AC_MSG_ERROR([Cannot compile java source. $JAVAC does not work properly]) + fi + if test x$ac_cv_prog_javac_works = x; then + AX_PROG_JAVAC + fi +AC_CACHE_CHECK(if $JAVA works, ac_cv_prog_java_works, [ +JAVA_TEST=Test.java +CLASS_TEST=Test.class +TEST=Test +changequote(, )dnl +cat << \EOF > $JAVA_TEST +/* [#]line __oline__ "configure" */ +public class Test { +public static void main (String args[]) { + System.exit (0); +} } +EOF +changequote([, ])dnl + if AC_TRY_COMMAND($JAVAC $JAVACFLAGS $JAVA_TEST) && test -s $CLASS_TEST; then + : + else + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD + AC_MSG_ERROR(The Java compiler $JAVAC failed (see config.log, check the CLASSPATH?)) + fi +if AC_TRY_COMMAND($JAVA -classpath . $JAVAFLAGS $TEST) >/dev/null 2>&1; then + ac_cv_prog_java_works=yes +else + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD + AC_MSG_ERROR(The Java VM $JAVA failed (see config.log, check the CLASSPATH?)) +fi +rm -f $JAVA_TEST $CLASS_TEST +]) +AC_PROVIDE([$0])dnl +] +) diff --git a/m4/ax_prog_javac_works.m4 b/m4/ax_prog_javac_works.m4 new file mode 100644 index 000000000..9b48149d8 --- /dev/null +++ b/m4/ax_prog_javac_works.m4 @@ -0,0 +1,72 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_javac_works.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVAC_WORKS +# +# DESCRIPTION +# +# Internal use ONLY. +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 7 + +AU_ALIAS([AC_PROG_JAVAC_WORKS], [AX_PROG_JAVAC_WORKS]) +AC_DEFUN([AX_PROG_JAVAC_WORKS],[ +AC_CACHE_CHECK([if $JAVAC works], ac_cv_prog_javac_works, [ +JAVA_TEST=Test.java +CLASS_TEST=Test.class +cat << \EOF > $JAVA_TEST +/* [#]line __oline__ "configure" */ +public class Test { +} +EOF +if AC_TRY_COMMAND($JAVAC $JAVACFLAGS $JAVA_TEST) >/dev/null 2>&1; then + ac_cv_prog_javac_works=yes +else + AC_MSG_ERROR([The Java compiler $JAVAC failed (see config.log, check the CLASSPATH?)]) + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD +fi +rm -f $JAVA_TEST $CLASS_TEST +]) +AC_PROVIDE([$0])dnl +]) diff --git a/tests/ChangeLog b/tests/ChangeLog index a92686469..a650692db 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,4 +1,8 @@ +2024-08-14 Nicolas Berthier + + * testsuite.src/run_java.at: new testsuite for Java interoperability + 2023-01-21 Simon Sobisch * atlocal.in: prefer config.status replacement over environment var diff --git a/tests/Makefile.am b/tests/Makefile.am index 3b5549231..79e88d15e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -51,6 +51,7 @@ testsuite_sources = \ testsuite.src/run_functions.at \ testsuite.src/run_fundamental.at \ testsuite.src/run_initialize.at \ + testsuite.src/run_java.at \ testsuite.src/run_misc.at \ testsuite.src/run_ml.at \ testsuite.src/run_refmod.at \ diff --git a/tests/atlocal.in b/tests/atlocal.in index ef679d979..079a4903c 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -58,6 +58,8 @@ GREP=@GREP@ SED=@SED@ export AWK GREP SED +JAVAC=@JAVAC@ + # be sure to use the English messages LC_ALL=C export LC_ALL @@ -218,6 +220,7 @@ if test "$GNUCOBOL_TEST_LOCAL" != "1"; then COB_HAS_XML2="@COB_HAS_XML2@" COB_HAS_JSON="@COB_HAS_JSON@" COB_HAS_CURSES="@COB_HAS_CURSES@" + COB_HAS_JNI="@COB_HAS_JNI@" else COB_OBJECT_EXT="$(grep COB_OBJECT_EXT info.out | cut -d: -f2 | cut -b2-)" @@ -256,6 +259,11 @@ else else COB_HAS_JSON="no" fi + if test $(grep -i -c "Java interoperability.*disabled" info.out) = 0; then + COB_HAS_JNI="yes" + else + COB_HAS_JNI="no" + fi # see note below if test $(grep -i -c " screen .*disabled" info.out) = 0; then COB_HAS_CURSES="yes" diff --git a/tests/testsuite.at b/tests/testsuite.at index 12a44f76a..977162d0c 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -72,6 +72,7 @@ m4_include([run_returncode.at]) m4_include([run_functions.at]) # 15 Intrinsic Functions / 9.4 User-Defined Functions m4_include([run_extensions.at]) m4_include([run_ml.at]) +m4_include([run_java.at]) ## Data Representation AT_BANNER([Data Representation]) diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at new file mode 100644 index 000000000..194382550 --- /dev/null +++ b/tests/testsuite.src/run_java.at @@ -0,0 +1,154 @@ +## Copyright (C) 2024 Free Software Foundation, Inc. +## Written by Nicolas Berthier, Vedant Tewari +## +## This file is part of GnuCOBOL. +## +## The GnuCOBOL compiler is free software: you can redistribute it +## and/or modify it under the terms of the GNU 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with GnuCOBOL. If not, see . + +### GnuCOBOL Test Suite + +### Java interoperability tests + +AT_SETUP([CALL Java static void (void)]) +AT_KEYWORDS([extensions jni]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([Test.java], [ +public class Test { + public static void printHelloWorld () { + System.out.println ("Hello world!"); + } +} +]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.printHelloWorld" + NOT ON EXCEPTION + DISPLAY "Java call worked" + END-CALL + STOP RUN. +]) + +AT_CHECK([$JAVAC Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], +[Hello world! +Java call worked +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) (missing class)]) +AT_KEYWORDS([extensions jni]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.isAMissingClass" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], +[libcob: prog.cob:5: error: Java class 'Test' not found +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) (missing method)]) +AT_KEYWORDS([extensions jni]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([Test.java], [ +public class Test {} +]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.missingMethod" + STOP RUN. +]) + +AT_CHECK([$JAVAC Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], +[libcob: prog.cob:5: error: Java method 'missingMethod' with signature '()V' not found in class 'Test' +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) (missing class with ON EXCEPTION)]) +AT_KEYWORDS([extensions jni exception]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.isAMissingClass" + ON EXCEPTION + DISPLAY "java call not successful" + END-CALL + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], [], [java call not successful +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) in a package]) +AT_KEYWORDS([extensions jni package]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_CHECK([mkdir -p testpackage]) +AT_DATA([testpackage/Test.java], [ +package testpackage; +public class Test { + public static void printHelloPackage () { + System.out.println("Hello from package!"); + } +} +]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.testpackage.Test.printHelloPackage" + NOT ON EXCEPTION + DISPLAY "Java call to package class worked" + END-CALL + STOP RUN. +]) + +AT_CHECK([$JAVAC testpackage/Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], +[Hello from package! +Java call to package class worked +]) + +AT_CLEANUP diff --git a/tests/testsuite.src/syn_misc.at b/tests/testsuite.src/syn_misc.at index 7ab38951d..60c9db68b 100644 --- a/tests/testsuite.src/syn_misc.at +++ b/tests/testsuite.src/syn_misc.at @@ -9531,3 +9531,19 @@ pgm1.cob:18: error: start of statement in Area A AT_CLEANUP + +AT_SETUP([CALL Java with malformed method name]) +AT_KEYWORDS([extensions jni malformed]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.InvalidName" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [1], [], +[prog.cob:5: error: malformed Java method name 'Java.InvalidName', expected format 'Java.ClassName.methodName' +]) +AT_CLEANUP