Model Files
General Format
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
Java Model Commands
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:
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.
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.
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.
Element Selectors
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.
Type Selectors
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.
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.
Field Selectors
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.
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.
Method Selectors
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.
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.
Java Model Properties
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.