|
Prototyping and Calling Java Methods from RPG
by Kevin Vandever
In the last issue, Ted Holt explained the advantages of prototyping programs and modules
("RPG Prototyping").
He provided examples of how to use prototypes and showed a consistent way to call prototyped
functions, regardless of their origin. I'm going to expand on that topic and show you some OS/400 V5R1
enhancements that enable RPG to define Java classes and call Java methods using the Java Native Interface
(JNI). And, as you'll see, prototyping is right there in the middle of it all.
Defining Some Terms
So what are classes and methods? And why do we want to define them and call them in RPG? A method is
a function or procedure that accomplishes a task. You can think of a method as being the same as an RPG
subprocedure. A well-written subprocedure is a function that accomplishes a specific task. A class is a
collection of methods and variables. You can compare a class to an iSeries service program, which is a
collection of subprocedures (methods) and variables. They're not exactly the same, but that description is
close enough to help you understand the concept. An object is a copy or instance of a class. Think of a class
as the blueprint and the object as the actual house. You can have many houses built from the same
blueprint, just as you can create many objects from one class.
The reasons we may want to define classes and call Java methods from within an RPG program are two-
fold. First, the Java API set contains hundreds of classes, any one of which may be exactly what you are
looking for to solve a particular problem. And having these classes already written and tested for you
means that you don't have to reinvent the wheel each time your application needs a new function. Second,
you or someone else in your shop may at some point develop your own home-grown Java applications that
provide the functionality you need to be able to access from an RPG program. OK, enough theory. Let's get
to it.
The Object Data Type
With V5R1 came the introduction of the object data type. The object data type is signified by placing an O
in column 40 of your D-spec and implementing the new keyword CLASS in the keyword portion of the D-
spec. The CLASS keyword contains two parameters. The first parameter is *JAVA, which simply identifies
the object as a Java object. The second parameter is the fully qualified class name for your object. The class
name is case-sensitive, so make sure you get the case correct when defining your class. Check out the
following example, which defines a Java BigDecimal class:
D Sum S O Class(*JAVA:'java.math.BigDecimal')
IBM does place some
restrictions on object usage. You cannot use objects as subfields in a data structure. Also, though you can
have an array of object fields, you cannot create pre-runtime or compile-time tables and arrays of object
data types.
Proper Prototyping Promotes Positive Programming
OK, so you've defined an object. Great! Now what? Well, that's where the second Jave Native Interface-
related V5R1 enhancement comes in. In last issue's prototyping article, Ted discussed the EXTPROC
keyword and how you can use it to prototype ILE modules so that you can call them with the prototyped
call, CALLP. IBM expanded the EXTPROC keyword in V5R1, making it possible to prototype a local Java
method as well as an ILE module. The EXTPROC keyword now has three parameters used to prototype a
Java method (EXTPROC has not changed in the case of ILE modules; you still use the one parameter,
module name, as you did before). You'll recognize the first two parameters from the CLASS keyword in
the previous paragraph. They are *JAVA and the qualified, case-sensitive class name that contains the Java
method you are prototyping. The last parameter is the Java method name. Below is the prototype for the
Java add method, which allows you to add two BigDecimal objects together and return the sum:
D add PR O ExtProc(*JAVA:
D 'java.math.BigDecimal':
D 'add')
D Class(*JAVA:'java.math.BigDecimal')
D BigD2 O Class(*JAVA:'java.math.BigDecimal')
D Const
I know; it doesn't look much like any prototype you've seen before. Well, not only does it look a little
different, it behaves differently, too. On the first line, I defined the prototype name, add. This is the same
name as the Java method I am prototyping, but it doesn't have to be. You can call it whatever you want.
The PR states that this is a prototype and the O data type signifies that the return value is an object. Next
you have the EXTPROC keyword with its three parameters: *JAVA, the class name, and the method that
you are prototyping--in this case, add. The next line is part of the definition of the return value. Remember
I told you that, to define an object, you needed the O data type and the CLASS keyword? The
same holds true here. The CLASS keyword completes the definition of the return value defined on the first
line of the prototype. Next is the input parameter definition, BigD2. This is pretty straight forward now that
you know the rules. This input parm is a Java object from the class java.math.BigDecimal. Since this
method will not change the value of this parameter, I use the Const keyword to tell it so and make it a little
more efficient.
You're set, right? What? Only one input parm? You thought the add method returned the sum of
two BigDecimals? You are correct, and this is where Java methods act a little differently than
prototyped programs, modules, or procedures. When prototyping ILE functions, you specifically define a
parameter for every value you will pass to that function. But Java doesn't work that way. Java has the
concept of THIS instance, meaning the instance of this object itself can contain a value; therefore, when
calling the add method, you will pass it THIS instance of the object and one input parameter. It will return a
BigDecimal object containing the sum. You'll see what I mean when I show you the call to the Java
method.
The *CONSTRUCTOR Method
Before I move on to calling the add method, I want to first talk about constructors. Java classes contain
constructor methods. These constructor methods allow you to create an instance of a particular class based
on some input. This is important to know when using objects, especially in RPG. Think about what we are
going to do. We are going to pass two BigDecimals to a method and return the sum. We already know how
to create object data types in D-specs, but how do we instantiate (assign a value) to those objects? Well,
there are two ways.
The first way is to define the object in a D-spec, as we will do with the sum object, and use that object to
accept the return value from a Java method. That works wonderfully when the value for that object is going
to be filled by the Java method, but if you want to instantiate the object with a value in your application,
you must prototype a valid constructor method and call that method passing the appropriate non-object
typed data. There is no MOVEOBJ op code, yet, that will do it for you.
In the case of the BigDecimal class, there are four constructor methods, and each one translates a particular
object type into a BigDecimal. In my example, I will show you one: BigDecimal converted from a String.
There are also constructors to translate from Double and two different types of BigInteger, but Sun Microsystems recommends translating a BigDecimal
from a String, because it will produce more accurate data, since BigDecimal will translate exactly to the
Strings value (.1 in String is .1 in BigDecimal); whereas data conversion issues may arise when you
translate from a non-string object. The constructor method prototype for String to BigDecimal translation is
the following:
D String2BigD PR O ExtProc(*JAVA:
D 'java.math.BigDecimal':
D *CONSTRUCTOR)
D Class(*JAVA:'java.math.BigDecimal')
D Str O Class(*JAVA:'java.lang.String')
D Const
It's not much different than the previous prototype, except that, instead of a method name as the third
parameter of the EXTPROC keyword, *CONSTRUCTOR is used. This method accepts a String and will
return a BigDecimal that we can then pass to the add method. The Str parm will not be changed by the
method, so I define it as a constant using the CONST keyword. There are a number of data conversion
issues and rules for passing data to and from Java. Since we are starting with alphanumeric data in our
RPG, we aren't too concerned with the rules. However, there will be times when you are. For a complete
listing of RPG-to-Java data issues, check out Chapter 11 of the ILE RPG
Programmer's Guide.
Calling the Java Method
Now we're ready to call our methods. I've listed the complete RPG used to do the trick. (You can
download the RPG
program as well as the prototype copybook.)
**********************************************************************
* To Compile:
*
* CRTBNDRPG PGM(xxx/MATH) SRCFILE(xxx/QRPGLESRC)
*
**********************************************************************
H DftActGrp(*NO) ActGrp(*CALLER)
/Copy *LibL/QRpgLeSrc,MathPr
D alpha1 C Const('2.5')
D alpha2 C Const('1.5')
D string1 S O Class(*JAVA:'java.lang.String')
D string2 S O Class(*JAVA:'java.lang.String')
D Sum S O Class(*JAVA:'java.math.BigDecimal')
D BigD1 S O Class(*JAVA:'java.math.BigDecimal')
D BigD2 S O Class(*JAVA:'java.math.BigDecimal')
D StringSum S O Class(*JAVA:'java.lang.String')
D DisplaySum S 30A Varying
* Create String objects from the alphanumeric constants
C Eval String1 = newString(alpha1)
C Eval String2 = newString(alpha2)
* Create BigDecimal Objects from the String objects. We
* could creat BigDecimal directly from a float or bigInt
* and save the String object creation step, but Sun
* recommends using Strings for data accuracy.
C Eval BigD1 = String2BigD(string1)
C Eval BigD2 = String2BigD(string2)
* Add the two BigDecimal objects. See how there are two
* parameters even though we only defined one. The first
* parm is the instance parm and the second parm is the
* one that we defined in the prototype.
C Eval Sum = add(BigD1:BigD2)
* Now convert the BigDecimal sum back to a string; then
* convert the String into aplha data for us to view.
C Eval StringSum = BigD2String(Sum)
C Eval DisplaySum = getBytes(StringSum)
* display the alpha version of the sum and end the program.
C DisplaySum Dsply
C Eval *InLr = *On
The first thing to mention is the H-spec. I tell the compiler from this spec that I don't want to use the default
activation group, DFTACTGRP(*NO). Instead I will use the callers activation group,
ACTGRP(*CALLER). Placing the directives in the H-spec will save you time and allow for more
consistent compilation and running. Next you'll see the /copy statement, used to bring in my prototypes.
Then I initialize two alpha constants; these are the two values I am eventually going to add together.
Let's jump to the C-specs and see how I call the Java methods. First, I convert my two alpha fields to
Strings. Again, I could have converted float or integer data directly to BigDecimal, but I decided to take the
extra step of converting to String in the name of accuracy. Now that my data has been translated to String
objects, I will now translate those string objects to BigDecimals, using the String2BigD method. Now I
have my data in the proper format, so I simply call the add method, which will add the two BigDecimal
objects and return a BigDecimal object in sum. Notice that I have two parameters in the add method even
though I only defined one. This is the instance object that I was talking about. Java uses the instance of the
object itself as the first parameter, and the second parameter is the one we defined. Anyway, now I have my
sum, but I can't really do anything with an object, so I need to convert it back to String and then to Alpha so
that I can display it. I accomplish these tasks by calling the BigD2String and getBytes methods
respectively.
JNI in RPG is A-OK
Of course, you don't have to go through all of this trouble just to add two numbers. The point is that you
can access local Java APIs and home-grown applications interactively, in RPG, using prototyping and
prototyped calls. I've given you a basic look at calling Java methods using JNI. I provided a simple enough
example, so that you could concentrate on the concepts and not worry so much about the business rules.
And you should definitely concentrate on the concepts. Make sure you understand the difference between
classes, objects, and methods. Get comfortable with constructors, static versus non-static calls, interfaces as
well as other object-oriented concepts. It will serve you well when using JNI.
In coming issues, I will provide a more advanced look at JNI; specifically, native method calls from Java,
threading issues, Java Virtual Machine concerns, and much more. The wall between Java and RPG is
starting to come crashing down. It's not quite there yet, but JNI has provided us with another hammer.
Kevin Vandever is a lead IT engineer at Boise Cascade Office Products in Itasca, Illinois, and co-editor
of Midrange Programmer, OS/400 Edition. He can be reached at kvandever@itjungle.com.
|