Starting out with nothing but Java...
In many integration projects, there's nothing but Java code to start out with. This document provides an introdution into how you would approach a project like this by using the JMS API as an example. Our integration project involves generating C# proxy types for the JMS API so we can integrate our .NET applications with a Java Message Service enterprise backbone. At the end of this introduction you will have a .NET assembly which exposes the entire Java JMS API to .NET.
We intentionally do not use several best practices in this example to keep focused on introductory and core points. We also focus on the code generator GUI rather than the commandline or ANT alternatives for generating proxy code. You would do several things very differently if you were to tackle this problem the same way we do when we generate our JMS Courier product.
Launching the code generator
First, you launch the code generator by running jcpp in the distribution's bin directory. The code generator invocation yields an empty code generator window suchas the one you see on the right.
Don't be distracted by the application claiming to be JunC++ion. This screenshot is from the combined JuggerNET and JunC++ion code generator that just happens to have the "JunC++ion" name.
You see a very simple GUI with three menus and a main window that accepts certain Java files in the course of a drag and drop operation. In this use case, we're going to drop a jms.jar file on the code generator window to import the JMS API. As you can see from the background image in the code generator, you could create a model first but you don't have to. We will choose the simplest alternative and drop the jar file directly on the code generator.
Importing the JMS API
After dropping jms.jar on the code generator, you see something like the image on the right. There now is a so called model window open. It has the familiar vertical split showing a tree view on the left-hand side and a detail view on the right-hand side. The tree view offers a hierarchical display of all the packages, classes, and even methods and fields of the imported Java types. You can also find two class counts in the model window. The first number tells you how many Java types were analyzed for this model. The second number tells you how many "important" types are marked as generating. In the javax.jms package just about all types are marked as generating. You will have to expand some of the other packages to find types that are not marked as generating.
The code generator uses heuristics to determine which types you're probably interested in. Types that you dropped on the code generator are definitely "interesting," therefore all public JMS types are generating. If you were to expand a JMS type, you will find that many types have methods that use java.lang.String or java.lang.Integer or other arguments. The code generator will also have to enable these types if you are going to be able to use such methods from your .NET application.
Why doesn't the code generator just enable all types? You would not ask that question if you had seen some (not too uncommon) integration scenarios we've seen: many thousands of Java classes in the model and only a couple of hundred really required. The code generator GUI is built around a key premise: it needs to be relatively easy for you to generate what you need... and only what you need!
Import some additional types
We're not done with importing yet! The code generator analyzed all the Java types referenced directly and indirectly by our imported jms.jar file, but that doesn't mean that it knows what code you will want to write in your .NET application! If you're going to be writing a .NET application that uses JMS, you will have to discover the JMS provider via JNDI (Java Naming and Directory Interface). For that purpose you need the javax.naming.InitialContext type. Unfortunately, none of the JMS classes referenced javax.naming.InitialContext, so this type is not yet part of the model.
The InitialContext type is part of your Java runtime environment Does that mean that you now have to import rt.jar (the file in which almost all built-in Java types reside) into your model just to get one additional required type? Thank goodness the answer is a resounding no! You should never import rt.jar into a Java model because you're going to end up with thousands of types all marked as generating and most of them of absolutely no use to you.
Instead, you can simply import the required type by its name by selecting the File Import Class from Classpath... menuitem. This opens a little dialog into which you can type the required typename. You can use this menuitem repeatedly to import additional types that you're interested in using from the .NET side but that are not yet part of the model.
Set the transformation options
Before we can generate the proxy types, we need to set some options in the configuration dialog available from the File Properties menuitem. You really only "need" to set an output directory, otherwise the code generator will complain, but there are several other options that you would normally wish to set.
Chief among the "optional" settings is the name transformation policy (do you wish the proxy API to look more like Java or more like .NET) and the name of the assembly and other .NET assembly options, but there are many other options governing COM interoperability and other topics. The options are grouped into tabs that hopefully help to unclutter the user interface.
Now you're ready to accept all changes by clicking OK which returns you to the model window.
Save your work
Before doing anything else, you want to save your work. The code generator has recorded all the actions that you have taken so far. It remembers all the import steps as well as all the property modifications that you made in the Properties dialog. By choosing File Save, you can persist all these activities in a so-called model file. The model file allows you to come back to this point later to make further modifications or to simply regenerate the .NET bindings.
Because a model file records the actions that you took and not the end result, it has some very interesting characteristics. Let's assume that the JMS API is revisioned by Sun and a new version of jms.jar is released. How many changes do you think you will you have to make to your modelfile to pick up the changes? The answer is: none! The code generator recorded the fact that it imported a file called jms.jar. As long as the new version is also in a file called jms.jar, you can use the same modelfile for generating proxy types for the updated API.
Generate proxy types
Finally we're ready to have the code generator do all the tough work. Choose Generate Generate .NET proxies from the menu and watch the files appear in your configured target directory. As already hinted at, the code generator does not generate just C# source code, it also generates build support files like Visual Studio project files and build batch files. It actually generates several different versions of Visual Studio project files, one for each traditional project type: an assembly (DLL), a console executable (EXE), and a Windows executable (EXE).
Build the generated code
Now that we have all these generated files, we will build them into an assembly.
Doubleclick the generated jms_buildlib.bat file or open the jms.lib.csproj project file and answer all project conversion-related questions with yes. At this point, you can build the project by choosing the project build command and as an end result, you will get a resulting assembly called jms.dll.
This excercise demonstrates the core points of JuggerNET-based integration using a very realistic use case. As you saw when you were setting the project properties, there are many more options that you could be playing with and which we ignored for the time being. What would we have done if this were a "real" production build of an integration library?
- We would have run the code generator with many Java source files on the model classpath, thereby enabling the code generator to find the Javadoc comments for the generating classes.
- We would have enabled documentation generation by setting the "Documentation"-related options in the project properties.
- We would have imported many more types into the model and marked several others as generating (the primitive wrapper types, reflection API types, some collection types, etc.)
- We would only be using modelfiles during the design phase of the integration project and then move on to an ANT-based build.
From start to finish, this project probably took you about 15 minutes. We're not going to lie to you: as a first project, you would probably have taken about a day or two to figure out what types you need to expose to .NET and you would also have spent some time learning the ins and outs of using the generated code.
Nevertheless, once set up, you will always be able to regenerate the bindings, even in the face of changes in the underlying Java APIs and you will always be able to have totally up-to-date .NET bindings for your Java code base.
If you are convinced at this point that JuggerNET is a nifty idea that saves you some time and money, we want you to consider one more thing:
The true benefits of using JuggerNET for Java/.NET integration really only begin when you're done with the code generator GUI and enter the world of product maintenance and automated, overnightly builds.
Too many people neglect to consider the long-term costs and implications of their technology choices. JuggerNET has a very compelling price/value ratio when you take long-term project maintenance into account.