: junc++ion generate

Model Files

Model files are a sequence of statements in Codemesh's terp language. Model files that were created by the code generator GUI all follow this pattern:

^javamodel(modelId).command1(arguments);
^javamodel(modelId).command2(arguments);

...

^javamodel(modelId)

Please note that all statements begin with a ^javamodel() lookup and all statements but the last one are terminated by a semicolon. This section explains why.

The terp language is stack oriented. When you have multiple expressions in sequence, they are pushed on the execution stack one after another. A semicolon converts an expression to a statement and statements restore the stack to what it was when they began executing. Anything that was done within a statement is popped off the stack when the semicolon is encountered.

Without looking at what happens in the statements, you can see that all but the last command in the model file will have their results popped off the stack. The very last command is not followed by a semicolon, which means that its result will be kept on the stack.

When the code generator opens a model file it expects it to contain a sequence of commands that result in one ^javamodel() instance being on the stack at the very end. That is the instance on which the code generator will operate when it is asked to transform Java to C++. Incidentally, whatever is left on top of the stack at the end of the model file will be referred to by the tos variable. See the CLI Reference for its purpose.

We always work on the same ^javamodel() instance in the model file because ^javamodel() acts as a factory method rather than as a constructor. The first time you invoke it with a model id that it does not know, it creates an empty Java model instance with the given id. All subsequent times it just returns that instance from its internal cache. That means that all commands in the model file will operate on the same instance. Were you to write a model file by hand, you could assign the first ^javamodel() lookup to a variable and then use that variable for subsequent command invocations. The following snippet is completely synonymous to the one above.

jm = ^javamodel(modelId);
jm.command1(arguments);
jm.command2(arguments);

...

jm

 

That leads to the question of what kind of commands we can execute on a ^javamodel(). The short answer is that for everything that you can do in the code generator GUI that alters the state of the Java model, there is a corresponding command. A model file usually starts out as a faithful recording of all the manipulations you performed on your Java model until you saved it.

There are two broad categories of commands that can be invoked on Java models:

  1. Many commands correspond with code generator actions that operate on the currently selected elements (Java packages, types, methods, or fields) in the model. They all have in common that they have a first argument of a selector type. These types are explained here after the commands have been introduced.

  2. Other commands deal with the getting and setting of model properties. Model properties govern the code generator's behavior at various stages of the work flow. While a model property can have any name you wish, there are some predefined names that the code generator uses. A full list is available here.

The table below contains a list of all commonly used commands that are invoked on a ^javamodel() instance with a brief description for each.

Command
Description
callbacktypes(
    typeselector,
    boolean
)

Enables or disables selected types as callback types. Only certain types are eligible to be callback types. Calling this on an ineligible type has undefined behavior and should be avoided.

enablefields(
    fieldselector,
    boolean
)

Enables or disables selected fields. Enablement determines whether the field will be present in the generated code. The effective enablement of a field is determined by logically and'ing its own enablement with that of its declaring type.

enablemethods(
    methodselector,
    boolean
)

Enables or disables selected methods. Enablement determines whether the method will be present in the generated code. The effective enablement of a method is determined by logically and'ing its own enablement with that of its declaring type.

enablenative(
    methodselector,
    boolean
)

Enables or disables native method processing for selected methods. This can only be called on methods that are declared native. If enabled, the code generator will generate a delegating native method implementation that expects to be implemented in terms of proxy types.

enabletypes(
    typeselector,
    boolean
)

Enables or disables selected types. Enablement determines whether the type will be present in the generated code. If a type is disabled, all its nested elements are disabled also, even if they are marked enabled.

evaluate(
    propertyname
)

Returns a typed object for the given property name rather than the property's string value. This is a utility method that allows you to have a model instance resolve a property value for you. All model properties have string values. If a property has an expression value and you need the value of the expression rather than the expression, you have to use the evaluate() method.

getmodelproperty(
    propertyname
)

Returns a model property's string value. This could be an expression that only makes sense in the context of the model

import(
    typeselector,
    [, pathRoots ]
)

Imports Java types from the model classpath or from the optionally specified pathroots.

setmodelproperty(
    propertyname,
    value
)

Sets a model property value. The value should always be a string. If the value's intent is an expression, enclose the expression in quotes.

 

Most of the commands introduced in the previous section have one or another selector type as an argument. In this section we will introduce the purpose and the power of element selectors.

Let's start with type selectors because they are the easiest to grasp and many times they will be the only ones you use.

What actually happens when you select the java.lang.Runnable type in the code generator GUI and press "Disable Selected Element(s)"? You can of course easily find that out by doing it and then checking the Model State, but to save you the hassle, here's what (more or less) gets recorded in your model file:

^javamodel('myId').enabletypes('java.lang.Runnable', false);

The simplest type selector is a fully qualified typename, just as you would expect. Also not terribly surprisingly, multi-selection of types results in a semicolon-separated list of type names:

^javamodel('myId').enabletypes('java.lang.Runnable;java.lang.Thread', false);

We start seeing a bit more of the potential of selectors when we disable a package in the code generator GUI. That action might get recorded as follows:

^javamodel('myId').enabletypes('java.lang.*', false);

or as follows:

^javamodel('myId').enabletypes('java.lang.**', false);

The first variant specifies that all types in the java.lang package are to be disabled. The second variant disables all these types and all the types in nested packages as well.

The above examples represent the limits of what is expressible via actions within the code generator GUI, but type selectors don't stop there. The functionality described below can only be accessed from within hand-written model files but it will work just like the simpler examples above. All previous examples have in common that they use a file name pattern to describe the types that should be included in the set of types.

We can also exclude types from this set to specify sets like: all files in the java.lang package except for types whose name ends with Error. For that, we have to use a map to specify our type selector:

^javamodel('myId').enabletypes({ include: 'java.lang.*', exclude: '**Error' }, false);

Here you see the more general form of a selector: it really is a map that allows you to specify a variety of conditions that all together form the inclusion and exclusion criteria for the complete set, in our case a type set.

There are many other selection criteria you can use, for example a type's accessibility. To select all protected types in the java.util package in order to disable them, use:

^javamodel('myId').enabletypes({ include: 'java.util.*', protected: true }, false);

Type selectors are not the only game in town. You also have to be able to select methods and fields. Their selectors work totally analogous to type selectors, the only difference lies in the patterns they use and some of the criteria that only apply to them and not to types (volatile, synchronized, etc.).

Field selectors look exactly like type selectors with another name level tacked on. For example, to enable all fields in any type whose name ends with Data, use:

^javamodel('myId').enablefields('**Data.*', true);

Methods have parentheses and support a pattern for argument types as well. To disable all methods declared by the java.lang.Class type, use:

^javamodel('myId').enablemethods('java.lang.Class.*(**)', false);

By the way, if you look at the model state in the code generator GUI you might notice that the selectors that we introduced you to in this section are explicitly cast to ^javatypeselector() or ^javamethodselector() or ^javafieldselector() types. As long as you use a value that is usable as an initializer for these selectors, you do not have to explicitly specify the selector type. Pattern strings and maps are legal initializers for all selector types and the command will use them to internally construct the proper selector.

 

Why Use Advanced Selectors?

Before we provide a complete list of all selection criteria we need to talk about the reason for all this complexity.

The code generator GUI is a great tool for defining a prototype integration but you should really start thinking about your integration problem keeping the power features in mind that are only accessible through hand-crafted model files.

Yes, you can absolutely use the GUI to actively maintain your model file but consider the following scenario. Let's say you generate a proxy library for Java POJO types. For each of them, there is also a validator type (whose name always ends with Validator) that is only used internally and does not need to be exposed. Every now and then you add new POJOs and their corresponding validators, and each time you need to edit the model to disable the newly added validator type which is enabled by default.

How much simpler would it be to have the following line in your model file:

^javamodel('myId').enabletypes('com.myfirm.myproduct.**Validator', false);

Or let's say that the API that you wish to integrate with has a policy of putting implementation details into types that are declared in nested impl packages. You only want to generate C++ proxy types for the interface types but not for their implementation detail types. Rather than selecting every one of them by hand and disabling them individually, you could just say:

^javamodel('myId').enabletypes('com.myfirm.**.impl.**', false);

Another way of achieving the same intended result might be to disable all non-interface types in the package hierarchy you're interested in:

^javamodel('myId').enabletypes({ include: 'com.myfirm.**', interface: false }, false);

That is the power of a well-crafted model file. It can adjust dynamically and smartly to changes in your code base. You have to know the Java domain types well for that to work, but it is extremely powerful when used properly.

 

The table below contains the criteria that can be included in a map defining a type selector. All are optional. If you use more than one criterion in your type selector, the criteria are anded together to perform the selection.

Name
Description
abstract

Select abstract types. A boolean that should be true to select abstract types and false to select non-abstract types.

annotation

Select annotation types. A boolean that should be true to select annotation types and false to select other types.

class

Select class types. A boolean that should be true to select class types and false to select other types.

default

Select default accessible types. A boolean that should be true to select default accessible types and false to select other types.

enum

Select enum types. A boolean that should be true to select enum types and false to select other types.

exclude

A pattern or pattern list specifying fully qualified names of types that should not be selected.

include

A pattern or pattern list specifying fully qualified names of types that should be selected.

inner

Select inner (nested) types. A boolean that should be true to select inner types and false to select other types.

interface

Select interface types. A boolean that should be true to select interface types and false to select other types.

private

Select private types. A boolean that should be true to select privately accessible types and false to select other types.

protected

Select protected types. A boolean that should be true to select protected accessible types and false to select other types.

public

Select public types. A boolean that should be true to select publicly accessible types and false to select other types.

static

Select static types. A boolean that should be true to select static (nested) types and false to select other types.

 

The table below contains the criteria that can be included in a map defining a field selector. All are optional. If you use more than one criterion in your field selector, the criteria are anded together to perform the selection.

Name
Description
default

Select default accessible fields. A boolean that should be true to select default accessible fields and false to select other fields.

exclude

A pattern or pattern list specifying fully qualified names of fields that should not be selected.

include

A pattern or pattern list specifying fully qualified names of fields that should be selected.

private

Select private fields. A boolean that should be true to select privately accessible fields and false to select other fields.

protected

Select protected fields. A boolean that should be true to select protected accessible fields and false to select other fields.

public

Select public fields. A boolean that should be true to select publicly accessible fields and false to select other fields.

static

Select static fields. A boolean that should be true to select static fields and false to select other fields.

transient

Select transient fields. A boolean that should be true to select transient fields and false to select other fields.

volatile

Select volatile fields. A boolean that should be true to select volatile fields and false to select other fields.

 

The table below contains the criteria that can be included in a map defining a method selector. All are optional. If you use more than one criterion in your method selector, the criteria are anded together to perform the selection.

Name
Description
abstract

Select abstract methods. A boolean that should be true to select abstract methods and false to select non-abstract methods.

bridge

Select bridge methods. A boolean that should be true to select bridge methods and false to select non-bridge methods. You should never have to use this selector.

constructor

Select constructors. A boolean that should be true to select constructors and false to select non-constructor methods.

default

Select default accessible methods. A boolean that should be true to select default accessible methods and false to select other methods.

exclude

A pattern or pattern list specifying fully qualified names of fields that should not be selected.

include

A pattern or pattern list specifying fully qualified signatures of methods that should be selected.

Methods can be specified with wildcard arguments or with exact argument type matches. Constructors have to be specified by their class name. Examples of valid method patterns include:

  java.lang.String.String(**)
  java.lang.Object.wait(int,long)
        
method

Select methods (rather than constructors). A boolean that should be true to select methods and false to select non-methods (constructors).

native

Select native methods. A boolean that should be true to select native methods and false to select non-native methods.

private

Select private methods. A boolean that should be true to select privately accessible methods and false to select other methods.

protected

Select protected methods. A boolean that should be true to select protected accessible methods and false to select other methods.

public

Select public methods. A boolean that should be true to select publicly accessible methods and false to select other methods.

static

Select static methods. A boolean that should be true to select static methods and false to select other methods.

synchronized

Select synchronized methods. A boolean that should be true to select synchronized methods and false to select other methods. You should never have to use this selector.

synthetic

Select synthetic methods. A boolean that should be true to select synthetic methods and false to select other methods. You should never have to use this selector.

 

Modelfile properties govern all configurable aspects of code generator behavior. Whether you want to simply change the model's name or whether you want to modify the default heuristics for identifying callback types, it's all done by manipulating model properties.

Even though model properties frequently resolve to complex Java types, they are internally stored as string values and converted to their complex type when used. They can be configured in multiple ways. At first, you will most likely only interact with them via the Model Properties pane in the code generator GUI. The Model Properties pane offers a very convenient way to interactively edit model property values. Once you have made an edit, simply save the model and/or regenerate the model and you will immediately see the changes in the generated code and in the persisted model file.

Eventually, you will be ready to go beyond the initial setup of your integration project and graduate to automated, host-portable builds. You will need a more powerful mechanism that allows you to configure directories and files as variables. While the property values should always be strings, the string will be interpreted as a terp expression, thereby allowing the use of complex expressions.

You can override the default values by passing them to the code generator in whatever form is appropriate ( -D or -p options for the CLI and GUI versions, the corresponding elements for the ANT task). This is important to understand: a property value passed to the code generator overrides the default value for that property. Your model will only use the custom default value if it does not contain a setmodelproperty() call that replaces your custom default value.

Let's look at a real property that you might wish to set either in the model file or as a property default override. The jdk property governs among other things where the code generator looks for the built-in Java types. The built-in default value for the jdk property is ^jdk().

That looks confusingly like a recursive definition, but jdk is just a name whereas ^jdk() is a JDK object initializer that does not take any configuration arguments. It essentially means: "give me a JDK of your choice, any JDK you can find."

That might be totally fine for prototyping (as long as ^jdk() can find an installed JDK) but for your production build you probably want to be a bit more specific to avoid Java version issues.

You can be more specific by initializing the ^jdk() instance with a path or a version number, for example:

^jdk('C:/Program Files/Java/zulu-8')

That stands for: Give me the JDK installed at the location I gave you." With this in mind, your first try at a production model file might include a line similar to:

^javamodel('myId').setmodelproperty("jdk", "^jdk('C:/Program Files/Java/zulu-8')");

That works just fine, but by enshrining the jdk value in your model file you have now given up on ever being able to customize its value from the commandline. Whatever property override you give the code generator just sets the property's default value. When your model file is executed it will overwrite the default value with the path hardcoded within it.

A better solution might have been to leave the property untouched in the model file and always override it from the code generator. You could for example invoke your code generator as follows:

xmog "-Djdk=^jdk('C:/Program Files/Java/zulu-8')" myModel.jmm

Or even better, you can put all machine- or build-specific properties into a properties file and then invoke the code generator with that file. That approach will give you full portability across build hosts.