Child pages
  • Generator User Guide Demo3
Skip to end of metadata
Go to start of metadata

Generator User Guide Demo 3

In the previous Demo 2 we were generating java statements like:

to create Swing component and add it to the application's content pane.

In this Demo 3 we are going to add support for component properties, which will require generation of more complex initialization code - not just a constructor invocation.
Moreover, this code is going to be different for different types of components.
Therefore we will choose another generation strategy - instead of making use of SWITCH-macro we will use Weaving Rules which will 'inject' component's initialization code into the 'DemoApp' class.

New Language

  • create new language: 'generator_demo.L3'
  • in language properties dialog add extended language : 'jetbrains.mps.sampleXML'
  • create new generator for this language (see Demo 1 for details)
  • delete mapping configuration 'main' from L3 generator (as in Demo 2, we will copy needed parts from L2 generator to L3 generator)
  • copy-paste mapping configuration 'main' from L2 generator to L3 generator

    when pasting nodes - don't forget to exclude L2 generator model from imports

    Icon

    See Demo 2 for details.

  • copy-paste template 'DemoApp' from L2 generator to L3 generator

addContent(container)

  • open the 'DemoApp' template
  • add static method 'addContent(Container)'
  • in the 'main()' method find the statement:
  • replace the statement above with statement below:

New Test Model

Before going on with generator let's create new input test model:

  • go to the 'test_models' solution
  • clone model 'test2' to model 'test3'
  • in the model properties dialog replace 'engaged on generation' language L2 -> L3 (see Demo 2 for details)
  • open the 'Button' document (from model 'test3') in editor
  • add attribute 'text="Hello"' to the 'button' element
  • add attribute 'text="world!"' to 'label' element in the 'Label' document

Now let's define semantic for these attributes.

Weaving Rules

  • return to L3 generator and open the mapping configuration 'main' in editor
  • create new Weaving Rule (press Insert while cursor is in the weaving rules section)
  • choose the rule applicable concept - 'Element'
  • enter the rule condition as shown below:

Weaving Context

Weaving Rule injects a generated code into other generated code.
The Weaving Context is the exact place to which the Weaving Rule will to inject the code. (Injected nodes are going to be children of the context node).

In this demo we will inject component's set-up code into our 'DemoApp' class.
Thus, we have to find generated 'DemoApp' class in output model and pass this class to our weaving rule as its weaving context.

Mapping Label

We will use mapping label to find the generated 'DemoApp' node in output model.

Declaring Mapping Label

Before mapping label can be used it must be declared in mapping configuration:

  • go to the mapping labels section
  • add new mapping label (press Insert)
  • give it name: 'main_class'
  • select 'ClassConcept' as the label's output concept

mapping label

Icon

Mapping label's input concept and output concept are optional parameters which are used to provide static type checking in expressions which are making use of mapping label to find output node.
In our case we are going to attach mapping label to rule which generates class 'Demo App' (Conditional Root Rule).
That's why we have chosen 'ClassConcept' as the label's output concept.
The expression (we will use it soon):

will then have type: 'node<ClassConcept>'.
The fact that the 'get output' expression has type, provides no benefits for us in this particular case, but in general it is very helpful feature.

Attaching Label to Rule

Attach mapping label 'main_class' to Conditional Root Rule using the rule's inspector:

Using Label for Finding of Output Node

Return to Weaving Rule and enter the following code into the context function:

External Template

Now let's create a template for this weaving rule:

  • type 'weave_Button' in the red 'choose consequence' cell
  • press Alt-Enter and choose 'New Template' (apply intention)
  • open the template in editor (use Ctrl-<left-click> or Ctrl+B on reference cell)

The template should already have a Class set as the template's content node.
If not - choose 'ClassConcept' as the template's content node:

  • give the class a name and delete its constructor

    note

    Icon

    You don't really have to give class a name or delete its constructor. This class is not going to be generated.
    This class will be serving as a context or place where we can put code that will be actually generated.
    We will mark this 'real code' with template fragment tags.

  • add static method 'createComponent()'
  • create template fragment: select the whole method declaration and press Ctrl-Shit+F :

    template fragment in weaving rules

    Icon

    Code tagged as template fragment (method declaration in this case) is inserted into weaving context node ('DemoApp' class) during a generation process.

IF-macro

Inside the 'createComponent()' method we are going to create and initialize the JButton component.
If the input Element has attribute 'text', then we will generate statement:

where text is the string specified in the 'text' attribute in input Element.

  • enter the following code in the body of 'createComponent()' method
  • create IF-macro around the 'component.setText("text");' statement (select whole (warning) statement, press Ctrl-Shift+M)
  • enter code into the condition function of IF-macro which will check presence of the 'text' attribute

  • create property-macro inside the string literal "text"
  • in value function of the property-macro enter code returning value of attribute 'text' in the input Element

Complete Generator

To complete this step we have to create another one Weaving Rule which is applicable to 'label' Element.

  • open mapping configuration 'main' in editor
  • select weaving rule which we have created for 'button' (select whole node: put cursor inside the rule, use Ctrl+W to inflate selection)
  • press Ctrl+D (duplicate) to create identical rule
  • in condition function, in the statement
    replace "button" with "label"
  • in project tree select template node 'weave_Button'
  • duplicate this template node using Clone Root command in popup menu
  • in editor rename this new template to 'weave_Label'
  • in template code replace the statement
    with
  • re-auto-complete 'setText' in statement
  • attach template 'weave_Label' to second weaving rule in mapping configuration 'main'
  • re-generate generator model

First Test (Error)

Try to generate files from model 'test3'.
Generation should run with no problems but compilation will fail with error:

Click on the error message to view the error in generated code:

The problem is that our weaving rules are always injecting method declaration with the same name: 'createComponent()'

Generating Unique Names

To make name of each generated 'createComponent()' method unique we will create another property-macro:

  • open the 'weave_Button' template in editor
  • add property-macro to the name of 'createComponent()' method
  • enter code in its value function as shown

The templateValue is going to be "createComponent" (i.e. the method's name as written in template).

  • make similar changes in the 'weave_Label' template
  • re-generate generator model

Second Test

Generate files form model 'test3' - should not be any error messages now.

Switch to the 'generator_demo_idea' project in IntelliJ IDEA and review the generated class 'generator_demo.test3.DemoApp':

The code has no compilation problems but it is still not working because body of the 'addContent()' method is empty.
Our next step will be weaving of the code:

into the body of the 'addContent()' method.

Second Template Fragment

To generate method call of the 'createComponent()' method we will add second template fragment into our weaving templates:

  • open the 'weave_Button' template and add second static method
  • name the method: 'addContent'

    note

    Icon

    The name of this method is not important but we will name it 'addContent' for clarity.
    This method is not going to be generated - it will be serving as context for a template fragment inside it.

  • add parameter 'Container container' to the method declaration ((warning) This parameter must have the same name as parameter in 'addContent()' method in the 'DemoApp' template)
  • in the method body enter code:
  • tag this statement as a template fragment (select whole statement(warning), press Ctrl-Shift+F):

Specifying Weaving Context for the Second Template Fragment

The weaving context (i.e. parent node in output model) for this statement is going to be a body of the generated 'addContent()' method in 'DemoApp' class.
We have to specify this in fragment context function of our template fragment:

This code will find 'addContent()' method in generated 'DemoApp' class (this class is the mainContextNode here) and return the method's body node.

Complete Generator

  • add identical 'addContent' method with template fragment to the 'weave_Label' template
  • re-generate generator model

Third Test

Generate files from model 'test3' in 'test_models' solution.
Switch to 'generator_demo_idea' project in IntelliJ IDEA and run the 'generator_demo.test3.DemoApp application':

Reusable template

We can go on adding in the same manner support for more and more component properties, but this way we are going to end up with a lot of duplicated code in our templates.

The better idea is to create one template containing some common code and re-use it in template of each component.
For example, let's add support for the property 'enabled'.
Let's create a "shared" template and re-use it in other templates using an INCLUDE-macro:

  • go to the 'main@generator' model in L3 generator (select in project tree)
  • create new template declaration node using Create Root Node in model popup menu
  • name it 'include_ComponentProperties'
  • choose input - Element
  • choose StatementList as the template's content node:

  • in the statement list create variable declaration:
    Icon

    The name of variable is important - generator will resolve references by this name.

  • add block-statement (press <Enter> after the variable declaration, type '{', press Ctrl+<space> to auto-complete)
  • create statement:
    inside the block-statement
  • mark the block-statement as a template fragment:

why use the _block-statement_?

Icon

We don't really have to use it but block-statement will make it easy for us to add more statements to this template fragment in the future.

MAP_SRC-macro

As in case of the 'text' property, the 'setEnabled()' method call generation should be conditional - this statement should only be generated if the input element has attribute 'enabled'.

This time, to achieve the conditional generation, instead of IF-macro we will use MAP_SRC-macro.
As we will see, MAP_SRC-macro has some advantages over IF-macro in this case.

  • create a MAP_SRC-macro (it should wrap whole statement(warning))
  • enter code in the macro's mapped node function as shown:

The mapped node function returns the 'enabled' attribute if present, and null otherwise. If mapped node function in a MAP_SRC-macro returns null then generator ignores the node wrapped by this macro.
If the mapped node is not null, then it becomes current input node while processing the wrapped template node.

Now creation of property-macro for actual argument is a bit easier then when we were using IF-macro - we don't have to find the 'enabled' attribute again, this attribute is already our input node.

  • attach property-macro to the boolean constant 'false'
  • enter code into its value function (note that this time the value function expects a boolean return value):

INCLUDE-macro

We will use INCLUDE-macro to specify place in code where we want to insert statement 'component.setEnabled(..);' on generation.

  • open the 'weave_Button' template in editor
  • add statement
    just after the statement
  • wrap the 'null;' statement (whole statement(warning)) into INCLUDE-macro
  • make reference to the 'include_ComponentProperties' template in the macro's inspector:

  • create similar macro in the 'weave_Label' template
  • re-generate generator model (Shift+F9)

    role of the 'null;' statement

    Icon

    This statement plays role of an anchor to which INCLUDE-macro can be attached.
    Actually, it doesn't have to be 'null;', any statement goes - it will be ignored by generator anyway.

Forth Test

Open the 'Button' document in model 'test3' in 'test_models' solution and add attribute 'enabled' to the root element:

Generate model 'test3' and run DemoApp - the button 'Hello' should be disabled.

Reference-macro (Resolving by Name)

So far all references in generated code have been smoothly resolved by MPS generator.

what references?

Icon

A method-call node or variable-usage node contains reference on a method or variable declaration.

Yet, in many cases reference can not be resolved automatically and this is where reference-macro comes in handy.

For example, let's add support for property 'background' to our generator:

  • open the 'include_ComponentProperties' template (it is our re-usable template)
  • add block-statement just after the 'component.setEnabled(false);' statement
  • add enter following code inside that block-statement:
  • wrap block-statement in MAP_SRC-macro
  • enter code into the macro's mapped node function as shown:
  • attach reference-macro to static field 'black' (in 'Color.black' expression)
  • enter code inside the referent function:

In this example, referent function has return type:

That means that we have two alternatives:

  1. we can find required static field declaration in class 'Color' and return that node
  2. or we can return a reference info string - name of static field declaration (name of color)

The 2nd option, of course, is much more attractive. Thus we are returning value of 'background' attribute - name of color.

Fifth Test

  • re-generate generator model
  • open the 'Label' document in model 'test3' in 'test_models' solution
  • add attribute 'background':
  • generate model 'test3' and run DemoApp:

End of Demo 3

  • No labels