I haven’t written anything on the Java and PeopleCode series (part 1, part 2) recently, so I thought I’d whip something together this evening.
As previously discussed in the series, there are a few, um, quirks in the bindings between Java and PeopleCode. One typical workaround when you can’t cross between Java and PeopleCode successfully is to write some additional glue code on the Java side to provide an easier “target” to work with. This post will discuss a few tips and techniques for doing it all from the PeopleCode side.
Why would you want to avoid writing the Java glue code to simplify things? Well, it’s certainly not to avoid the complexity of Java (as the rest of this post will show). A more common reason is to avoid needing to distribute the compiled Java code out to each application server (which can be the source of various logistical difficulties).
On with the code. The use case here is to take an image and modify it so that we can stamp some text on it. The example comes from an article that shows how to use the Java Advanced Imaging
libraries that are part of the standard Java environment as of Java 1.4.
The actual working code is below. Let’s start by looking at the first line of code that causes problems.
&jBufImage = &jImageIO.read(CreateJavaObject("java.io.File", &sSourceFileName));
This line of code will trigger the infamous “more than 1 overload matches” PeopleCode error. If you look at the relevant Javadoc, you’ll see that there are indeed multiple versions of the read method. Java can tell these apart by the type of the parameters being sent in, but PeopleCode only uses the number of parameters to differentiate among methods in a Java class with the same name.
In order to call this method from PeopleCode, we’ll need to use reflection. Reflection is how Java lets you determine class metadata (such as what methods it has and what parameters they take) at runtime.
Here’s what it looks like in action. This is broken into separate lines for clarity, but as you’ll see in the code, you can combine these where it makes sense.
&jReadArgTypes = CreateJavaObject("java.lang.Class", &jIOFileClass);
&jReadMethod = &jImageIOClass.getDeclaredMethod("read", &jReadArgTypes);
&jReadArgs = CreateJavaObject("java.lang.Object", CreateJavaObject("java.io.File", &sSourceFileName));
&jBufImage = &amp;jReadMethod.invoke(&jImageIO, &jReadArgs);
This is easier to explain working from the bottom up. In order to call a method via reflection, we need to have the correct Method classinstance and use it’s invoke method. That’s what the 4th line is doing. The first parameter, &jImageIO, is the same object that we were trying to use before, and the second parameter is an array of parameters that invoke() will pass along to the “real” method.
Getting that parameter array is what line 3 does. When we have all of the values that are ever going to be in the array, then using CreateJavaObject with the braces, , at the end of the class name is nicer than using the CreateJavaArray PeopleCode function. Mainly because we can pass all of the values in at once instead of setting them one by one as CreateJavaArray forces you to do.
We also needed to have the actual Method object. That’s what line 2 is doing. We call the getDeclaredMethod() method of the underlying Class object (this is the actual Java class that defines what a Java class is; chicken, meet egg) and pass it the name of the method that we want, along with array of the parameter types (not the parameter values!) that the method expects.
You can get the underlying Class object for any Java object by calling the getClass() method (there are examples in the code below). But when you have a JavaObject in PeopleCode that you obtained via GetJavaClass (instead of CreateJavaObject), then you actually have a PeopleCode reference to the class and not an instance of java.lang.Class. The PeopleCode reference allows you to call static methods on the class, but if you call getClass() on it, you’ll get an error. The secret to getting to a java.lang.Class instance for a particular class when you don’t have any instances of that class is to do something like this.
&jImageIOClass = GetJavaClass("java.lang.Class").forName("javax.imageio.ImageIO");
Now &jImageIOClass is an actual java.lang.Class instance, suitable for the reflection work that we’re doing.
Finishing things off, in line 1, we created the array of parameter types that we needed for the call to getDeclaredMethod(). The parameter types are specified by using their underlying java class, so you definitely want to be sure that you understand the difference between a java class and the java.lang.Class object which describes that java class.
Whew! That’s a lot of explaining to do just because PeopleCode doesn’t resolve all Java methods properly. What’s worse is that we’re not done yet. We now have another problem.
In the original line of PeopleCode, we called a method (“read”) that returns a Java object. Specifically an object of type java.awt.image.Bufferedimage
. But we can’t use it as a BufferedImage object, because PeopleTools looks at the return type for invoke() and sees that it returns java.lang.Object, which is the base class for everything in Java. If you try to do something useful with &jBufImage (like get the dimensions of the image), PeopleTools will give you a “method not found” error.
Thankfully the underlying JVM still understands that this is a BufferedImage object and not just a java.lang.Object. So we can (read “have to”) use reflection again in order to use our BufferedImage. Of course, since we’re using reflection with BufferedImage, that means that any Java objects that we get back from reflected method calls are also misunderstood by PeopleTools (it will think that they are instances of java.lang.Object rather than whatever they really are).
So, once you fall into needing to use reflection within PeopleCode, you end up using a lot of it.
Believe it or not, it’s not so bad once you wrap your head around it. It took me longer to write this post than it took to write the code below since the extra work is essentially just some extra typing.
Obviously if you are doing a lot of Java/PeopleCode integration, then you’d be better off just writing a little bit of glue code on the Java side to avoid all of this, but when you’re just doing something quick (like using Java hashmaps instead of faking it with 2 dimensional arrays in PeopleCode), then this technique works well.
Finally, here is the actual code, along with the starting image (found in your PeopleTools directory) and the altered image.
Labels: PeopleCode, User