In Demo 2 we were generating java statements like:
to create a Swing component and add it to the application's content pane.
In 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, the generated property initialization code is going to be different for different types of components.
Therefore we will choose a generation strategy that is capable of handling such requirements.
Again, we need to setup a new language and copy some of the Demo 2 generator artifacts to it.
See Demo 2 for details.
The generator will resemble in many aspects the one we created in Demo 2. So we'll be using the conditional root rule to insert the DemoApp class into the output model. Initially, we need to make some tweaks to the DemoApp class.
Now, the addContent() method is supposed to add all components to the JFrame. So it will call container.add() and pass in initialized visual components in turn - one for each input XML Document. So we'll add a dummy method that we can use in the code and have the generator replace it with real component initialization code.
In order to have a component initialization method generated for each input Element, we will wrap the component() method declaration with a LOOP macro. The LOOP macro will iterate over all Elements in the input model and generate a method for each of them.
Similarly, we need to call these generated methods for each input Element and add the returned components into the container, so we need to use another LOOP macro around the call to the container.add(component()); method inside the addContent() method.
Last but not the least, we have to replace the call to the dummy component() method with a call to the real generated method corresponding to the same input Element. So select component (without the ending parentheses) and hit Control + Shift + M to insert a reference macro.
This is where we can't move forward just yet. We have no clue how to resolve the method declaration corresponding to the current node so that we could refer to it from the reference macro. We will employ mapping labels at this stage. They will serve as registries for method declarations, accessible by the Element that they were created for. So our reference macro will be able to retrieve the corresponding method declaration from there.
To define a mapping label, go back to the 'main' mapping configuration:
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.
will then have type: 'node<StaticMethodDeclarationt>'.
Now, back to DempApp, we can finish the reference macro:
Obviously now we need to complete the generation of method declarations and properly store them in the method mapping label.
Just like in Demo 2, we'll use a SWITCH macro to accommodate for different types of Elements when generating the methods for them. So right-click on the generator model and choose New -> template switch. Depending on the name of the Element, we either generate code for a button or for a label.
This time, since the generated code is more complex, in-line templates would not be enough to capture the logic, so we need standalone templates for both generating a button and a label - named insert_Button and insert_Label respectively. Create them by right-clicking on the generator model and choosing New -> template declaration.
Templates are snippets of the code to generate, parametrized with values calculated from the input model. In our case, we need to generate a new static method that will be added to the DemoApp class and will have a signature compatible with the component() method declared in the DemoApp class template. So what we'll do now for a button is to create a dummy class with a static method in it. This static method will be made a template fragment, so that only the method and not the whole class will be inserted into the target model.
First, choose ClassConcept as the content node:
Enter the code below declaring the static method with the signature that we want.
Now select the whole method declaration, hit Alt + Enter and pick Create Template Fragment from the menu. This will indicate that it is only our method declaration that should be used as a template. The surrounding class serves merely as context for the method.
This is the time when we register all methods generated with this template into the method mapping label, so that we can retrieve these later when generating calls to these methods. Simply enter the desired mapping label in the Inspector window for the template fragment (the <TF visual element).
At the moment we are generating methods for all input Elements with identical name - createComponent, which would cause Java compile-time troubles, if we have more than one Element in the input model. We need to give each generated method a unique name and we'll do it through a property macro.
We're using the genContext capabilities to generate unique identifiers. The templateValue parameter is going to be "createComponent" (i.e. the method's name as written in template).
Now, please, create template declaration for creating a JLabel.
Once done, we should be able to complete the SWITCH template declaration.
After fixing the SWITCH macro in the DempApp class template and making the language, we should be able to give our new generator a first try.
Let's create a new input test model:
When you hit Preview generated code, you should get a valid Java application.
Notice the uniqueness of the createComponent() methods.
Now, let's define the semantics for the extra XML attributes.
Let's focus on the insert_Button and insert_Label templates. These define the code that will initialize the Swing components based on the values specified in the input XML Element. So far we ignore all the attributes in the input model, but this will change now. Focusing on insert_Label for now, if the input Element has an attribute called 'text', we will generate a statement:
component.setText( _text_ )
where text is the string specified in the 'text' attribute of the input Element.
The same steps should now be repeated to the insert_Button 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.
A 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'. We'll create a "shared" template and re-use it in other templates using an INCLUDE-macro:
JComponent component = null;
The name of variable is important - generator will resolve references by this name.
We don't really have to use a block statement, but choosing so will make it easy for us to add more statements to this template fragment in the future.
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 posses the attribute 'enabled'.
This time, to achieve the conditional generation, instead of an IF-macro we will use a MAP_SRC-macro. As we will see, MAP_SRC-macros have several advantages over IF-macros in this case.
The mapped node function returns the node for the 'enabled' attribute, if present, and null otherwise. If the mapped node function in a MAP_SRC-macro returns null, the generator then ignores the node wrapped by this macro (just like an If macro would).
However, if the mapped node is not null, then it becomes the current input node, while processing the wrapped template code. This helps us get hold of the attribute making creation of the property-macro a bit easier compared to using an IF-macro - we don't have to find the 'enabled' attribute again, this attribute is already our input node.
Now, to set the correct value to the template:
We will use INCLUDE-macro to specify places in code, into which we want to insert the 'component.setEnabled(..);' statement during generation.
Generating code for the 'test3' model will render a valid Java application:
We correctly get the setEnabled() method generated only for the button, but not for the label.
A method-call node or variable-usage node contains a reference to a method or a variable declaration.
In many cases reference cannot be resolved automatically and this is where reference-macros come in handy.
For example, let's add support for the 'background' property into our generator:
In this example, the referent function has combined return type:
JOIN(node<StaticFieldDeclaration> | String)
having the eithor-or semantics. That means that we have two alternatives for what to return from the function:
The 2nd option, of course, is much more attractive. Thus we are returning a value of the 'background' attribute - the name of the desired color.
Now, after a language rebuild the 'test3' model will be generated into a Java application that now takes into account all the input element's attributes.