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:
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:
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:
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:
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.
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