Hammer L Under construction

4.4. Organizing code instructions

About the author

Sandra L Sandra is the best. Toevoegen: iets over Sandra en over wat maakt dat ze plezier heeft in haar werk.

In blog Repository we demonstrated how to generate various implementations of Java POJO classes with the Code Composer. We looked at three different code instructions and we can choose which code instruction we want to use and generate. This already gives us the opportunity to easily switch between three different implementations, but why don’t we make it even more fine grained?

In this blog we will discuss how we can benefit from further splitting the code instructions. We will also look at some implementations for the toString method to make our Java POJO classes even more complete.

4.4.1. Multiple code instructions creating one Java class

Before we had code instructions for Java 8, Lombok, and Apache Commons implementations. Let’s again have a look at the Java 8 code instruction:

Listing 4.22 Desired JPA code instruction
 1<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
 2              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-codeinstruction
 4                                  https://metafactory.io/xsd/v1/java-codeinstruction.xsd"
 5              name="${createPojoPackageName()}">
 6        <class name="${createPojoClassName(${object.name})}"
 7               foreach="object">
 8                <apicommentline>
 9                        Created by java/pojo/equals-and-hashcode-java8.xml
10                </apicommentline>
11                <field name="${attribute.name}"
12                       access="rw"
13                       foreach="attribute">
14                        <datatype>${attribute.type}</datatype>
15                </field>
16                <field name="${reference.name}"
17                       access="rw"
18                       foreach="reference">
19                        <datatype>${createPojoClassName(${reference.type})}</datatype>
20                </field>
21
22                <method name="equals">
23                        <annotation>@Override</annotation>
24                        <parameter name="return">
25                                <apicommentline>
26                                        Return true if this object is the same as the argument object,
27                                        otherwise return false.
28                                </apicommentline>
29                                <datatype>boolean</datatype>
30                        </parameter>
31                        <parameter name="other">
32                                <apicommentline>
33                                        The reference object with which to compare.
34                                </apicommentline>
35                                <datatype>Object</datatype>
36                        </parameter>
37                        <body>${fmsnippet.java.pojo.method.equals_by_bk_java8}</body>
38                </method>
39
40                <method name="hashCode">
41                        <annotation>@Override</annotation>
42                        <parameter name="return">
43                                <apicommentline>A hash code value for this object.</apicommentline>
44                                <datatype>integer</datatype>
45                        </parameter>
46                        <body>
47                                ${fmsnippet.java.pojo.method.hashcode_by_bk_java8}
48                        </body>
49                </method>
50
51        </class>
52</java_package>

This code instruction can be split into two parts: one part that generates the fields and one part that generates the equals and hashCode methods.

What if we would write these instructions in separate files? The code instructions will become smaller, and each will have its own responsibility. We would also be more flexible in changing implementations and there would be less copying of code.

Let us first take a look at how we can split the code instruction above into two separate ones.

First the one for the fields:

Listing 4.23 Code instruction for fields
 1<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
 2              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-codeinstruction
 4                                  https://metafactory.io/xsd/v1/java-codeinstruction.xsd"
 5              name="${createPojoPackageName()}">
 6        <class name="${createPojoClassName(${object.name})}"
 7               foreach="object">
 8                <apicommentline>
 9                        Created by java/pojo/equals-and-hashcode-java8.xml
10                </apicommentline>
11                <field name="${attribute.name}"
12                       access="rw"
13                       foreach="attribute">
14                        <datatype>${attribute.type}</datatype>
15                </field>
16                <field name="${reference.name}"
17                       access="rw"
18                       foreach="reference">
19                        <datatype>${createPojoClassName(${reference.type})}</datatype>
20                </field>
21        </class>
22</java_package>

And secondly for the two methods:

Listing 4.24 Code instruction for methods
 1<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
 2              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-codeinstruction
 4                                  https://metafactory.io/xsd/v1/java-codeinstruction.xsd"
 5              name="${createPojoPackageName()}">
 6        <class name="${createPojoClassName(${object.name})}"
 7               foreach="object">
 8                <apicommentline>
 9                        Created by java/pojo/equals-and-hashcode-java8.xml
10                </apicommentline>
11
12                <method name="equals">
13                        <annotation>@Override</annotation>
14                        <parameter name="return">
15                                <apicommentline>
16                                        Return true if this object is the same as the argument object, otherwise return false.
17                                </apicommentline>
18                                <datatype>boolean</datatype>
19                        </parameter>
20                        <parameter name="other">
21                                <apicommentline>
22                                        The reference object with which to compare.
23                                </apicommentline>
24                                <datatype>Object</datatype>
25                        </parameter>
26                        <body>
27                                ${fmsnippet.java.pojo.method.equals_by_bk_java8}
28                        </body>
29                </method>
30
31                <method name="hashCode">
32                        <annotation>@Override</annotation>
33                        <parameter name="return">
34                                <apicommentline>
35                                        A hash code value for this object.
36                                </apicommentline>
37                                <datatype>integer</datatype>
38                        </parameter>
39                        <body>
40                                ${fmsnippet.java.pojo.method.hashcode_by_bk_java8}
41                        </body>
42                </method>
43        </class>
44</java_package>

By generating the Code instruction for fields we generate the fields of the POJO class only. By generating with the Code instruction for methods we generate only the equals and hashCode methods. Generating both instructions together will produce one class with both the fields and the methods.

What is the advantage of splitting up the code instructions to generate one class?

When looking back at the code instructions for the various implementations, we can see that in all cases we need to generate the fields. Only the implementations of the methods are different. In this case we can create one instruction for the fields and 3 separate code instructions for the different implementations of the equals and hashCode methods. If you like to use the Java 8 approach for the equals method but prefer to use a Lombok annotation for the hashCode method, you can even split it up further and combine 3 code instructions.

What is interesting to keep in mind is the order of the code instructions. The instructions will be processed by the Code Composer in the order you put them in the codeinstruction.xml file. This is especially interesting when looking at the Lombok implementation (see Listing 4.25). For the model object Book (and one attribute) it will generate the code in Listing 4.26.

Listing 4.25 Lombok Code instruction 1
1<field name="${attribute.name}"
2       access="rw"
3      foreach="attribute">
4        <datatype>${attribute.type}</datatype>
5</field>
Listing 4.26 Java class 1
 1public class Book {
 2        private String name;
 3
 4        public String getName() {
 5                return this.name;
 6        }
 7
 8        public void setName(final String name) {
 9                this.name = name;
10        }
11}

And this is a small piece of the Lombok code instruction:

Listing 4.27 Lombok code instruction 2
1<import>lombok.Getter</import>
2<import>lombok.Setter</import>
3<annotation>@Getter</annotation>
4<annotation>@Setter</annotation>
5<field name="${attribute.name}"
6       access=""
7       foreach="attribute">
8        <datatype>${attribute.type}</datatype>
9</field>

and the Java code:

Listing 4.28 Java class 2
1@Getter
2@Setter
3public class Book {
4
5        private String name;
6}

Let us have a look at what happens when we generate both code instructions in this order. The first code instruction will generate a class including getter and setter methods, because this was instructed with the property access=”rw”. By explicitly leaving this property empty in the Lombok code instruction (line 6) we actually remove the getter and setter methods. We don’t need them because we have the Lombok annotations. The order can therefore play a significant role in splitting code instructions.

4.4.2. Features

Code instructions are listed in the codeinstruction.xml. Splitting code instructions into smaller ones can result in long lists to be processed by the Code Composer.

To make this easier to manage, we can use the feature functionality with which we can bundle related code instructions together in one feature. For example, we can create a Lombok feature code instruction which includes 4 code instructions:

Listing 4.29 Feature (example)
 1<feature xmlns="https://metafactory.io/xsd/v1/codeinstruction"
 2         xmlns:java="https://metafactory.io/xsd/v1/java-codeinstruction"
 3         xmlns:xi="http://www.w3.org/2001/XInclude"
 4         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 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         id="lombok"
10         description="Creates POJO classes with Lombok annotations">
11        <java:external_java_package id="libProject/java/pojo/fields-clean.xml"
12                                    path="${pattern.property.java.main.directory}" />
13        <java:external_java_package id="libProject/java/pojo/fields-lombok.xml"
14                                    path="${pattern.property.java.main.directory}" />
15        <java:external_java_package id="libProject/java/pojo/equals-and-hashcode-lombok.xml"
16                                    path="${pattern.property.java.main.directory}" />
17        <java:external_java_package id="libProject/java/pojo/tostring-lombok.xml"
18                                    path="${pattern.property.java.main.directory}" />
19</feature>

This one feature will generate a Java POJO class with fields and equals, hashCode, and toString method implementations:

Listing 4.30 Generated Book class
 1@Getter
 2@Setter
 3@EqualsAndHashCode(onlyExplicitlyIncluded = true)
 4@ToString(onlyExplicitlyIncluded = true)
 5
 6public class Book {
 7
 8        @EqualsAndHashCode.Include
 9        @ToString.Include
10        private String name;
11
12        @EqualsAndHashCode.Include
13        @ToString.Include
14        private String description;
15
16        @EqualsAndHashCode.Include
17        @ToString.Include
18        private Author author;
19}

Hopefully, you can appreciate how features can be used to bundle together multiple code instructions based on their functionality, or on any basis that is convenient in your situation.

Remember, you are always in control!

4.4.3. toString

Let us complete our story for now with the addition of the toString method to the Java POJO classes. Here is our code instruction the Lombok implementation:

Listing 4.31 toString code instruction
 1<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
 2              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-codeinstruction
 4                                  https://metafactory.io/xsd/v1/java-codeinstruction.xsd"
 5              name="${createPojoPackageName()}">
 6        <class name="${createPojoClassName(${object.name})}"
 7               foreach="object">
 8                <apicommentline>Created by java/pojo/tostring-lombok.xml</apicommentline>
 9                <import>lombok.ToString</import>
10                <annotation>@ToString(onlyExplicitlyIncluded = true)</annotation>
11
12                <field name="${attribute.name}"
13                       access=""
14                       foreach="attribute">
15                        <annotation
16                                condition="${doesAttributeHaveBusinessKey(${object.name},${attribute.name})}">
17                                @ToString.Include
18                        </annotation>
19                        <datatype>${attribute.type}</datatype>
20                </field>
21
22                <field name="${reference.name}"
23                           access=""
24                           foreach="reference">
25                        <annotation
26                                condition="${doesReferenceHaveBusinessKey(${object.name},${reference.name})}">
27                                @ToString.Include
28                        </annotation>
29                        <datatype>${createPojoClassName(${reference.type})}</datatype>
30                </field>
31
32        </class>
33</java_package>

We introduced two new functions: doesAttributeHaveBusinessKey (line 16) and doesReferenceHaveBusinessKey (line 26). These functions check in the model whether the associated attribute or reference has the metadata <businesskey>. If the function returns true, the annotation will be added. The combination of metadata in the model and these functions give us full control to decide which fields should be included in the toString Lombok implementation and which not. In this case the fields will only be included if the attribute or reference is part of the business key

And here our Java class:

Listing 4.32 toString code instruction
 1/**
 2 * Book - Created by java/pojo/fields-clean.xml
 3 *        Created by java/pojo/fields-lombok.xml
 4 *        Created by java/pojo/tostring-lombok.xml
 5 *
 6 * @author - MetaFactory Code Composer
 7 */
 8@Getter
 9@Setter
10@ToString(onlyExplicitlyIncluded = true)
11public class Book {
12
13        @ToString.Include
14        private String name;
15
16        @ToString.Include
17        private String description;
18
19        @ToString.Include
20        private Author author;
21
22}

Now our POJO classes are complete.

In this blog we have seen how we can split code instructions and organize them in features to make it easier to switch between implementations. This gives us full control over our generated code.

The MetaFactory Code Composer is all about enabling you to create the code in any way you like it.