How to generate data classes in Java

120 阅读3分钟

How to generate data classes in Java

Go to the profile of Bertil Muth
Bertil MuthBlockedUnblockFollowFollowing
arbeit arbeiten arbeitsbereich arbeitsplatz by Negative Space on pexels.

Kotlin has a concise syntax to declare data classes:

data class User(val name: String, val age: Int)

The equivalent Java syntax is verbose. You have to create a Java class with private fields. And getter and setter methods for the fields. And additional methods like equals(), hashCode() and toString().

But who says you have to create the Java code by hand?

In this article, I’ll show you how to generate Java source files from a YAML file.

Here’s the example YAML file:

User:    name: Name    age: Integer
Name:    firstName: String    lastName: String

The example output of the code generator is two Java source files, User.java and Name.java.

Name.java is similar.

The point of this article is: You’ll learn how to program a code generator from scratch. And it’s easy to adapt it to your needs.

The main method

The main() method does two things:

  • Step 1: Read in the YAML file, into class specifications
  • Step 2: Generate Java source files from the class specifications

It decouples reading and generating. You can change the input format in the future, or support more input formats.

Here’s the main() method:

Step 1: Read the YAML file into class specifications

Let me explain what happens in this line:

List<ClassSpecification> classSpecifications =  yamlReader.read(yamlFile);

A class specification is a definition of a class to be generated and its fields.
 Remember the User in the example YAML file?

User:    name: Name    age: Integer

When the YAML reader reads that, it will create one ClassSpecification object, with the name User. And that class specification will reference two FieldSpecification objects, called name and age.

The code for the ClassSpecification class and the FieldSpecification class is simple.

The content of ClassSpecification.java is shown below:

The content of FieldSpecification.java is:

The only remaining question for Step 1 is: how do you get from a YAML file to objects of these classes?

The YAML reader uses the SnakeYAML library to parse YAML files. 
SnakeYAML makes a YAML file’s content available in data structures like maps and lists.

For this article, you only need to understand maps — because that’s what we use in the YAML files.

Look at the example again:

User:    name: Name    age: Integer
Name:    firstName: String    lastName: String

What you see here is two nested maps.

The key of the outer map is the class name (like User).

When you get the value for the User key, you get a map of the class fields:

name: Nameage: Integer

The key of this inner map is the field name, and the value is the field type.

It’s a map of strings to a map of strings to strings. That’s important to understand the code of the YAML reader.

Here’s the method that reads in the complete YAML file contents:

With the yamlClassSpecifications as input, the YAML reader creates the ClassSpecification objects:

The createClassNameToFieldSpecificationsMap() method creates

  • the field specifications for each class, and based on these
  • a map of each class name to its field specifications.

Then the YAML reader creates a ClassSpecification object for each entry in that map.

The contents of the YAML file are now available to Step 2 in a YAML independent way. We’re done with Step 1.

Step 2: Generate Java source files from the class specifications

Apache FreeMarker is a Java template engine that produces textual output. Templates are written in the FreeMarker Template Language (FTL). It allows static text to mix with the content of Java objects.

Here’s the template to generate the Java source files, javadataclass.ftl:

Let’s look at the first line:

public class ${classSpecification.name}{

You can see it begins with the static text of a class declaration: public class. The interesting bit is in the middle: ${classSpecification.name}.

When Freemarker processes the template, it accesses the classSpecification object in its model. It calls the getName() method on it.

What about this part of the template?

<#list classSpecification.fieldSpecifications as field>    private ${field.type} ${field.name};</#list>

At first, Freemarker calls classSpecification.getFieldSpecifications(). It then iterates over the field specifications.

One last thing. That line is a bit odd:

public ${field.type} get${field.name?cap_first}(){

Let’s say the example field is age: Integer (in YAML). Freemarker translates this to:

public Integer getAge(){

So ?cap_first means: capitalize the first letter, as the YAML file contains age in lower case letters.

Enough about templates. How do you generate the Java source files?

First, you need to configure FreeMarker by creating a Configuration instance. This happens in the constructor of the JavaDataClassGenerator:

To generate source files, the JavaDataClassGenerator iterates over the class specifications, and generates a source file for each:

And that’s it.

Conclusion

I showed you how to build a Java source code generator based on YAML files. I picked YAML because it is easy to process, and thus easy to teach. You can replace it with another format if you like.

You can find the complete code on Github.

To make the code as understandable as possible, I took a few shortcuts:

  • no methods like equals(), hashCode() and toString()
  • no inheritance of data classes
  • generated Java classes are in the default package
  • the output directory is the same as the input directory
  • error handling hasn’t been my focus

A production-ready solution would need to deal with those issues. Also, for data classes, Project Lombok is an alternative without code generation.

So think of this article as a beginning, not an end. Imagine what is possible. A few examples:

  • scaffold JPA entity classes or Spring repositories
  • generate several classes from one specification, based on patterns in your application
  • generate code in different programming languages
  • produce documentation

I currently use this approach to translate natural language requirements 
 directly to code, for research purposes. What will you do?

The original version of this article was posted on dev.to