MetaFactory Developer Series

Metaprogramming: Pojo classes bouwen

Marnix van Bochove
20 April 2022

Datamodel

In dit artikel gebruik ik het volgende datamodel:

Om data uit een database te ontsluiten hebben we pojo classes nodig om de data te kunnen opslaan. We beginnen daarom met het maken van de pojo classes op basis van het datamodel.

De Book class

Als we kijken naar de entiteit Book, dan zou ik de volgende Java class kunnen maken:

package org.blog1.pojo;
public class Book {
    private String name;
    private String description;
    private Author author;
    // Getters en Setters weggelaten
}

Voor alle entiteiten in het datamodel zou ik een Java class willen maken die net zo is opgebouwd als de class Book.
Wij gebruiken de Code Composer als programmeertool. De Code Composer werkt via “code instructions”, waarmee op meta niveau wordt vastgelegd hoe de structuur van je class eruit ziet en voor welke entiteiten uit het datamodel je deze class wil bouwen. We maken een code instructie.

Code instructie

<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-
                                                      codeinstruction 
                                  https://metafactory.io/xsd/v1/java-
                                                  codeinstruction.xsd"
              name="org.blog1.pojo"
              package="na_model">
    <class name="${object.name}"
           visibility="public"
           foreach="object">
        <field name="${attribute.name}"
               visibility="private"
               access="rw" foreach="attribute">
            <datatype>${attribute.type}</datatype>
        </field>
        <field name="${reference.name}"
               visibility="private"
               foreach="reference">
            <datatype>${reference.type}</datatype>
        </field>
    </class>
</java_package>

Bovenstaande code instructie is een voorbeeld van hoe je de pojo’s met “metaprogramming” beschrijft.
We definiëren dat we voor elk model object (foreach=”object”) een Java class willen waarvan de naam wordt gemaakt op basis van de naam van het model object.
Daarna definiëren we dat we in deze class een veld willen voor ieder model attribuut dat in het object staat waar we mee bezig zijn (foreach=”attribute”). De naam van het veld maken we hier ook op basis van de naam van het attribuut. Hetzelfde doen we voor referenties, de relationele verbanden tussen de model objecten. Voor elke referentie maken we een Java veld aan met de naam van de referentie. Als type van het veld gebruiken we het type van de referentie. Omdat we weten dat de naam van de class gemaakt wordt door de naam van het model object te gebruiken, kunnen we hier ook het datatype van het veld zetten door naar het type van de referentie te kijken.

Als we met deze code-instructie de pojo’s genereren, dan komt zowel de Book class als de Author class uit de Code Composer.

De Author class

De Author class ziet er als volgt uit:

package org.blog1.pojo;
public class Author {
    private String firstName;
    private String lastName;
    private Book book;
    // Getters en Setters weggelaten
}

De velden firstName en lastName zijn gemaakt op basis van de 2 attributen van het Author model object en het veld book (van het type Book) is gemaakt op basis van de referentie van Author naar Book in het Author object.

Naamgeving

Naamgeving is een belangrijk aspect bij metaprogramming. Op allerlei plaatsen wordt de naam gemaakt op basis van een expressie (${x.y.z}), waarbij gebruik gemaakt kan worden van de context om bijvoorbeeld iets van het huidige model object te gebruiken. In dit geval wordt de naam van de pojo class gemaakt op basis van de naam van het model object. Deze naamgeving wil je ergens kunnen vastleggen, omdat we straks op veel meer plaatsen deze kennis nodig hebben. Ik maak daarom een zogeheten “pattern functie”, waarmee ik vastleg hoe de naam van de pojo class gemaakt wordt op basis van de naam van het object. De pattern functie ziet er als volgt uit:

<function name="createPojoClassName">
    <!-- Function must be called with 1 argument:
    1. Name of model object -->
    <definition>${arg1}</definition>
</function>

De code instructie pas ik nu aan, zodat gebruik wordt gemaakt van deze nieuwe functie:

<java_package xmlns="https://metafactory.io/xsd/v1/java-codeinstruction"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="https://metafactory.io/xsd/v1/java-
                                                      codeinstruction
                                  https://metafactory.io/xsd/v1/java-
                                                  codeinstruction.xsd"
              name="${createPojoPackageName()}">
    <class name="${createPojoClassName(${object.name})}"
           visibility="public"
           foreach="object">
        <field name="${attribute.name}"
               visibility="private"
               access="rw"
               foreach="attribute">
            <datatype>${attribute.type}</datatype>
        </field>
        <field name="${reference.name}"
               visibility="private"
               access="rw"
               foreach="reference">
            <datatype>${createPojoClassName(${reference.type})}</datatype>
        </field>
    </class>
</java_package>

We gebruiken nu een functie om de java packagenaam van de pojo’s te maken en om de naam van de pojo class te maken. De functie om de naam van de class te maken wordt nu gebruikt in het class element zelf, maar ook in het field element die voor alle references een field maakt. Als we nu onze pojo classes anders willen noemen, bijvoorbeeld BookEntity en AuthorEntity, dan hoeven we dat maar op 1 plaats te wijzigen.

Voor de volledigheid voeg ik nog even een equals en hashCode methode toe aan beide classes, waarbij ik gebruik maak van Java 8  mogelijkheden. In volgende blogs laat ik zien hoe we makkelijk een andere implementatie van de equals en hashCode kunnen krijgen (of hoe we Lombok inzetten) om zo aan te geven hoe je met metaprogramming meer controle over je eigen code en beslissingen krijgt.

Code instructie -2-

Met de volgende stukjes code instructie definieer ik de equals en hashCode methode:

<method name="equals" visibility="public">
    <annotation>@Override</annotation>
    <parameter name="return">
        <datatype>boolean</datatype>
        <apicommentline>true if this object is the same as the obj argument;
                        false otherwise</apicommentline>
    </parameter>
    <parameter name="other">
        <datatype>Object</datatype>
        <apicommentline>the reference object with which
                        to compare</apicommentline>
    </parameter>
    <body>${fmsnippet.java.pojo.method.equals_by_bk}</body>
</method>
<method name="hashCode" visibility=”public”>
    <annotation>@Override</annotation>
    <parameter name="return">
        <datatype>integer</datatype>
        <apicommentline>a hash code value for this object</apicommentline>
    </parameter>
    <body>${fmsnippet.java.pojo.method.hashcode_by_bk}</body>
</method>

Na genereren krijgen we dan deze 2 java classes:

Author class

package org.blog1.pojo;
import java.util.Objects;
public class Author {
    private String firstName;
    private String lastName;
    private Book book;
    /**
     * equals - Fields used as businesskey: 1) firstName 2) lastName.
     * 
     * @param other the reference object with which to compare
     * @return boolean true if this object is the same as the obj argument; false otherwise
     */
    @Override
    public boolean equals(final Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Author)) {
            return false;
        }
        final Author otherAuthor = (Author) other;
        boolean result;
        result = Objects.equals(getFirstName(), otherAuthor.getFirstName());
        result = result && Objects.equals(getLastName(), 
                                                 otherAuthor.getLastName());
        return result;
    }
    /**
     * hashCode - Fields used as businesskey: 1) firstName 2) lastName.
     * 
     * @return integer a hash code value for this object
     */
    @Override
    public int hashCode() {
        return Objects.hash(getFirstName(), getLastName());
    }
    /**
     * Getter for property firstName.
     * 
     * @return value of property firstName
     */
    public String getFirstName() {
        return this.firstName;
    }
    /**
     * Setter for property firstName.
     * 
     * @param firstName new value of property firstName
     */
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
    /**
     * Getter for property lastName.
     * 
     * @return value of property lastName
     */
    public String getLastName() {
        return this.lastName;
    }
    /**
     * Setter for property lastName.
     * 
     * @param lastName new value of property lastName
     */
    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
    /**
     * Getter for property book.
     * 
     * @return value of property book
     */
    public Book getBook() {
        return this.book;
    }
    /**
     * Setter for property book.
     * 
     * @param book new value of property book
     */
    public void setBook(final Book book) {
        this.book = book;
    }
}

Book class

package org.blog1.pojo;
import java.util.Objects;
public class Book {
    private String name;
    private String description;
    private Author author;
    /**
     * equals - Fields used as business key: 1) name 2) description.
     * 
     * @param other the reference object with which to compare
     * @return boolean true if this object is the same as the obj argument; false otherwise
     */
    @Override
    public boolean equals(final Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Book)) {
            return false;
        }
        final Book otherBook = (Book) other;
        boolean result;
        result = Objects.equals(getName(), otherBook.getName());
        result = result && Objects.equals(getDescription(), otherBook.getDescription());
        return result;
    }
    /**
     * hashCode - Fields used as businesskey: 1) name 2) description.
     * 
     * @return integer a hash code value for this object
     */
    @Override
    public int hashCode() {
        return Objects.hash(getName(), getDescription());
    }
    /**
     * Getter for property name.
     * 
     * @return value of property name
     */
    public String getName() {
        return this.name;
    }
    /**
     * Setter for property name.
     * 
     * @param name new value of property name
     */
    public void setName(final String name) {
        this.name = name;
    }
    /**
     * Getter for property description.
     * 
     * @return value of property description
     */
    public String getDescription() {
        return this.description;
    }
    /**
     * Setter for property description.
     * 
     * @param description new value of property description
     */
    public void setDescription(final String description) {
        this.description = description;
    }
    /**
     * Getter for property author.
     * 
     * @return value of property author
     */
    public Author getAuthor() {
        return this.author;
    }
    /**
     * Setter for property author.
     * 
     * @param author new value of property author
     */
    public void setAuthor(final Author author) {
        this.author = author;
    }
}

Template engines

In de code instructie van equals en hashcode zie je deze 2 expressions staan:

${fmsnippet.java.pojo.method.equals_by_bk} (Code instructie -2- regel 13)

${fmsnippet.java.pojo.method.hashcode_by_bk} (Code instructie -2- regel 22)

Op deze manier geven we  aan dat we gebruik maken van de Freemarker template engine om de Java code van de equals en hashCode te genereren.
In het volgende blog artikel zal ik meer uitleggen over het gebruik van Freemarker bij metaprogramming met de Code Composer.

Code downloaden

Ben je nieuwsgierig dan kan je alle code downloaden via deze url: https://github.com/MetaFactory/metafactory-developer-series-blog

Abonneer
Laat het weten als er
guest
0 Comments
Inline feedbacks
Bekijk alle reacties
0
Zou graag jouw mening willen weten. Laat een reactie achter.x