3.6. SDA example using snippets

This example demonstrates how a template engine can be used to create code. In the previous HelloWorld examples all code is created by a XML pattern. 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. Pattern

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

Listing 3.6 Pattern to create POJO classes
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
<?xml version="1.0" encoding="UTF-8"?>
<pattern xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://www.firstbase.nl/xsd/personaliom/pattern"
         xsi:schemaLocation="http://www.firstbase.nl/xsd/personaliom/pattern
                             http://www.firstbase.nl/xsd/personaliom/pattern.xsd">

        <properties>
        <java.main.directory>src/main/java/</java.main.directory>
        <conf.main.directory>src/main/resources</conf.main.directory>
        <java.test.directory>src/test/java/</java.test.directory>
        <conf.test.directory>src/test/resources</conf.test.directory>

        <base>org.metafactory.example.usingsnippets</base>

        <model.implementation.package>${pattern.property.base}.model.implementation</model.implementation.package>
        <model.implementation.class>${object.name}</model.implementation.class>
        <model.implementation.reference>${reference.type}</model.implementation.reference>
        <model.implementation.fqcn>${pattern.property.model.implementation.package}.${pattern.property.model.implementation.class}</model.implementation.fqcn>
        </properties>

        <!--
        A pattern package for creating simple pojo's with equals, hashcode, toString, but without jpa annotations.
        -->
        <package
                name="${pattern.property.model.implementation.package}"
                path="${pattern.property.java.main.directory}"
                package="domain_model"
                skip="true">
                <class name="${pattern.property.model.implementation.class}" visibility="public" enum="${model.property.object.enum}" foreach="object">
                        <libraries>
                        </libraries>
                        <enumConstants condition="${model.property.object.enum}==true">${fmsnippet.java.pojo.enum.enumConstants}</enumConstants>
                        <attribute name="${attribute.name}" foreach="attribute" access="rw">
                                <datatype>${attribute.type}</datatype>
                                <apicomment>${fmsnippet.java.pojo.attribute.apicomment}</apicomment>
                                <body condition="${propertyExists(${model.property.attribute.entity.default.value})}">${model.property.attribute.entity.default.value}</body>
                                <setterBody>${fmsnippet.java.pojo.operation.setter}</setterBody>
                        </attribute>
                        <attribute name="${reference.name}" foreach="reference" access="rw" condition="${fmsnippet.condition.reference_1_and_enum}">
                                <datatype>${pattern.property.model.implementation.reference}</datatype>
                        </attribute>
                        <attribute name="${reference.name}" foreach="reference" access="rw" condition="${fmsnippet.condition.reference_1_no_enum}"> <!-- ${reference.multiplicity}=0..1 OR ${reference.multiplicity}=1..1 -->
                                <datatype>${pattern.property.model.implementation.reference}</datatype>
                        </attribute>
                        <attribute name="${reference.name}Set" foreach="reference" access="rw" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
                                <library>java.util.HashSet</library>
                                <library>java.util.Set</library>
                                <datatype>
                                        <![CDATA[Set<${pattern.property.model.implementation.reference}>]]>
                                </datatype>
                                <body>
                                        <![CDATA[new HashSet<${pattern.property.model.implementation.reference}>()]]>
                                </body>
                        </attribute>
                        <operation name="constructor" visibility="${fmsnippet.attribute.visibility_of_constructor}" foreach="currentModelObject.property.createConstructor" var0="${forEachPropertyValue}">
                                <parameter name="${attribute.name}" foreach="attribute.property.useInConstructor" condition="${forEachPropertyValue}=${var0}">
                                        <datatype> ${attribute.type}</datatype>
                                </parameter>
                                <parameter name="${reference.name}" foreach="reference.property.useInConstructor" condition="${forEachPropertyValue}=${var0}">
                                        <datatype> ${reference.type}</datatype>
                                </parameter>
                                <body>${fmsnippet.java.pojo.operation.constructor.constructor-use-fields} </body>
                        </operation>
                        <operation name="addTo${firstUpper(${reference.name})}" foreach="reference" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
                                <parameter name="${firstLower(${reference.name})}">
                                        <datatype>${pattern.property.model.implementation.reference}</datatype>
                                </parameter>
                                <body>${fmsnippet.java.pojo.operation.addToSet}</body>
                        </operation>
                        <operation name="deleteFrom${firstUpper(${reference.name})}" foreach="reference" condition="${reference.multiplicity}=0..n OR ${reference.multiplicity}=1..n">
                                <parameter name="${firstLower(${reference.name})}">
                                        <datatype>${pattern.property.model.implementation.reference}</datatype>
                                </parameter>
                                <body>${fmsnippet.java.pojo.operation.deleteFromSet}</body>
                        </operation>
                        <operation name="equals" visibility="public" condition="${model.property.object.enum}!=true">
                                <!--  condition="${model.property.object.enum}!=true" -->
                                <apicomment>FIXME define businesskeys in model</apicomment>
                                <parameter name="return">
                                        <datatype>boolean</datatype>
                                </parameter>
                                <parameter name="${firstLower(${object.name})}">
                                        <datatype>${pattern.property.model.implementation.class}</datatype>
                                </parameter>
                                <body>${fmsnippet.java.pojo.operation.equals.equals-use-businesskey}</body>
                        </operation>
                        <operation name="hashCode" condition="${model.property.object.enum}!=true">
                                <apicomment>FIXME define businesskeys in model</apicomment>
                                <parameter name="return">
                                        <datatype>integer</datatype>
                                </parameter>
                                <body>${fmsnippet.java.pojo.operation.hashcode.hashCode-use-businesskey}</body>
                        </operation>
                        <operation name="toString">
                                <parameter name="return">
                                        <datatype>String</datatype>
                                </parameter>
                                <!--
                                <body>${fmsnippet.java.pojo.operation.tostring.toString-use-businesskey}</body>
                                -->
                                <body>${fmsnippet.java.pojo.operation.tostring.toString-use-all-attributes}</body>
                        </operation>
                </class>
        </package>
</pattern>

3.6.2. Model

This is the model used:

Listing 3.7 Model used
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?xml version="1.0" encoding="UTF-8"?>
<model xmlns="https://metafactory.io/xsd/v1/model"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://metafactory.io/xsd/v1/model
                           https://metafactory.io/xsd/v1/model.xsd">

        <package name="domain_model">
                <object name="Person">
                        <metadata>
                                <enum>false</enum>
                                <name.plural>Persons</name.plural>
                        </metadata>
                        <attribute name="firstName" type="String" length="50" notnull="true" >
                                <metadata>
                                        <businesskey>2</businesskey>
                                </metadata>
                        </attribute>
                        <attribute name="lastName" type="String" length="50" notnull="true" >
                                <metadata>
                                        <businesskey>1</businesskey>
                                </metadata>
                        </attribute>
                        <reference name="homeAddress" type="Address" notnull="true" multiplicity="1..1" />
                        <reference name="workAddress" type="Address" notnull="false" multiplicity="0..1" />
                        <reference name="phone" type="Phone" notnull="false" multiplicity="0..n" />
                </object>
                <object name="Address">
                        <metadata>
                                <enum>false</enum>
                                <name.plural>Addresses</name.plural>
                        </metadata>
                        <attribute name="streetName" type="String" length="100" notnull="true" />
                        <attribute name="zipCode" type="String" length="10" notnull="false" >
                                <metadata>
                                        <businesskey>1</businesskey>
                                </metadata>
                        </attribute>
                        <attribute name="city" type="String" length="50" notnull="true" />
                        <attribute name="country" type="String" length="50" notnull="false" >
                                <metadata>
                                        <businesskey>2</businesskey>
                                </metadata>
                        </attribute>
                </object>
                <object name="Phone">
                        <metadata>
                                <enum>false</enum>
                                <name.plural>Phones</name.plural>
                        </metadata>
                        <attribute name="number" type="String" length="30" notnull="true" >
                                <metadata>
                                        <businesskey>1</businesskey>
                                </metadata>
                        </attribute>
                        <attribute name="description" type="String" length="100" notnull="true" />
                        <reference name="person" type="Person" notnull="true" multiplicity="1..1" />
                </object>
        </package>
</model>

3.6.3. Project

For completeness, here is the MetaFactory project file used:

Listing 3.8 Project
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<personal-iom-project
        xmlns="http://www.firstbase.nl/xsd/personaliom/project"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.firstbase.nl/xsd/personaliom/project
                            http://www.firstbase.nl/xsd/personaliom/project.xsd" allowDeprecated="true">

        <model>model.xml</model>
        <pattern>pattern.xml</pattern>
        <snippetsFolder>snippets</snippetsFolder>
        <output>
                <path type="java"></path>
                <path type="xml"></path>
                <path type="file"></path>
        </output>
        <businessStyle>
                <createDefaultJavaDocForClass>true</createDefaultJavaDocForClass>
                <addAuthorToDefaultJavaDocForClass>true</addAuthorToDefaultJavaDocForClass>
                <createDefaultJavaDocForInterface>true</createDefaultJavaDocForInterface>
                <addAuthorToDefaultJavaDocForInterface>true</addAuthorToDefaultJavaDocForInterface>
        </businessStyle>
</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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package org.metafactory.example.usingsnippets.model.implementation;

/**
* Phone - Created by MetaFactory: Automation of Software Development
*
* @author - marnix
*/
public class Phone
{
        private String number;

        private String description;

        private Person person;

// Getters and setters omitted

        /**
        * toString -
        *
        * @return String
        */
        public String toString()
        {
                StringBuilder result = new StringBuilder("Phone: ");

                result.append("number=" + number);
                result.append(", ");

                result.append("description=" + description);

                return result.toString();
        }

                /**
                * equals - Fields used as businesskey: 1) number.
                *
                * @param phone
                * @return boolean
                */
        public boolean equals(final Phone phone)
        {
                boolean result = true;
                // If all business keys are null, return false
                if (this.getNumber() == null)
                {
                        result = false;
                }
                else
                {
                        if (this.getNumber() != null && !this.getNumber().equals(phone.getNumber()))
                        {
                                result = false;
                        }
                }
                return result;
        }

        /**
        * hashCode - Fields used as businesskey: 1) number.
        *
        * @return integer
        */
        public int hashCode()
        {
                int result;
                result = 14;

                if (number != null)
                {
                        result = 29 * result + this.getNumber().hashCode();
                }

                return result;
        }
}

3.6.5. Freemarker snippet

The following Freemarker snippet is used to create this toString method as declared at line 101 in Pattern to create POJO classes.

Listing 3.10 Freemarker snippet
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<#--stop if $currentModelObject is null-->
<#if !(currentModelObject)??>  <#stop "currentModelObject not found in context" ></#if>

<#assign modelObjectName = currentModelObject.getAttributeValue("name")>
<#assign modelObjectNameFL = modelObjectName?uncap_first>

  StringBuilder result = new StringBuilder("${modelObjectName}: ");

<#assign attributes = currentModelObject.getChildren("attribute", nsModel)>
<#list attributes as attribute>
  <#assign attributeName = attribute.getAttributeValue( "name")>
  <#assign attributeType = attribute.getAttributeValue( "type")>
  <#assign javaType = generator.getJavaType(attributeType)>
  <#-- Add a , after first iteration of this loop using index variable created by freemarker -->
  <#if (attribute_index > 0) >
        result.append(", ");
  </#if>

  <#if (generator.isPrimitiveJavaType(attributeType)) >
        result.append("${attributeName}=" + ${attributeName}); // I am primitive
  <#else>
  <#-- handle primitive types other than objects -->
        <#if (javaType == "String") >
          result.append("${attributeName}=" + ${attributeName});
        <#else>
          result.append("${attributeName}=" + ${attributeName}.toString());
        </#if>
  </#if>
</#list>

return 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 SDA (Software Developement Automation) the developer creates all methods at a meta level.

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