Appendix B Mixing RPG And Java

Overview

This appendix is a brief tour of the new capabilities added to RPG IV as of V5R1, which makes it easier to use Java Native Interface to call Java from RPG and to call RPG from Java. Appendix A discusses how to use the AS/400 Toolbox for Java to call RPG from Java, using either hand-crafted code to the ProgramCall and ServiceProgramCall classes or using the Program Call Markup Language (PCML).

While the Toolbox remains the option of choice for calling RPG from Java for many people, there is now an alternative. Specifically, the Toolbox calls RPG programs and service programs in a separate job from that running the Java Virtual Machine. The advantage of this is that there are no worries about thread safety, and it is a tried and trusted application architecture. The alternative is to use Java Native Interface, as Chapter 14 hints at, to call ILE procedures inside service programs, within the same job as the Java Virtual Machine. The advantage to this approach is strictly performance, as it saves the overhead of starting that second job. If your service program does not turn on LR, then the advantage is perhaps minor. The downside of JNI for RPG calls has always been coding complexity, but as of V5R1, RPG has been enhanced to reduce this complexity.

On the other hand, if your goal is to call Java from RPG, you really are tied to JNI. Remember, JNI comes with C APIs for starting the JVM, instantiating objects, calling methods on those objects, and translating data between compiled languages and Java. Thus, it can be used to go either way. The historical problem with using JNI to call into Java has always been that of complexity, which is increased when using RPG versus C. Once again, however, the enhancements in V5R1 of RPG IV have significantly reduced this complexity, hiding much of it behind new RPG-native syntax.

One alternative to using JNI to call Java from RPG is to launch the Java application by calling the JAVA CL command from the RPG program, and then use a data queue or data area to communicate between the two programs. Another alternative is to use an embedded SQL call to invoke the Java class as a stored procedure, leveraging the database. Both come with their own advantages and disadvantages, but this appendix focuses exclusively on the new RPG IV built-in support to call Java directly via Java Native Interface.



RPG Calling JAVA

To call Java from RPG, the following enhancements needed to be added to the RPG language syntax:

You might also expect the ability to access public variables in an object, but this support was not added to the RPG language. If you need to access a variable, you must create a Java class with methods that return the variables you are interested in, and optionally create methods to set them, if you want write access.

The enhancements added to V5R1 of RPG IV to enable calling of Java includes the following:

  1. A new object data type, o, holds references to instantiated Java objects. Any field defined to be of type o must also specify the new CLASS keyword on the D-spec to identify the object's class type. The syntax for CLASS is CLASS(*JAVA:'package.class'), where both parameters are required. The first parameter, *JAVA, identifies this as a Java object and allows support for non-Java objects in the future. The second parameter is a character literal or constant identifying the package-qualified class name for this object. This name is case-sensitive, just as in Java.
  2. An extension of the existing procedure prototyping and calling syntax allows prototyping and calling Java methods as though they were regular RPG procedures. This involves new syntax for the EXTPROC keyword specified on a D-spec prototype, to identify the class that contains the method: EXTPROC(*JAVA:'package.class':'method').

    The first parameter, *JAVA, again identifies this as a prototype for a Java method. The second parameter is a character literal or constant field that identifies the package-qualified class that contains the method. The third parameter is a character literal or constant field that identifies the name of the method being prototyped. Both the second and third parameters are case-sensitive. When you prototype a method, use a D-spec with PR in positions 24 and 25, exactly as you do when prototyping a procedure. On the PR spec, specify the EXTPROC keyword, and also specify the return type of the method, if it returns something. The sub-sequent D-specs that have blanks in columns 24 and 25 identify the parameters of the method, again just as with regular RPG procedure prototypes. For the return type and the parameters, if the data type is a primitive, use the corresponding native RPG data type, as shown in Table B.1. You must also specify the VALUE keyword on the parameter D-specs, as all Java primitives are passed by value. If the return type or parameter type is an object, use the new o data type and specify the CLASS keyword in the keyword area of the D-spec. This keyword has the syntax described in the previous step.

  3. Once you have a method prototyped, and you have created an object instance of the class containing the method (described in step 4), you can call the method the same way you call any RPG procedure. This is an elegant mapping of RPG's syntax to Java. Use the CALLP op-code to call a method if you do not care about the returned value, or the EVAL op-code to assign the returned value to an RPG field, or call the method as part of an expression just as with RPG procedures, by using the method name in place of a field name. If the method takes parameters, place them inside parentheses and use a colon to separate them. The only tricky part is identifying the object upon which the method is being invoked. This is done by specifying the object's reference field (type o) as the first parameter. Thus, even methods that do not take parameters will always have a least one parameter in the call to them. While the target object is required as the first parameter in the call, you do not prototype it.
  4. A special flavor of the method prototyping syntax is needed for prototyping constructors. To instantiate objects, simply prototype the constructor as though it were a method, and call it as you call any method. This "method" returns a reference to the instantiated object, just as though you had used the new operator in Java. Indeed, RPG is doing this for you under the covers. Because these constructor methods return objects, you must specify the CLASS keyword on a constructor prototype, using the syntax described in step 1. Further, you must tell RPG that this is a constructor method, so that it knows to call the new operator. Do this by specifying *CONSTRUCTOR for the method name, in the third parameter of the CLASS keyword. If the constructor takes parameters, simply prototype them exactly as you prototype parameters to regular Java methods.
  5. If you wish to call a static method, it is very easy. You do not need to define an object reference field to hold an object, you do not need to prototype the constructor, you do not need to call the constructor to instantiate an object, and you do not need to specify an object as the first parameter to the method call. You do still need to prototype the method as described in step 3, and you must specify an additional keyword, STATIC, to tell RPG this is a static method. Once prototyped, you can directly call a static method using the usual procedure-call syntax, but you do not need to specify an object field as the first parameter.

Prior to discussing the important topic of data type mapping, let's see an example of this new syntax. Imagine you want to use a Vector object from within RPG. You want to instantiate an instance of Vector and access the methods addElement, size, and elementAt within that object. To keep the example simple, instantiate and store two Integer objects in the vector. Then walk the vector, extracting each Integer object and converting it first to a String object and then to an RPG character field for the purposes of displaying its value on the console.

This example requires the following fields:

This example also requires the following prototypes:

Once the fields are declared and the constructors and methods prototyped, you can write RPG logic to do the following:

Start with the field declarations and method prototypes, as shown in Listing B.1. This declares prototypes for two Java constructors and five methods. You do not need to prototype the String constructor because you will not be instantiating a String object directly. Instead, you will get a String object back from a call to the toString method of the Integer class.

Listing B.1: Declaring Object Fields and Java Method Prototypes

D* Declare fields to hold Vector and Integer objects... D VectorObj S O CLASS(*JAVA : 'java.util.Vector') D IntegerObj1 S O CLASS(*JAVA : 'java.lang.Integer') D IntegerObj2 S O CLASS(*JAVA : 'java.lang.Integer') D* Declare field to hold a String object... D StringObj S O CLASS(*JAVA : 'java.lang.String') D* Declare primitive fields to hold int values... D IntegerField S 10I 0 INZ D Idx S 10I 0 INZ D VectorSizeFld S 10I 0 INZ D* Declare primitive field to hold RPG version of String values... D StringField S 10A VARYING D*----------------------------------------------------------------------- D* D* Declare Vector constructor prototype... D VectorCtor PR O EXTPROC(*JAVA : 'java.util.Vector' : D *CONSTRUCTOR) D CLASS(*JAVA : 'java.util.Vector' ) D* Declare Integer constructor prototype... D IntegerCtor PR O EXTPROC(*JAVA : 'java.lang.Integer' D : *CONSTRUCTOR) D CLASS(*JAVA : 'java.lang.Integer' ) D** Parameter prototype declaration for Java type: int D IntegerCtorParm1... D 10I 0 VALUE D*----------------------------------------------------------------------- D* D* Prototype for addElement method in Vector class... D VectorAddElementMethod... D PR EXTPROC(*JAVA : 'java.util.Vector' : D 'addElement') D** Parameter prototype declaration for Java type: java.lang.Object D addElementParm1... D O CLASS(*JAVA : 'java.lang.Object' ) D* Prototype for size method in Vector class D VectorSizeMethod... D PR 10I 0 EXTPROC(*JAVA : 'java.util.Vector' : D 'size') D* Prototype for elementAt method in Vector class... D VectorElementAtMethod... D PR O EXTPROC(*JAVA : 'java.util.Vector' : D 'elementAt') D CLASS(*JAVA : 'java.lang.Object' ) D** Parameter prototype declaration for Java type: int D elementAtParm1... D 10I 0 VALUE D* Prototype for toString method in Integer class... D IntegerToStringMethod... D PR O EXTPROC(*JAVA : 'java.lang.Integer' D : 'toString') D CLASS(*JAVA : 'java.lang.String' ) D* Prototype for getBytes method in String class... D StringGetBytesMethod... D PR 10A EXTPROC(*JAVA : 'java.lang.String' D : 'getBytes') D VARYING

The hard part is prototyping the constructor and method calls, and declaring fields to hold object references. Once that is done, simply write code to call the constructors and methods as though they were procedures written in RPG. The mainline code to do this is shown in Listing B.2.

Listing B.2: Instantiating, Populating, and Traversing a Java Vector from RPG IV

C* Instantiate Vector object... C EVAL VectorObj = VectorCtor() C* Instantiate first Integer object, with 10 for the value... C EVAL IntegerField = 10 C EVAL IntegerObj1 = IntegerCtor(IntegerField) C* Instantiate second Integer object, with 20 for the value... C EVAL IntegerField = 20 C EVAL IntegerObj2 = IntegerCtor(IntegerField) C* Add the two Integer objects to the Vector object... C CALLP VectorAddElementMethod(VectorObj:IntegerObj1) C CALLP VectorAddElementMethod(VectorObj:IntegerObj2) C* Walk all elements of the Vector object, displaying each element... C EVAL VectorSizeFld = VectorSizeMethod(VectorObj) C FOR Idx = 0 to (VectorSizeFld-1) C** Retrieve Integer object using elementAt method of Vector... C EVAL IntegerObj1 = C VectorElementAtMethod(VectorObj:Idx) C** Convert Integer object into String object using toString method... C EVAL StringObj = C IntegerToStringMethod(IntegerObj1) C** Convert String object into RPG character field using getBytes method C EVAL StringField = C StringGetBytesMethod(StringObj) C** Display converted String field on console... C StringField DSPLY C* End FOR loop C ENDFOR C* Exit C EVAL *INLR = *ON

This code starts by calling the Vector constructor method to instantiate a Vector object. It next calls the Integer constructor method twice, to instantiate two integer objects. It passes an RPG integer field as a parameter to the constructor, first with the number 10 and then the number 20. It next calls the addElement method of the Vector object twice to add each Integer object to that Vector object. (For non-constructor and non-static method calls, you must pass the target object as the first parameter, even though you do not prototype this first parameter. It is implicit, and simply a 3GL alternative to Java's "dot" operator.)

An RPG FOR loop visits each element in the Vector object, from zero to the size minus one, as the Vector class uses zero-based element access. The code calls the size method of the Vector object to get the element count, and stores the result in an RPG field of type integer. Each iteration of the loop first calls the elementAt method to retrieve a reference to the Integer object at that index, and then calls the toString method of the Integer object to convert the Integer to a String object, to which it is given a reference. This String object is converted to an RPG character field by calling the toBytes method on the String object, which returns a byte array of individual characters. When you assign a Java byte array to an RPG character field, RPG takes care of converting the data to an EBCDIC RPG character field. Finally, this value is displayed to the console. The result of running this program is the values 10 and 20, as you would expect.

This example illustrates how to deal with casting Java objects when calling Java from RPG. The methods in the Vector class accept and return objects of type java.lang.Object, as shown in the prototypes of the addElement and elementAt methods. However, the code passed an object of type java.lang.Integer when calling the addElement method, and assigned the result of calling elementAt to an object field of type java.lang.Integer. This is legal in Java, but the assignment would require you to cast the result, using java.lang.Integer. There is no such syntax in RPG because the casting is done for you implicitly.

The code for accessing Java from RPG can be a bit tedious to write, due to the requirement to prototype all the constructors and methods and fully describe the class types of object fields. To help with this drudgery, a wizard in the CODE editor will generate these declarations for you, given the package, class, and method you wish to call. Another wizard in CODE converts RPG IV fixed-form logic to the new free-form style. Listing B.3 shows the logic from Listing B.2 in free-form style.

Listing B.3: The Logic from Listing B.2 in Free-Form RPG style

// Insttantiate Vector object... /FREE VectorObj = VectorCtor(); // Instantiae first Integer object, with 10 for the value... IntegerField = 10; IntegerObj1 = IntegerCtor(IntegerField); // Instantiate second Integer object, with 20 for the value... IntegerField = 20; IntegerObj2 = IntegerCtor(IntegerField); // Add the two Integer objects to the Vector object... VectorAddElementMethod(VectorObj:IntegerObj1); VectorAddElementMethod(VectorObj:IntegerObj2); // Walk all elements of the Vector object, displaying each element... VectorSizeFld = VectorSizeMethod(VectorObj); FOR Idx = 0 to (VectorSizeFld-1); //* Retrieve Integer object using elementAt method of Vector... IntegerObj1 = VectorElementAtMethod(VectorObj:Idx); //* Convert Integer object into String object using toString method... StringObj = IntegerToStringMethod(IntegerObj1); //* Convert String object into RPG character field using getBytes method StringField = StringGetBytesMethod(StringObj); //* Display converted String field on console... DSPLY StringField; // End FOR loop ENDFOR; // Exit *INLR = *ON; /END-FREE

Mapping data types

When prototyping method and constructor calls, you specify RPG data types for the parameters and the return type, not Java data types. For each of the eight primitive data types in Java, there is a corresponding RPG data type you should use, and the RPG runtime takes care of the data mapping between the two languages. For objects, use the object o data type together with the CLASS keyword to specify the class type of the object. For arrays, use RPG arrays of fields with the appropriate type for each element. Table B.1 shows the mappings from each of the Java types to their corresponding RPG types.

Table B.1: Data Type Mapping between RPG and Java

Java Type

RPG Type

Comment

Boolean: boolean

Indicator: N

True/false versus one/zero.

Byte: byte

Integer: 3I 0 Character: 1A

If using byte to hold a numeric value, use a three-digit integer in RPG. Otherwise, use a one-digit alpha.

Byte array: byte[]

Character length > 1: nA

Array of char length=1: 1A DIM

Date: D

Time: T

Timestamp: Z

An array of bytes can be mapped to a fixed-length alpha field or an array of alpha characters in RPG. It can also be mapped to a date, time, or timestamp if it holds date values

Short: short

2 byte integer: 5I 0

A Java two-byte integer maps to a five-digit integer.

Character: char

UCS length = 1: 1C

Java characters are two-byte Unicode.

Character array: char[]

UCS length > 1: nC

Array of UCS len=1: 1C DIM(x)

Straight mapping of Unicode characters or choose an array of single characters.

Integer: int

4 byte integer: 10I 0

A Java four-byte integer maps to a 10-digit int.

Long: long

8 byte integer: 20I 0

A Java eight-byte integer maps to a 20-digit int.

Float: float

4 byte float: 4F

Single-precision floating-point.

Double: double

8 byte float: 8F

Double-precision floating-point.

Any object: Object

Object: O CLASS("x.y")

Holds an object reference.

Any array: xxx[]

Array of equivalent type: x DIM

Choose the type from above for an array type.

Some Java data types, such as byte, map to more than one RPG data type. The one you use depends on your knowledge of the contents. If the byte variable or array contains a character or characters that are the result of calling toBytes on a String object, then use a character field in RPG. On assignment, RPG will do the necessary codepage mappings. If the Java byte variable contains numeric data, assign it to an RPG three-digit integer field, so that no such mapping is done.

In Java, you can only convert characters or strings to single-byte variables and arrays if the Unicode contents can, in fact, be converted to a single-byte codepage. This will not be the case if the character or string contains true double-byte data, such as Chinese or Japanese characters. In these cases, you need to assign the Java character field to an RPG Unicode character field. For Java strings, you first need to use the toCharArray method to convert the String object into a Java character array. This, in turn, can be assigned to an RPG Unicode character field (data type c) or an array of RPG Unicode characters.

When assigning to a field versus an array, set the length to be as big as the string might possibly be (or 32,767 if you don't know how long the Java string might be), and then specify the VARYING keyword to indicate that the length may vary. Keep in mind that, while RPG Unicode characters are two bytes long internally, you specify the length in terms of number of characters, not bytes.

Java exceptions when calling Java from RPG

When you call a Java method from RPG, that method might throw an exception. For example, when RPG does its implicit casting from one object type to another, if the object types are not compatible (as discussed in Chapter 9), you will get a ClassCastException thrown. RPG intercepts all Java exceptions and converts them to standard RPG runtime errors, with one of the program status codes shown in Table B.2. (For a complete and up-to-date list of status codes, see the "Program Status Data Structure" section of the ILE RPG Reference manual.)

Table B.2: Java-Related Program Status Codes

Status

Code Comment

0301

Class or method not found in method call, or error in method call

0302

Error while converting a Java array to an RPG parameter on entry to a Java native method

0303

Error converting an RPG parameter to a Java array on exit from an RPG native method

0304

Error converting an RPG parameter to a Java array in preparation for a Java method call

0305

Error converting a Java array to an RPG parameter or return value after a Java method

0306

Error converting an RPG return value to a Java array

Java Virtual Machine considerations

When calling Java from RPG, the RPG runtime takes care of starting the Java Virtual Machine, if it is not already started. To explicitly start or explicitly destroy the JVM, call the JNI (Java Native Interface) procedures JNI_CreateJavaVM or JNI_DestroyJavaVM, respectively. (You first have to call JNI_GetCreatedJavaVMs.) Calling operating-system JNI methods like these requires you to use /COPY for the copy member JNI in file QSYSINC/QRPGLESRC. The details of making these calls is beyond this book, but are well documented in the RPG Programmer's Guide, in the section on RPG and Java.

At the time of this writing, a problem exists when the JVM is used in an interactive job. Specifically, the JVM for the job is destroyed when an ILE activation group ends, and it does not start again cleanly for the same job. This is a known problem, and work is being done to address it in V5R2, and possibly V5R1 via a PTF.

Classpath considerations

When RPG starts the JVM or you start it explicitly using a JNI call, the default CLASSPATH is used. This allows access to all the Java-supplied classes. To access other classes, you need to set your CLASSPATH prior to running your RPG program. This is most easily done using the ADDENVVAR CL command, specifying ENVVAR(CLASSPATH) and a colon-separated list of IFS folders for the VALUE parameter. This command sets the CLASSPATH for the life of this job only.



Calling RPG Procedures as RPG Native methods from JAVA

In addition to calling Java from RPG, there is also RPG support for calling RPG from Java. This support simplifies the effort to code and call Java native methods that are written in RPG as ILE procedures inside service programs. The Java language standard defines a common way to access C functions from Java, by defining a method signature with the keyword native. Such a method has no body, much like methods defined with the keyword abstract. Rather, the method is implemented in C, as a function, within a DLL or service program on iSeries. The DLL or service program containing the function (with the same signature as the Java method definition) is identified using a static initializer, as described in Chapter 14.

While it has always been possible to implement these functions using ILE RPG procedures, the effort required has been daunting without the special syntax and runtime support added to RPG IV as of V5R1. It is very easy, on the other hand, to call any iSeries program or ILE procedure within a service program using the Program Call Markup Language (PCML) supplied in the AS/400 Toolbox for Java. While easy, this means of calling RPG from Java does involve the overhead of starting a new job for the program or service program on the first call, and marshalling the parameter data between the jobs. In many cases, it can be more efficient to use RPG native method support, as the RPG procedures are called within the same job. You might or might not consider it easy.

The first important note about RPG native method support is that you must remember to specify the THREAD(*SERIALIZE) keyword on your RPG H-spec at the top of each module in which you wish to call procedures. Because Java is a threaded language, and you are calling RPG within the same job and possibly within multiple threads, you must use this keyword to avoid corrupting your data if two threads use the same database- accessing RPG logic simultaneously.

Writing an RPG native method is very easy. Simply code a RPG IV procedure as normal, being sure to specify the EXPORT keyword on the P-spec, and export it when creating the service program. (For example, use EXPORT(*ALL) on CRTSRVPGM.) You must also specify the EXTPROC keyword on your procedure prototype, with *JAVA for the first parameter, the package-qualified Java class containing the Java native method definition for the second parameter, and the Java name by which you want to refer to this method as the third parameter. When you create your service program, it is best to specify a named activation group such as QILE or your own name, versus using the default activation group or even *CALLER, since the caller in this case is Java.

To be able to access the procedure from your Java code, simply code a Java native method signature with exactly the same name (including case) as specified on the EXTPROC keyword of your procedure, and the same number of parameters. Each parameter and return type should be the Java equivalent of the RPG data type, as described in Table B.1. To tell Java the name of the service program containing the RPG native method, code the following static initializer at the top of your Java class:

static { System.loadLibrary ("MYSRVPGM"); }

Replace MYSRVPGM with the name of your service program. Repeat the System.loadLibrary statement for each service program containing native methods you wish to call.

With this, your Java code can now call that Java method as though it were written in Java! All data-mapping of the parameters will be done for you by the RPG runtime. At runtime, ensure that the library containing your service program is on your library list.

Let's look at an example, adapted from the ILE RPG Programmer's Guide. It starts with an RPG module that contains a single, simple procedure named checkCust. Given a customer ID, it does a chain operation to that record and returns true if the record was found, and false if it was not. This uses the CUSTDB database file from Listing A.3 in Appendix A, which contains a record format named CUSTREC. The key is the customer ID field, CUSTID, which has six integer digits and zero decimal places.

In the procedure in Listing B.4, the input parameter is defined as a 10-digit integer field, which maps to the int primitive data type in Java, as shown in Table B.1. The keyword CONST for the parameter indicates the parameter value does not change in the procedure. The procedure simply does a chain to that key, in the database file, and returns an indicator value of one if the chain was successful. The RPG indicator data type maps to the boolean primitive data type in Java. Notice that EXTPROC is specified, but the class name is not qualified with the package name because for this simple example, the class is in the unnamed package.

Listing B.4: An RPG Procedure to Be Used as an RPG Native Method

H NOMAIN THREAD(*SERIALIZE) DFTACTGRP(*NO) ACTGRP('QILE') ALWNULL(*USRCTL) FCUSTDB UF E DISK * -------------- * PROCEDURE checkCust prototype * -------------- D checkCust PR N EXTPROC(*JAVA:'MyClass':'checkCust') D custId 10I 0 CONST D* * --------- * PROCEDURE checkCust * --------- P checkCust B EXPORT D checkCust PI N D custId 10I 0 CONST /free chain custId custREC; return %found; /end-free P checkCust E

The Java code to call this procedure as a native method is shown in Listing B.5. A static initializer identifies the service program, and there is a very simple native method declaration. The main method tests instantiating the class and calling the native method.

Listing B.5: A Java Native Method to Call an RPG Procedure via JNI

public class MyClass { static { System.loadLibrary ("RPGNTVMTD"); } /** * The declaration of the RPG native method. * Calling this calls the RPG procedure checkCust * in service program RPGNTVMTD in library list. */ native boolean checkCust (int custId); /** * Command line control. Tests the RPG native method. */ public static void main(String args[]) { MyClass testObj = new MyClass(); // call the native method boolean found = false; int custId = 123; found = testObj.checkCust(custId); System.out.println("Result of native method = " + found); } }

To other Java code, a native method is no different than a regular Java method. Very nice! When dealing with character strings, it is best to define your RPG procedure to accept either a character or Unicode field, with the VARYING keyword. From the Java side, declare the native method to accept a byte array or a character array, respectively. Then, when you call the method with a String object, use the getBytes or getCharArray method, respectively. The RPG runtime will handle all the codepage conversions. Also very nice!

If your RPG procedure wants to "call back" and execute Java methods within the same object, use the %THIS built-in function to return a reference to the current object. This can then be passed as the first parameter to the Java method, using the syntax described for RPG calling Java.

There are some considerations when writing native methods. One is the CLASSPATH, which must be set properly to find your class, as usual. Another consideration involves exceptions. If your RPG native method ends in an error for some reason, the RPG runtime will throw a Java exception of class type java.lang.Exception, and getMessage on that exception object will return a string of the form "RPG nnnn" where nnnn is the status code from the RPG runtime.

Another consideration regards telling Java when you are done using a Java object, so that the JVM can cleanly dispose of that object, reducing memory leaks. This is done by calling the JNI API DeleteLocalRef. These and other considerations are described well in the ILE RPG Programmers Guide, in the section about RPG and Java. We leave them to your additional reading pleasure when you need them.



Finally

We hope you take these RPG enhancements as an indication of IBM's commitment not only to Java, but also to RPG. Indeed, IBM believes both languages have a long future and will live happily together for a long time to come!



Категории