Generator User Guide Demo 4
In this demo, we will further evolve the demoLang3 generator (see Demo 3) and add support for a panel component. Unlike buttons or labels, panels can contain other components, including other panels. So we get recursion in the generator. We will see how reduction can help to solve problems of this kind.
We'll reuse a great part the demoLang3 generator in demoLang4.
- create new language 'generator_demo.demoLang4'
- in language properties dialog add extended languages : 'jetbrains.mps.sampleXML' and 'jetbrains.mps.baseLanguage'
- create a new generator for this language if it does not exist (see Demo 1 for details)
- delete the (empty) mapping configuration 'main' from the demoLang4 generator (we will copy all needed parts from the demoLang3 generator into the demoLang4 generator)
copy-paste all nodes from the demoLang3 generator into demoLang4 generator
Template for Panel
In-line with how buttons and labels are generated, we'll create a new template to generate code for panels - insert_Panel. As this template is going to be very similar to, say, insert_Label, we will create the new template as a copy of one of the existing ones.
- select (in the tree) the insert_Label template in the demoLang4 generator and invoke Clone Root command in the popup-menu
- in the editor - rename the new template: insert_Label -> insert_Panel
- in the template code - replace JLabel with JPanel
- remove the 'setText(...)' statement - panels don't support the 'text' property
- in the switch_Element template switch adds an entry for panels:
- re-generate the generator
Let's create a new test model:
- go to the 'test_models' solution
- clone model 'test3' to model 'test4'
- in the model properties dialog replace 'engaged on generation' language demoLang3 -> demoLang4 (See Demo 2 for details)
- in the model 'test4' add a new document 'Panel' with a root element - 'panel'
- add an attribute: 'background="white"' to the 'panel' element
- add a couple of 'label' elements to the 'panel' element:
- generate files for the 'test4' model
- preview the generated files
The generated UI clearly ignores that the 'Hello MPS!' labels should be nested inside the new panel. Instead, they are added to the frame directly. This is because our generator currently does not make any difference between root and non-root elements and simply turns them all into a top-level visual components. For each element in the input model the generator inserts a static method declaration 'createComponent()' into the 'DemoApp' class (which is perfectly okay) and generates a corresponding method call inside the 'addContent()' method in the 'DemoApp' class (which we need to change, since this should only be done for root elements).
The current implementation flattens our component hierarchy (all components are added directly to the application's content pane).
Restricting the LOOP macro to root elements
The first problem to attack is the LOOP macro in the DemoApp template, which should only iterate through root elements to generate container.add() calls for them. The elements nested inside panels, must not be included in the loop since it should be the containing panels that add them to the UI at some point later.
Let's alter the first LOOP macro. Notice also, that the second LOOP macro, which generated static method declarations for each Element, remains untouched.
Since we're now looping over Documents, not Elements, we need to alter the reference macro inside the LOOP.
If you rebuild the language and see the generated files, you'll notice that we're no longer adding the nested components into the top-most application frame.
However, we're not adding them into the panel, either. So this is what we'll fix now. We will use the COPY_SRC-macro for that.
COPY_SRC macros replace the wrapped dummy piece of template code with a node from the input model. If this node has a reduction rule defined, it will be reduced before being added into the output model. In our case, we need panels to have their child Elements correctly added as visual components to the panel's visual representation. If, for example, our concrete Panel get converted into a JPanel, we need its two child Labels to be converted into JLabels and these JLabels need to be added to the JPanel. The conversion part is already done for all Elements correctly irrespective of their nesting thanks to the second LOOP macro in the DemoApp template. The addition part is only done partially, thought. For the nested Elements, it must be the Panel that adds them.
- open insert_Panel
- add code to add null to the component
- wrap the whole statement (including the; character) with a LOOP macro that iterates over the content of the XML Panel
We obviously do not want to add nulls into the panel, do we? Now, the trick with the COPY_SRC macro - we'll add the current node, which refers to the Panel's child_ Element_ that we currently iterate over with the LOOP macro. The COPY_SRC macro will take care of converting Element into a swing component before being added to the component.
Thus, our input xml element can easily become something else after being 'copied' by the MPS generator. Now we will define what that 'something else' is going to be.
Reduction rules define transformations of source-language elements into target-language elements. For example, we want to transform XML Elements, which represent a Panel's children, into Java swing components that have been created by already generated static factory methods. In our particular case, we want to replace the Panel's children with calls to static methods. Luckily, the static methods to look for have been preserved in the method mapping label, so we can easily get hold of the correct one.
- go to the mapping configuration in the demoLang4 generator and create a Reduction Rule, which is applicable to Elements
- apply the intention (Alt + Enter) to create a new template for this rule
- open the 'reduce_Element' template in editor (Ctrl-click on reference)
- select ClassConcept as the rule content node
- add a static method to this class (name doesn't matter)
- create this method's call expression and make it a template fragment (Ctrl-Shift+F):
- add reference-macro for method name in method call expression - we'll retrieve the method declaration from the method mapping label:
Rebuild the generator and preview generated text for the 'test4' model.
This time both labels "Hello" and "MPS!" are correctly added to the content pane. We're done!