Skip to content

binis2/code-generation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

code-generation

Code generation module for Binis CodeGen Library.

Introduction

This is a code generation library inspired by lombok with the addition of generating/handling interfaces and reduced manual typing even more. There are some extensions that heavily support functional programming.

Note: Since version 1.0 the library uses and supports Java 17+ (Spring 6, Spring Boot 3, Hibernate 6 for specific modules) for Java 11+ (Spring 5, Spring Boot 2, Hibernate 5) support use version 0.x of the library.

Basics

CodeGen library is based on object prototypes. Here is simple example.

@CodePrototype
public interface TestPrototype {
    String title();
}

The generated code for this prototype will look like this:

Interface:

public interface Test {
    String getTitle();
    void setTitle(String title);
}

Implementation:

public class TestImpl implements Test {
    protected String title;

    public TestImpl() {
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

Enrichers

The CodeGen library introduces code enrichers to help you quickly spice up your code.

ModifierEnricher

@CodePrototype(enrichers = {ModifierEnricher.class})
public interface TestPrototype {
    String title();
    double amount;
}

this will enable you the ability to modify existing instances of your objects in a functional way like this

    test.with().title("title").amount(10.0).done();

AsEnricher

@CodePrototype(enrichers = {AsEnricher.class})
public interface TestPrototype extends OtherInterface {
    String title();
    double amount;
}

enables functional casting for your objects

    test.as(OtherInterface.class)...

CreatorEnricher and CreatorModifierEnricher

@CodePrototype(enrichers = {CreatorModifierEnricher.class, ModifierEnricher.class})
public interface TestPrototype {
    String title();

    double amount;
}

enables decoupled instantiation of your objects

    Test.create().title("title").amount(10.0).done();

The difference between CreatorModifierEnricher and CreatorEnricher is that CreatorEnricher give you the created object itself instead of its modifier. CreatorModifierEnricher is to be used with combination with ModifierEnricher. If ModifierEnricher is not added to the prototype CreatorModifierEnricher will act as CreatorEnricher.

QueryEnricher

@CodePrototype(enrichers = {QueryEnricher.class})
public interface TestPrototype {
    String title();
    double amount;
}

enables query building for your jpa entities

    Test.find().by().title("title").get();
    Test.find().by().amount().greater(10.0).and().not().title("title").get();

Note that QueryEnricher requires code-generation-spring module dependency to your project

ValidationEnricher

import net.binis.codegen.annotation.validation.Sanitize;

@CodePrototype(enrichers = {ValidationEnricher.class})
public interface TestPrototype {
    @ValidateNull
    @SanitizeTrim
    String title();

    //or use the longer syntax 
    @Sanitize(ReplaceSanitizer.class, "\\s+", "_")
    @Validate(NullValidator.class, "Subtitle can't be null!")
    String subtitle;
}

enables validation and sanitization for your entities. It handles both setters and modifiers.

    assertThrows(ValidationException.class, () -> entity.setTitle(null));
    assertEquals("title", entity.with().title("  title  ").done().getTitle());
Custom validators/sanitizers

The validation system has a neat way to inherit existing validation/sanitization annotations and introduce your own custom annotation validations/sanitizations.

@Validate(LambdaValidator.class)
public @interface ValidateNotBlank {
    @AsCode
    @AliasFor("params")
    String value() default "org.apache.commons.lang3.StringUtils::isNotBlank";
    String message() default "Value can't be blank!";
}
@Validate(value = RegExValidator.class, params = ValidateEmail.REGEX)
public @interface ValidateEmail {
    String REGEX = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
    String message() default "Invalid Email!";
}

and use it like this

@SanitizeTrim
@ValidateNotBlank
String field();

@ValidateEmail
String email();

@ValidateRange(min = 0, max = 100)
int value();

@SanitizeLambda("String::trim")
String lambda();

Note: Validation module needs to be included into the project!

Note that ValidationEnricher requires code-generation-validation module dependency to your project

Multiple enrichers can be combined for spicing up your objects. See below.

Collections support

All enrichers have extensive collection support

@CodePrototype(enrichers = {QueryEnricher.class, ModifierEnricher.class})
public interface TestPrototype {
    String title();
    List<Double> amounts(); 
}

For either creation

    Test.create()
            .title("title")
            .amounts()
                .add(5)
                .add(6)
            .and()
        .done();

or querying

    Test.find().by().title("title").get().ifPresent(test ->
        test.with()
            .amounts().add(10.0)
        .save());
    Test.find().by().amounts().contains(10.0).list();

Base and Sub Prototypes

The library supports prototypes interaction

@CodePrototype
public interface UserPrototype extends BasePrototype {
    String username();
    List<AccountPrototype> accounts(); 
}

Real life examples

Initial design of the library was to ease the creation and maintenance of JPA entities. So here is a full scale example. Note that it copes well with lombok.

So let's declare a base entity first:

@CodePrototype(
        base = true,
        interfaceName = "BaseInterface",
        interfaceSetters = false,
        implementationPackage = "my.project.db.entity",
        enrichers = {AsEnricher.class, ModifierEnricher.class},
        inheritedEnrichers = {CreatorModifierEnricher.class, ModifierEnricher.class, QueryEnricher.class})
@MappedSuperclass
public interface BaseEntityPrototype extends Serializable, Identifiable {

    @CodeConstant(isPublic = false)
    long serialVersionUID = 1862576989031617048L;

    @Id
    @Column(name = "id", nullable = false, updatable = false)
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @JsonSerialize(using = ToStringSerializer.class)
    @ToString.Include
    Long id();

    @Ignore(forModifier = true)
    @CreatedDate
    @Column(nullable = false, updatable = false)
    @ColumnDefault("current_timestamp")
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    OffsetDateTime created();

    @LastModifiedDate
    @Column(nullable = false)
    @ColumnDefault("current_timestamp")
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    OffsetDateTime modified();

    @Ignore(forInterface = true, forModifier = true)
    @CreatedBy
    @Column(updatable = false)
    String createdBy();

    @Ignore(forInterface = true, forModifier = true)
    @LastModifiedBy
    String modifiedBy();
    
}

Now an actual entity:

@CodePrototype(
        interfaceSetters = false,
        implementationPackage = "my.project.db.entity",
        baseModifierClass = BaseEntityModifier.class)
@Entity(name = UserEntityPrototype.TABLE_NAME)
public interface UserEntityPrototype extends BaseEntityPrototype, Addressable, Statusable<CustomerStatus> {

    @CodeConstant(isPublic = false)
    long serialVersionUID = 2344606065855771677L;

    String TABLE_NAME = "users";

    @Column(unique = true)
    String username();

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    String password();

    String firstName();
    String lastName();
    String email();

    @OneToOne(targetEntity = AddressEntityPrototype.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id")
    AddressEntityPrototype address();

    @ColumnDefault("0")
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    CustomerStatus status();

    @Ignore(forInterface = true)
    @Transient
    default String getPreview() {
        return username() + " (" + firstName() + " " + lastName() + ")";
    }

    @ForImplementation
    @Transient
    default boolean isActive() {
        return !CustomerStatus.INACTIVE.equals(status());
    }

}

Now you can simply write things like this anywhere in your code without need of anything else except importing your new interface

User.create()
        .username("username")
        .password("password")
        .email("email@email.com")
        .address(Address.create()
            .street("nowhere str.")
            .number(5)
            .done())
        .status(CustomerStatus.ACTIVE)
        .save();

or

User.find().by().email("email@email.com").get().ifPresent(user -> 
        user.with().status(CustomerStatus.ACTIVE).save());

*For more use cases check the unit tests here and here *

How to make it work?

Depends on your preferences you can keep your prototypes outside your actual project into lets call it prototypes project. Just add this build step to your prototypes project's pom.xml.

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>generateCode</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>java</goal>
                    </goals>
                    <configuration>
                        <mainClass>net.binis.codegen.CodeGen</mainClass>
                        <arguments>
                            <argument>-s</argument>
                            <argument>${project.basedir}/..</argument>
                            <argument>-d</argument>
                            <argument>${project.basedir}/../modules/core/src/main/java</argument>
                            <argument>-id</argument>
                            <argument>${project.basedir}/../modules/db/src/main/java</argument>
                            <argument>-f</argument>
                            <argument>**Prototype.java</argument>
                        </arguments>
                        <classpathScope>compile</classpathScope>
                        <sourceRoot>${project.build.directory}/generated-sources/main/java</sourceRoot>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

or you can use the annotation processor

<dependency>
    <groupId>dev.binis</groupId>
    <artifactId>code-generator-annotation</artifactId>
    <version>1.2.19</version>
    <scope>compile</scope>
</dependency>

Maven Dependency

    <dependency>
        <groupId>dev.binis</groupId>
        <artifactId>code-generator</artifactId>
        <version>1.2.19</version>
    </dependency>

Other modules of the suite

Core - [https://github.com/binis2/code-generation-core]
Spring Extension - [https://github.com/binis2/code-generation-spring]
Tests mocking suite - [https://github.com/binis2/code-generation-test]
Annotation processor - [https://github.com/binis2/code-generation-annotation]
Validation and Sanitization extension - [https://github.com/binis2/code-generation-validation]
Jackson support - [https://github.com/binis2/code-generation-jackson]
Spring Boot configuration - [https://github.com/binis2/code-generation-spring-configuration]
Projections support - [https://github.com/binis2/code-generation-projection]
Hibernate support - [https://github.com/binis2/code-generation-hibernate]

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages