3.6. SDA example using snippets

This example demonstrates how a template engine can be used to create code. In the Hello World example (Tutorial 01) all code is created by a XML code instruction.

Although XML is great to define the structure of a class it isn’t well suited for creating the body of a method when the method needs to access the model object. This is why a snippet (a freemarker or velocity template) can be used in MetaFactory. To use a snippet the following expressions can be used:

${fmsnippet.path.to.your.freemarker.template}

or

${snippet.path.to.your.velocity.template}

3.6.1. Code Instruction

With the following code instruction, we create pojo classes from a model and use Freemarker to create the toString, equals and hashcode methods:

Listing 3.6 code instruction to create POJO classes
  1<?xml version="1.0" encoding="UTF-8"?>
  2<code_instruction
  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4        xmlns="https://metafactory.io/xsd/v1/codeinstruction"
  5        xsi:schemaLocation="https://metafactory.io/xsd/v1/codeinstruction
  6                            https://metafactory.io/xsd/v1/codeinstruction.xsd
  7                            https://metafactory.io/xsd/v1/java-codeinstruction
  8                            https://metafactory.io/xsd/v1/java-codeinstruction.xsd
  9                            https://metafactory.io/xsd/v1/typescript-codeinstruction
 10                            https://metafactory.io/xsd/v1/typescript-codeinstruction.xsd"
 11        xmlns:typescript="https://metafactory.io/xsd/v1/typescript-codeinstruction"
 12        xmlns:java="https://metafactory.io/xsd/v1/java-codeinstruction">
 13
 14
 15        <properties>
 16                <java.main.directory>src/main/java/</java.main.directory>
 17                <conf.main.directory>src/main/resources</conf.main.directory>
 18                <java.test.directory>src/test/java/</java.test.directory>
 19                <conf.test.directory>src/test/resources</conf.test.directory>
 20
 21                <base>org.metafactory.example.usingsnippets</base>
 22
 23                <model.implementation.package>${pattern.property.base}.model.implementation</model.implementation.package>
 24                <model.implementation.class>${object.name}</model.implementation.class>
 25                <model.implementation.reference>${reference.type}</model.implementation.reference>
 26                <model.implementation.fqcn>${pattern.property.model.implementation.package}.${pattern.property.model.implementation.class}</model.implementation.fqcn>
 27        </properties>
 28
 29        <!--
 30        A java package for creating simple pojo's with equals, hashcode, toString, but without jpa annotations.
 31        -->
 32        <java_package
 33                name="${pattern.property.model.implementation.package}"
 34                path="${pattern.property.java.main.directory}"
 35                package="domain_model"
 36                skip="true">
 37                <class name="${pattern.property.model.implementation.class}" visibility="public" enum="${model.property.object.enum}" foreach="object">
 38                        <import>
 39                        </import>
 40                        <enumConstants condition="${model.property.object.enum}==true">${fmsnippet.java.pojo.enum.enumConstants}</enumConstants>
 41                        <field name="${attribute.name}" foreach="attribute" access="rw">
 42                                <datatype>${attribute.type}</datatype>
 43                                <apicomment>${fmsnippet.java.pojo.attribute.apicomment}</apicomment>
 44                                <body condition="${propertyExists(${model.property.attribute.entity.default.value})}">${model.property.attribute.entity.default.value}</body>
 45                                <setterBody>${fmsnippet.java.pojo.operation.setter}</setterBody>
 46                        </field>
 47                        <field name="${reference.name}" foreach="reference" access="rw" condition="${fmsnippet.condition.reference_1_and_enum}">
 48                                <datatype>${pattern.property.model.implementation.reference}</datatype>
 49                        </field>
 50                        <field name="${reference.name}" foreach="reference" access="rw" condition="${fmsnippet.condition.reference_1_no_enum}"> <!-- ${reference.multiplicity}=0..1 OR ${reference.multiplicity}=1..1 -->
 51                                <datatype>${pattern.property.model.implementation.reference}</datatype>
 52                        </field>
 53                        <field name="${reference.name}Set" foreach="reference" access="rw" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
 54                                <library>java.util.HashSet</library>
 55                                <library>java.util.Set</library>
 56                                <datatype>
 57                                        <![CDATA[Set<${pattern.property.model.implementation.reference}>]]>
 58                                </datatype>
 59                                <body>
 60                                        <![CDATA[new HashSet<${pattern.property.model.implementation.reference}>()]]>
 61                                </body>
 62                        </field>
 63                        <method name="constructor" visibility="${fmsnippet.attribute.visibility_of_constructor}" foreach="currentModelObject.property.createConstructor" var0="${forEachPropertyValue}">
 64                                <parameter name="${attribute.name}" foreach="attribute.property.useInConstructor" condition="${forEachPropertyValue}=${var0}">
 65                                        <datatype> ${attribute.type}</datatype>
 66                                </parameter>
 67                                <parameter name="${reference.name}" foreach="reference.property.useInConstructor" condition="${forEachPropertyValue}=${var0}">
 68                                        <datatype> ${reference.type}</datatype>
 69                                </parameter>
 70                                <body>${fmsnippet.java.pojo.operation.constructor.constructor-use-fields} </body>
 71                        </method>
 72                        <method name="addTo${firstUpper(${reference.name})}" foreach="reference" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
 73                                <parameter name="${firstLower(${reference.name})}">
 74                                        <datatype>${pattern.property.model.implementation.reference}</datatype>
 75                                </parameter>
 76                                <body>${fmsnippet.java.pojo.operation.addToSet}</body>
 77                        </method>
 78                        <method name="deleteFrom${firstUpper(${reference.name})}" foreach="reference" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
 79                                <parameter name="${firstLower(${reference.name})}">
 80                                        <datatype>${pattern.property.model.implementation.reference}</datatype>
 81                                </parameter>
 82                                <body>${fmsnippet.java.pojo.operation.deleteFromSet}</body>
 83                        </method>
 84                        <method name="equals" visibility="public" condition="${model.property.object.enum}!=true">
 85                                <!--  condition="${model.property.object.enum}!=true" -->
 86                                <apicomment>FIXME define businesskeys in model</apicomment>
 87                                <parameter name="return">
 88                                        <datatype>boolean</datatype>
 89                                </parameter>
 90                                <parameter name="${firstLower(${object.name})}">
 91                                        <datatype>${pattern.property.model.implementation.class}</datatype>
 92                                </parameter>
 93                                <body>${fmsnippet.java.pojo.operation.equals.equals-use-businesskey}</body>
 94                        </method>
 95                        <method name="hashCode" condition="${model.property.object.enum}!=true">
 96                                <apicomment>FIXME define businesskeys in model</apicomment>
 97                                <parameter name="return">
 98                                        <datatype>integer</datatype>
 99                                </parameter>
100                                <body>${fmsnippet.java.pojo.operation.hashcode.hashCode-use-businesskey}</body>
101                        </method>
102                        <method name="toString">
103                                <parameter name="return">
104                                        <datatype>String</datatype>
105                                </parameter>
106                                <!--
107                                <body>${fmsnippet.java.pojo.operation.tostring.toString-use-businesskey}</body>
108                                -->
109                                <body>${fmsnippet.java.pojo.operation.tostring.toString-use-all-attributes}</body>
110                        </method>
111                </class>
112        </java_package>
113</code_instruction>

3.6.2. Model

This is the model used:

Listing 3.7 model.xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<model xmlns="https://metafactory.io/xsd/v1/model"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="https://metafactory.io/xsd/v1/model
 5                           https://metafactory.io/xsd/v1/model.xsd">
 6
 7        <package name="domain_model">
 8                <object name="Person">
 9                        <metadata>
10                                <enum>false</enum>
11                                <name.plural>Persons</name.plural>
12                        </metadata>
13                        <field name="firstName" type="String" length="50" notnull="true" >
14                                <metadata>
15                                        <businesskey>2</businesskey>
16                                </metadata>
17                        </field>
18                        <field name="lastName" type="String" length="50" notnull="true" >
19                                <metadata>
20                                        <businesskey>1</businesskey>
21                                </metadata>
22                        </field>
23                        <reference name="homeAddress" type="Address" notnull="true" multiplicity="1..1" />
24                        <reference name="workAddress" type="Address" notnull="false" multiplicity="0..1" />
25                        <reference name="phone" type="Phone" notnull="false" multiplicity="0..n" />
26                </object>
27                <object name="Address">
28                        <metadata>
29                                <enum>false</enum>
30                                <name.plural>Addresses</name.plural>
31                        </metadata>
32                        <field name="streetName" type="String" length="100" notnull="true" />
33                        <field name="zipCode" type="String" length="10" notnull="false" >
34                                <metadata>
35                                        <businesskey>1</businesskey>
36                                </metadata>
37                        </field>
38                        <field name="city" type="String" length="50" notnull="true" />
39                        <field name="country" type="String" length="50" notnull="false" >
40                                <metadata>
41                                        <businesskey>2</businesskey>
42                                </metadata>
43                        </field>
44                </object>
45                <object name="Phone">
46                        <metadata>
47                                <enum>false</enum>
48                                <name.plural>Phones</name.plural>
49                        </metadata>
50                        <field name="number" type="String" length="30" notnull="true" >
51                                <metadata>
52                                        <businesskey>1</businesskey>
53                                </metadata>
54                        </field>
55                        <field name="description" type="String" length="100" notnull="true" />
56                        <reference name="person" type="Person" notnull="true" multiplicity="1..1" />
57                </object>
58        </package>
59</model>

3.6.3. Project

For completeness, here is the MetaFactory project file used:

Listing 3.8 Project
 1<?xml version="1.0" encoding="UTF-8"?>
 2<personal-iom-project xmlns="http://www.firstbase.nl/xsd/personaliom/project"
 3                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4                      xsi:schemaLocation="http://www.firstbase.nl/xsd/personaliom/project
 5                                          http://www.firstbase.nl/xsd/personaliom/project.xsd"
 6                      allowDeprecated="false">
 7
 8        <model>model.xml</model>
 9        <pattern>pattern.xml</pattern>
10        <snippetsFolder>snippets</snippetsFolder>
11        <output>
12                <path type="java"></path>
13                <path type="xml"></path>
14                <path type="file"></path>
15        </output>
16        <businessStyle>
17                <createDefaultJavaDocForClass>true</createDefaultJavaDocForClass>
18                <addAuthorToDefaultJavaDocForClass>true</addAuthorToDefaultJavaDocForClass>
19                <createDefaultJavaDocForInterface>true</createDefaultJavaDocForInterface>
20                <addAuthorToDefaultJavaDocForInterface>true</addAuthorToDefaultJavaDocForInterface>
21        </businessStyle>
22</personal-iom-project>

3.6.4. Phone class created

This is the Phone class, created by MetaFactory. The toString method is highlighted:

Listing 3.9 Phone class
 1package org.metafactory.example.usingsnippets.model.implementation;
 2
 3/**
 4* Phone - Created by MetaFactory: Automation of Software Development
 5*
 6* @author - marnix
 7*/
 8public class Phone
 9{
10        private String number;
11
12        private String description;
13
14        private Person person;
15
16// Getters and setters omitted
17
18        /**
19        * toString -
20        *
21        * @return String
22        */
23        public String toString()
24        {
25                StringBuilder result = new StringBuilder("Phone: ");
26
27                result.append("number=" + number);
28                result.append(", ");
29
30                result.append("description=" + description);
31
32                return result.toString();
33        }
34
35                /**
36                * equals - Fields used as businesskey: 1) number.
37                *
38                * @param phone
39                * @return boolean
40                */
41        public boolean equals(final Phone phone)
42        {
43                boolean result = true;
44                // If all business keys are null, return false
45                if (this.getNumber() == null)
46                {
47                        result = false;
48                }
49                else
50                {
51                        if (this.getNumber() != null && !this.getNumber().equals(phone.getNumber()))
52                        {
53                                result = false;
54                        }
55                }
56                return result;
57        }
58
59        /**
60        * hashCode - Fields used as businesskey: 1) number.
61        *
62        * @return integer
63        */
64        public int hashCode()
65        {
66                int result;
67                result = 14;
68
69                if (number != null)
70                {
71                        result = 29 * result + this.getNumber().hashCode();
72                }
73
74                return result;
75        }
76}

3.6.5. Freemarker snippet

The following Freemarker snippet is used to create this toString method as declared at line 109 in Listing 3.6.

Listing 3.10 Freemarker snippet
 1<#--stop if $currentModelObject is null-->
 2<#if !(currentModelObject)??>  <#stop "currentModelObject not found in context" ></#if>
 3
 4<#assign modelObjectName = currentModelObject.getAttributeValue("name")>
 5<#assign modelObjectNameFL = modelObjectName?uncap_first>
 6
 7  StringBuilder result = new StringBuilder("${modelObjectName}: ");
 8
 9<#assign attributes = currentModelObject.getChildren("attribute", nsModel)>
10<#list attributes as attribute>
11  <#assign attributeName = attribute.getAttributeValue( "name")>
12  <#assign attributeType = attribute.getAttributeValue( "type")>
13  <#assign javaType = generator.getJavaType(attributeType)>
14  <#-- Add a , after first iteration of this loop using index variable created by freemarker -->
15  <#if (attribute_index > 0) >
16        result.append(", ");
17  </#if>
18
19  <#if (generator.isPrimitiveJavaType(attributeType)) >
20        result.append("${attributeName}=" + ${attributeName}); // I am primitive
21  <#else>
22  <#-- handle primitive types other than objects -->
23        <#if (javaType == "String") >
24          result.append("${attributeName}=" + ${attributeName});
25        <#else>
26          result.append("${attributeName}=" + ${attributeName}.toString());
27        </#if>
28  </#if>
29</#list>
30
31return result.toString();

3.6.6. Evaluation

With the above template the developer coded exactly how the toString method must be created for all classes.

With MetaProgrammimg the developer creates all methods at a meta level.

The MetaFactory project can be downloaded here: example-using-snippets