Skip to content

alex-zuy/boilerplate-generator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Boilerplate generator

Build Status

The project deals with generation of property name constants for Java Beans classes. Implemented as annotation processor it does not requires complex sutup, build tools integrations, IDE plugins, etc. Just add as dependency and you are good to go.

Index

  1. Features
  2. Rationale
  3. Setup
  4. Usage
  5. Java Doc
  6. Similar projects

Features

With Boilerplate generator you can:

  1. Can
    • Use bean property names in your code in safe way (using constants declared in generated code).
    • Construct nested properties paths (see usage section)
  2. Don't need to
    • Setup build tool plugins (javac`s builtin compiler API is used)

Rationale

Bean Property Names

The ubiquitous in Java World Java Beans pattern often forces you to refer to properties of beans using their names. Some cases are:

  • validation of beans (especially in web applications). You may need to validate bean property and put error message in some sort of map with bean properties used as key.
  • advanced UI frameworks. For example framework may provide table widget which will show list of beans using list of property names you provide to it.

You can always use string literal to specify property name when needed. Say you have a bean with following properties:

public class User {
    
    private String login;
    
    private String name;
    
    // getters-setters ...
}

When you are validating this bean you can do:

public class UserValidator {
    
    public Map<String, String> validate(User user) {
        
        Map<String, String> errors = new HashMap<>();
        
        if(user.getLogin() == null || user.getLogin().isEmpty()) {
            errors.put("login", "Login must be present");              //hardcoded property name
        }
        
        return errors;
    }
}

Going this way you will hardcode this name each time you need it. If bean property name changes you must carefully check all the code to ensure that you've updated hardcoded name in each place.

You can try to solve this problem by using string constants:

public class UserValidator {
    
    private static final String LOGIN_PROPERTY = "login"; //define constant to avoid hardcoding name
    
    public Map<String, String> validate(User user) {
        
        Map<String, String> errors = new HashMap<>();
        
        if(user.getLogin() == null || user.getLogin().isEmpty()) {
            errors.put(LOGIN_PROPERTY, "Login must be present");      //use manually created constant
        }
        
        return errors;
    }
}

Now you can update property name by editing a single line of code. However this constant is still some kind of code duplication, as you already defined property name when defined getter/setter pair in bean class. Also you still can easily forget to update it when property name changes.

At some point you may need to use property names constants in several classes and you will be forced to define them in one separate place:

public class UserProperties {
    public static final String LOGIN_PROPERTY = "login";
}

With this solution we still have following problems:

  1. We need to update constants manually.
  2. We need to manually write such 'constants' classes - boilerplate code.

This project aims at solving those last two points!

Setup

Boilerplate generator is implemented as Annotation Processor (JSR 269: Pluggable Annotation Processing API). To use it in your project you need to:

  1. Add it as dependency in you project.
  2. Configure it by defining annotations in your code.

Adding a dependency

Boilerplate generator is available from Maven Central, so you can get it in your project just by adding dependency to you pom.xml:

<dependency>
    <groupId>com.github.alex-zuy.boilerplate</groupId>
    <artifactId>processor</artifactId>
    <!-- Update version to the latest available -->
    <version>1.0.0</version>
    <scope>compile</scope>
</dependency>

Configuring generator

Boilerplate generator strives to not pollute your code with its annotations. For this reason it does not provides annotations to do that, so you will need to define your own annotations to mark classes you want to process (or you can use annotations which already present in your project). There are no requirements to this annotations, so it can be as simple as:

package  com.example.app;

public @interface IncludeMarker {}

Using this annotation you can configure boilerplate generator to generate metadata classes for all classes marked with @IncludeMarker annotation. Here is example configuration (in package-info.java):

@BeanMetadataConfiguration(
    supportClassesConfiguration = @SupportClassesConfiguration(
        basePackage = "com.example.app.generated.support"),
    domainConfiguration = @DomainConfiguration(
            includes = @DomainConfiguration.Includes(
                typeAnnotations = {"com.example.app.IncludeMarker"}))
)
package com.example.app;

Given bean class Person:

package com.example.app;

@IncludeMarker
public class Person {
    
    private String name;
    
    // getters-setters ...
}

Boilerplate generator will generate class Person_p which you can use to refer to Person`s class properties. For more information about configuration and available options please refer to JavaDoc.

Using generated classes

Once you added boilerplate generator to your project you can use it. Let's assume that you have following bean classes (getters-setters omitted for brevity):

Address.java

public class Address {
    
    String street;
    
    int houseNumber;
    
    int apartmentNumber;
}

Apartment.java

public class Apartment {
    
    Address address;
    
    float square;
}

Citizen.java

public class Citizen {
    
    String firstName;
    
    String lastName;
    
    Apartment apartment;
}

To access property names of Citizen you can just use constants:

assert "firstName".equals(Citizen_p.FIRST_NAME)

To access nested property of Citizen bean you need to navigate through property chain starting from Citizen_p class`s 'relationships' methods and continue navigation using relationships classes methods. For example, we can construct path to citizen`s apartment square:

assert "apartment.square".equals(Citizen_p.apartment().squareProperty())

Or we can navigate to apartment`s address properties:

assert "apartment.address.street".equals(Citizen_p.apartment().address().streetProperty())

You can reuse part of property path to avoid duplication:

Address_r addressChain = Citizen_p.apartment().address();
assert "apartment.address.street".equals(addressChain.streetProperty())
assert "apartment.address.houseNumber".equals(addressChain.houseNumberProperty())

Similar projects

This project is similar to Bread Crumbs project in its goals and implementation details (both are annotation processors).

This project is different to Bread Crumbs in:

  1. More flexible configuration (especially in configuring generated code style).
  2. Support for nested properties.