Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Handling of Non-Void Java Method Calls in GnuCOBOL #174

Open
wants to merge 8 commits into
base: java-interop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@

2024-07-12 Vedant Tewari <[email protected]>

2023-02-25 Ron Norman <[email protected]>
* ax_prog_java.m4: Added macro for jni check
* ax_jni_include_dir.m4: Added macro for jni check
* configure.ac: added support for Java interoperability through JNI

2023-02-25 Ron Norman <[email protected]>

* configure.ac: Add check for sys/time.h

Expand Down
15 changes: 15 additions & 0 deletions DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ NEWS - user visible changes -*- outline -*-


* New GnuCOBOL features

** Initial support for Java interoperability through JNI (new optional dependency JDK)
** file handling: added backends for ODBC (so far PostgrSQL, MySQL, SQLite,
MSSQL) and OCI, along with new directory COB_SCHEMA_DIR containing the
necessary internal schema files to match the file definition to the
Expand Down
4 changes: 4 additions & 0 deletions cobc/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

2024-08-14 Nicolas Berthier <[email protected]>

* cobc.c (cobc_print_info): added note for Java interoperability

2024-08-04 David Declerck <[email protected]>

Adjustments to merge 2022-12-21:
Expand Down
6 changes: 6 additions & 0 deletions cobc/cobc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
186 changes: 185 additions & 1 deletion cobc/codegen.c
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -120,6 +120,7 @@ struct field_list {
struct call_list {
struct call_list *next;
const char *call_name;
const char *method_sig;
};

#define COB_RETURN_INT 0
Expand Down Expand Up @@ -147,6 +148,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;
Expand Down Expand Up @@ -395,6 +397,23 @@ lookup_source (const char *p)
return source_id++;
}

static void
lookup_java_call(const char *p, const char *signature)
{
struct call_list *clp;

for (clp = call_java_cache; clp; clp = clp->next) {
if (strcmp (p, clp->call_name) == 0 && strcmp(signature, clp->method_sig) == 0) {
return;
}
}
clp = cobc_parse_malloc (sizeof (struct call_list));
clp->call_name = p;
clp->method_sig = signature;
clp->next = call_java_cache;
call_java_cache = clp;
}

static void
lookup_call (const char *p)
{
Expand Down Expand Up @@ -1978,6 +1997,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",
Expand Down Expand Up @@ -7068,6 +7092,161 @@ output_field_constant (cb_tree x, int n, const char *flagname)
output_newline ();
}

static void
output_java_call (struct cb_call *p)
{
char* full_name = (char *)CB_LITERAL(p->name)->data; /* Assume java.prefix (enforced in `parser.y`, rule `call_body`)*/
char* class_and_method_name = full_name + 5;
char *last_dot;
char *method_name;
const char *class_name;
char return_type_signature[32];
char method_signature[2048] = "(";
char* mangled;
struct cb_tree_common *ptr;

mangled = strdup(class_and_method_name);
for (size_t i = 0; i < strlen(mangled) + 1; i++) {
mangled[i] = (mangled[i] == '.') ? '_' : mangled[i];
}

last_dot = strrchr(class_and_method_name, '.');
if (last_dot == NULL) {
cobc_err_msg (_("malformed call '%s' to a Java method"), class_and_method_name);
return;
}

*last_dot = '\0';
method_name = last_dot + 1;
class_name = class_and_method_name;

for (int i = 0; (ptr = ((struct cb_tree_common **)p->args)[i]) != NULL; i++) {
switch(CB_TREE_TAG(ptr)) {
case CB_TAG_INTEGER:
strcat(method_signature, "I");
break;
case CB_USAGE_FLOAT:
strcat(method_signature, "F");
break;
case CB_USAGE_DOUBLE:
strcat(method_signature, "D");
break;
case CB_CLASS_BOOLEAN:
strcat(method_signature, "Z");
break;
case CB_TAG_STRING:
strcat(method_signature, "Ljava/lang/String;");
break;
case CB_USAGE_OBJECT:
strcat(method_signature, "Ljava/lang/Object;");
break;
case CB_TAG_LITERAL:
if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_NUMERIC) {
strcat(method_signature, "I");
} else if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_ALPHANUMERIC) {
strcat(method_signature, "Ljava/lang/String;");
}
break;
case CB_TAG_LIST:
{
struct cb_tree_common **list_elements = (struct cb_tree_common **) ptr;
int array_dimension = 1;

while (list_elements != NULL) {
struct cb_tree_common **inner_list = NULL;
for (int j = 0; list_elements[j] != NULL; j++) {
if (CB_TREE_TAG(list_elements[j]) == CB_TAG_LIST) {
array_dimension++;
inner_list = (struct cb_tree_common **) list_elements[j];
} else {
switch (CB_TREE_TAG(list_elements[j])) {
case CB_TAG_INTEGER:
strcat(method_signature, "[I");
break;
Copy link
Collaborator

@GitMensch GitMensch Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of integer we further have to check the type per usage

pic/type java type
PIC S9(n) COMP-5, where ≥ 1 n ≤ 4 short
PIC S9(n) COMP-5, where ≥ 5 n ≤ 9 int
PIC S9(n) COMP-5, where ≥ 10 n ≤ 18 long
usage COMP-3/PACKED-DECIMAL/DISPLAY numeric java.math.BigDecimal

Note that checking the TAGs and USAGEs and CLASSes in a single switch doesn't work (check with the debugger to get more details).

Also note that PIC X (=single byte) is a special case:

  • if it has two or one level88 below it with value x'01' / x'00' --> boolean
  • otherwise: byte

For all of the types you have (after addition of the above) there needs to be a check for the occurs attribute ("more than 1 dimension" will already be checked in the conformance checks) and if existing just add [].

As the valid types are identical for both arguments and returning items, their generation should be moved out to a static helper function called for each of the parameters (the loop here) and the returning item below.

case CB_USAGE_FLOAT:
strcat(method_signature, "[F");
break;
case CB_USAGE_DOUBLE:
strcat(method_signature, "[D");
break;
case CB_CLASS_BOOLEAN:
strcat(method_signature, "[Z");
break;
case CB_TAG_STRING:
strcat(method_signature, "[Ljava/lang/String;");
break;
case CB_USAGE_OBJECT:
strcat(method_signature, "[Ljava/lang/Object;");
break;
default:
cobc_err_msg(_("Unsupported array element type in Java method call"));
COBC_ABORT();
}
}
}
list_elements = inner_list;
}

if (array_dimension > 2) {
cobc_err_msg(_("Unsupported array dimension: %d"), array_dimension);
COBC_ABORT();
Copy link
Collaborator

@GitMensch GitMensch Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those two messages should be errors in cb_check_conformance as well as a check for "only field identifiers" (= no literals/functions/...) - which then also allows above to directly cast to a field pointer and operate on this; additional, for parameters passed BY REFERENCE that are a String/BigDecimal mapping item there should be a warning "immutable type implicit passed BY CONTENT".

}
}
break;
default:
cobc_err_msg(_("Unsupported argument type in Java method call"));
COBC_ABORT();
}
}

if (p->call_returning == NULL) {
strcat(method_signature, ")V");
strcpy(return_type_signature, "void");
} else {
switch(CB_TREE_TAG(p->call_returning)) {
case CB_TAG_INTEGER:
strcat(method_signature, ")I");
strcpy(return_type_signature, "jint");
break;
case CB_TAG_STRING:
strcat(method_signature, ")Ljava/lang/String;");
strcpy(return_type_signature, "jstring");
break;
case CB_USAGE_FLOAT:
strcat(method_signature, ")F");
strcpy(return_type_signature, "jfloat");
break;
case CB_USAGE_DOUBLE:
strcat(method_signature, ")D");
strcpy(return_type_signature, "jdouble");
break;
case CB_CLASS_BOOLEAN:
strcat(method_signature, ")Z");
strcpy(return_type_signature, "jboolean");
break;
default:
strcat(method_signature, ")V");
strcpy(return_type_signature, "void");
break;
}
}

strcat(method_signature, ")V");

lookup_java_call(mangled, method_signature);
output_line("if (call_java_%s == NULL)", mangled);
output_block_open();

output_prefix();
output_line("call_java_%s = ", mangled);
output("cob_resolve_java(\"%s\", \"%s\", \"%s\", \"()V\");", class_name, method_name, method_signature);
output_newline ();
output_prefix ();
output_line("cob_call_java(call_java_%s);\n", mangled);
output_newline();
output_block_close();
}

static void
output_call (struct cb_call *p)
{
Expand Down Expand Up @@ -7097,6 +7276,11 @@ output_call (struct cb_call *p)
}
system_call = NULL;

if (p->convention & CB_CONV_JAVA) {
output_java_call(p);
return;
}

#ifdef _WIN32
if (p->convention & CB_CONV_STDCALL) {
convention = "_std";
Expand Down
5 changes: 5 additions & 0 deletions cobc/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -12253,6 +12253,11 @@ call_body:

/* Check parameter conformance, if we can work out what is being called. */
if (CB_LITERAL_P ($3)) {
/* Check for "Java." prefix and set call convention */
char* s = (char *)CB_LITERAL ($3)->data;
if (strncasecmp("Java.", s, 5) == 0) {
call_conv = CB_CONV_JAVA;
}
cb_check_conformance ($3, $7, $8);
} else if (CB_REFERENCE_P ($3)) {
cb_tree ref = cb_ref ($3);
Expand Down
1 change: 1 addition & 0 deletions cobc/tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading