Lesson 3: Import Built-In Java Types
This is the first lesson where we are dealing with Java types. In the previous lessons we learnt how to create, modify, save, and open model files. We focused exclusively on the right side of the code generator GUI, i.e. the Model Properties and Model State panes. Now we are going to start looking at the left side: the Navigation pane. We are going to define an integration project that actually contains some Java types.
The Case for Built-In Types
Java comes with many built-in types that are immediately useful. One possible use case that comes up surprisingly often is the JDBC API. We're going to illustrate the integration case without getting too deeply into databases. JDBC stands for Java DataBase Connectivity and is a standardized API for communicating with databases. Even C++ and .NET developers are frequently interested in JDBC because of the absence of database drivers in their native language or because the existing drivers have different bugs from the ones that they use in the Java parts of their applications. This provides the motivation for attempting to use the JDBC API from another language.
First, we need to figure out which Java types we're interested in. The JDBC API is defined in the java.sql
and javax.sql
packages, so we will definitely need the types from these packages. These types rely on many
other Java types though, the java.lang.String
or java.util.Date
types come to mind. We will
start by importing the JDBC core types and inspect the model.
Step by Step Instructions
- Start the Code Generator GUI
To start the code generator, run
xmog
without the-version
argument. Assuming you are still in the code generator'sbin
directory, simply type:xmog
./xmog
If you run into problems please check out the first lesson.
- Change the Model Name
Set the model name to JDBC (see Lesson 1 if you don't know how).
- Open the Import Type(s) from Classpath Dialog
Click the toolbar icon or select Import Type(s) from Class Path... from the code generator's Edit menu. You should see this dialog displayed:
This dialog allows you to specify the set of types that you wish to import by combining inclusion and exclusion patterns. Basically, anything matching the pattern(s) you enter in the Include field but not matching the pattern(s) in the Exclude field will be imported into the model.
- Specify the Types You Want to Import
In our case, we are primarily interested in the types in the
java.sql
andjavax.sql
packages, so we leave the Exclude field blank and enter the following in the Include field:java.sql.*,javax.sql.*
This means: all types in the
java.sql
package plus all types in thejavax.sql
package. The asterisk stands for "every type in this package." The code generator understands one more pattern: the double asterisk. A double asterisk stands for "every type in this package and all nested packages."Please note that you don't have to use name patterns; you could certainly specify one or more exact type names if you know exactly which type(s) you needed. You could also combine names and patterns, for example:
java.lang.*Exception
stands for "all types in the
java.lang
package whose name ends withException
." - Check Out What Happened
You should now see something looking similar to the image below:
If you check the Model State pane now, you will find the familiar
setmodelproperty()
command for the name change but also animport()
command that looks something like this:^javamodel('javamodel.12796493465.2').import({include:'java.sql.*,javax.sql.*'});
That of course is a very faithful recording of what we just did!
The Navigation pane now contains a bunch of packages, many more than the two packages that we specified! Where did they all come from?
The code generator analyzed the include string we provided and translated it correctly into a command to import all types from the two packages we asked for. It went and located those types in the jar files containing the Java class library. Once it had located the types, it analyzed them and found that they relied on many more types in other packages, so it located and analyzed those types as well. It recursively repeated this process until no additional required types were discovered. This is how we ended up with all these packages in the model.
Why are some of the package names displayed in bold text and some not? Bold names are an indicator that the package contains enabled types. Type enablement is a critical concept that you need to understand in order to really understand the code generator.
In a nutshell: only enabled types result in corresponding C++ proxy types being generated. If you consider all the work that the code generator did in analyzing the imported types, you can probably imagine how many types it had to analyze. To give you a hint: if you recursively follow all dependencies from the most basic of Java types, i.e.
java.lang.Object
, you end up with almost 400 Java types! Now imagine that you are interested in generating a proxy type forjava.lang.Object
. Do you really want 400 proxy types so that all of Object's dependencies are available in C++? Probably not.Remember that you do only need to generate proxy types for the Java types that you are going to code to in C++. All the other types are irrelevant as far as you as a C++ developer are concerned. Those type continue to exist on the Java side and they will continue to be used at runtime, but at development time you only need the types that you code against.
You probably want a proxy type for
java.lang.Object
plus a few others, maybejava.lang.String
becausejava.lang.Object
has atoString()
method that returns aString
instance. If you want to call thetoString()
method from your generated code it stands to reason that you will need theString
type! Possibly, you wantjava.lang.Class
becausejava.lang.Object
has agetClass()
method returning aClass
instance. Three types is a far cry from 400 types!The code generator has built-in rules that govern which types should be enabled by default. By default, all the types that you explicitly asked for are enabled. Also by default, all types referenced publicly from these types, be it as super types, as a field type, as a method argument or method return type, are enabled.
- Expand the
java.lang
PackageClick on the plus icon in front of the
java.lang
package. This expands the package and you can see the types from this fundamental package that were pulled in by our import action. You should see something like the screenshot below:The different text styles give us visual cues about how the code generator "thinks" of these types. As mentioned already, types in boldface are enabled, i.e. they will result in C++ proxy types. We can also see that the enabled types in the
java.lang
package are displayed in gray. Gray tells us that the code generator is aware that we did not specifically ask for this type but it enabled it anyway by applying its enablement rules.If you expand the
java.sql
package and look at the types there, you will find that they are all shown in black boldface. Bold means that they are enabled. No surprise there because we specifically asked for these types. Black indicates that they code generator is aware that these are types that we explicitly asked for. - Check out the Model's Java Settings
We could have done this first, but it's not too late. If you come from the Java side and you have worked with different versions of Java, you might wonder:
Where does the code generator import these types from?
Which version of the JRE does it use?
We distinguish between built-in Java types and user Java types. The built-in Java types are the types for which you would not have to specify a classpath when running your Java application. These are the types that come with Java—like our JDBC types—and are simply there because Java is there. That is different from user Java types. Those Java types typically require you to run your application with a
-cp
or-classpath
switch and they are located in files and directories that are not generally known. You cannot import user Java types without specifying where to import them from. This is covered in another lesson.Up to Java 8, the built-in types could usually be found in a file called
rt.jar
that was in your JRE installation. More modern versions of Java use Java modules (.jmod
files) to hold their Java library files. You do not need to worry about the exact places that the built-in Java types are stored in. You most certainly do not have to import these types into your model!Take a look at the Model Properties pane, specifically its System Class Path property. The system class path corresponds to the built-in classes that come with Java. When you hover over it you should see a long list of classpath roots from a Java Runtime Environment. Its value can be interpreted as follows: "take the
jre
model property and interpret it as a terp^jre
type, then ask that instance for the value of itssystemclasspath
property."Now look at the JRE model property. It can be interpreted as: "take the value of the
jdk
model property and interpret it as a terp^jdk
type, then ask it for the value of itsjre
property."Now look at the JDK model property. It can be interpreted as: "give me a JDK instance, any JDK instance!"
So you can see that ultimately, the system class path is derived from the JDK property's value and that means that you can control where these Java types are imported from by simply changing the value of the JDK property and having it point at a different JDK!
You might not be able to do this as part of this lesson unless you have multiple versions of a JDK installed. If you do you can simply add your JDK's home directory to the
^jdk()
value, for example:^jdk('C:/Program Files/zulu-13')
Please note that doing this after you have imported the types is meaningless. This is very important to understand:
You have to set the Java class path roots for your model before you import types from them!
In general, you want to set all model properties to their final values before you perform any import actions.
- Play With Navigation Pane Settings
Use the controls along the top of the Navigation pane to change what you are shown and how it is being shown.
The first button toggles between "flat" and "hierarchical" package modes. It is a mere display preference.
The next two buttons allow you to control whether fields and methods are displayed in the model. Often you do not need to work with your model at that level of detail, so by default the code generator does not display fields and methods. You can change that here.
The following four buttons allow you to control the display of elements by accessibility.
The filter field allows you to quickly locate a Java type by name when you don't know which package it is located in. To use it, switch to flat package mode and expand all elements (using the second to last button in the toolbar). Then start typing in the filter box and you'll see the type list adjust. Remember to clear it to see all the types in the model.
- We're Done For Now
You could generate your C++ proxy types now but we're going to do some further model tweaking in the next lesson. Don't close your model because we're going to need it for the next lesson.
Don't worry if you messed up some settings while you played with your model. All you need to do to recreate it from scratch is change the model name and import the two Java packages. Everything else was merely instructional.
Take-Away Points
- Never import
rt.jar
or a Java Runtime Environment's.jmod
files. The types in these files are automatically available based on yourjre
model property. - You can import types by name and name pattern as long as the types are built-in Java types (or they are findable on the User Class Path).
- You learnt how to configure the JDK/JRE that you wish to base your C++ proxy types on.
- Import commands record your expressed intent in the model file, not the end result. This means that an import
command records for example that you wanted to import all types in the
java.sql
package, it does not record the list of types that were imported.This is important because it allows you to express two equally valid use cases:
- Import all types in a package (using name pattern)
If new types are added to the package later, the new types will already be part of the import specification and will be default-enabled when you regenerate with the updated Java libraries.
- Import all currently known types in package (using exact type list)
If new types are added to the package later, the new types will not automatically be part of the import specification and might be missing when you regenerate with the updated Java libraries.
There can be legitimate reasons for either use case, though in our experience the second one is much rarer.
- Import all types in a package (using name pattern)