An open API service indexing awesome lists of open source software.

https://github.com/kasparthommen/java-code-gen

Annotation-based Java code generation
https://github.com/kasparthommen/java-code-gen

annotation annotation-processor code-generation concrete-types generic generics instantiation java parametric-polymorphism primitives reification template template-engine

Last synced: 5 months ago
JSON representation

Annotation-based Java code generation

Awesome Lists containing this project

README

          

= Java Code Generator Annotations

image:https://maven-badges.herokuapp.com/maven-central/io.github.kasparthommen.codegen/java-code-gen/badge.svg[link="https://maven-badges.herokuapp.com/maven-central/io.github.kasparthommen.codegen/java-code-gen"]

:INSTANTIATE: pass:quotes[link:java-code-gen/src/main/java/com/kt/codegen/Instantiate.java[`@Instantiate`]]
:DERIVE: pass:quotes[link:java-code-gen/src/main/java/com/kt/codegen/Derive.java[`@Derive`]]
:REPLACE: pass:quotes[link:java-code-gen/src/main/java/com/kt/codegen/Replace.java[`@Replace`]]
:SOURCE_DIRECTORY: pass:quotes[link:java-code-gen/src/main/java/com/kt/codegen/SourceDirectory.java[`@SourceDirectory`]]
:SRC_DIR: ../main/java/com/kt/codegen/demo
:GEN_DIR: ../../target/generated-sources/annotations/com/kt/codegen/demo

== TL;DR
=== What does this library provide?
This library provides the following code-generating annotations:

* {INSTANTIATE} generates concrete instantiations of generic classes
analogous to C++ templates:

[cols="1a"]
|===
|
[source,java]
----
/* you write... */
@Instantiate(String.class)
class MyList {
private T[] array;
// ...
}
----
|
[source,java]
----
/* ... and you'll get */
class MyListString {
private String[] array;
// ...
}
----
|===

* {DERIVE} generates new classes from existing ones by applying
string or regex replacements to the source code:

[cols="1a"]
|===
|
[source,java]
----
/* you write... */
@Derive(name = "MyFloatList", replace = @Replace(from = "double", to = "float"))
class MyDoubleList {
private double[] array;
// ...
}
----
|
[source,java]
----
/* ... and you'll get */
class MyFloatList {
private float[] array;
// ...
}
----
|===

=== Why should I use it?
The main advantage of this library over generic template engines such as
link:https://www.stringtemplate.org/[StringTemplate],
link:https://velocity.apache.org/[Velocity]
or
link:https://freemarker.apache.org/[FreeMaker] is:

*Your template is actual code!*

Thus, instead of having to write a placeholder-sprinkled, engine-specific
template file, your "template" is a normal Java class (with annotations).
The benefits are as follows:

** The "template" is source code rather than a resource file
** The "template" can be unit tested
** The "template" enjoys IDE syntax highlighting - no template
engine-specific plugins required
** The "template" can be auto-formatted, linted and refactored by your IDE

== Instantiate generic classes with {INSTANTIATE}

=== Motivation

Consider the following generic class (which, of course, would require a lot more work
before it's a reasonable list implementation):
[source,java]
----
package com.kt.codegen.demo.list1;

class MyList {
private T[] array;

MyList(int size) {
this.array = (T[]) new Object[size];
}

T get(int index) {
return array[index];
}
}
----

You can annotate it with {INSTANTIATE} to e.g. create a concrete String instantiation,
analogous to C++ templates:
[source,java]
----
package com.kt.codegen.demo.list2;

import com.kt.codegen.Instantiate;

@Instantiate(String.class)
class MyList {
private T[] array;

MyList(int size) {
this.array = (T[]) new Object[size];
}

T get(int index) {
return array[index];
}
}
----

This will generate the following class:
[source,java]
----
// generated from com.kt.codegen.demo.list2.MyList
package com.kt.codegen.demo.list2;

class MyListString {
private String[] array;

MyListString(int size) {
this.array = (String[]) new Object[size];
}

String get(int index) {
return array[index];
}
}
----

Nice, but the annotation processor only operates on a source code level and simply
replaces occurrences of `T` with `String`. This leads to a guaranteed class cast
exception in the expression `(String[]) new Object[size]`. Can we fix this? Yes, with custom
string replacements, see below.

=== Custom String Replacements
Simply replacing a generic type with a concrete type like we just did doesn't usually
get us all the way, but fret not, there are custom string replacements:
[source,java]
----
package com.kt.codegen.demo.list3;

import com.kt.codegen.Instantiate;
import com.kt.codegen.Replace;

@Instantiate(value = String.class,
replace = @Replace(from = "(T[]) new Object[size]", to = "new String[size]"))
class MyList {
private T[] array;

MyList(int size) {
this.array = (T[]) new Object[size];
}

T get(int index) {
return array[index];
}
}
----

Now the generated string list is safe:
[source,java]
----
// generated from com.kt.codegen.demo.list3.MyList
package com.kt.codegen.demo.list3;

class MyListString {
private String[] array;

MyListString(int size) {
this.array = new String[size];
}

String get(int index) {
return array[index];
}
}
----

=== Primitives
How about adding a primitive version of our list? Simple: just add a `double` instantiation:
[source,java]
----
package com.kt.codegen.demo.list4;

import com.kt.codegen.Instantiate;
import com.kt.codegen.Replace;

@Instantiate(value = String.class,
replace = @Replace(from = "(T[]) new Object[size]", to = "new String[size]"))
@Instantiate(value = double.class,
replace = @Replace(from = "(T[]) new Object[size]", to = "new double[size]"))
class MyList {
private T[] array;

MyList(int size) {
this.array = (T[]) new Object[size];
}

T get(int index) {
return array[index];
}
}
----

This will additionally geenrate the following class:
[source,java]
----
// generated from com.kt.codegen.demo.list4.MyList
package com.kt.codegen.demo.list4;

class MyListDouble {
private double[] array;

MyListDouble(int size) {
this.array = new double[size];
}

double get(int index) {
return array[index];
}
}
----

Note that the class is called `MyListDouble` instead of `MyListdouble` (note the
different case of the "d") to make the two types explicit in the class name.

=== Multiple Type Parameters
If your generic class has more than one type parameter then you'll simply have to provide
the necessary number of concrete types for each instantiation:
[source,java]
----
package com.kt.codegen.demo.map;

import com.kt.codegen.Instantiate;

import java.time.Instant;

@Instantiate({String.class, Instant.class}) // <-- two concrete types
class MyMap { // <-- two type parameters
private K[] keys;
private V[] values;

// ...
}
----

=== Notes
* For projects that don't follow the Maven directory layout you can specify the relative
source directory with {SOURCE_DIRECTORY} on the source class.
* If normal string replacement won't cut it, you can set `{REPLACE}.regex` to `true`.
* You can specify multiple replacements with
`replace = {@Replace(...), @Replace(...), ...}`.
* I you prefer prepending the concrete type(s) to the class rather than the default
appending style (i.e., `StringMyList` rather than `MyListString`) then set
`{INSTANTIATE}.append` to `false`.

== Generate derived classes with {DERIVE}
Say you are working on a primitive collection library. You have just finished writing
a `double` list implementation:
[source,java]
----
package com.kt.codegen.demo.double1;

public class MyDoubleList {
private double[] array;

MyDoubleList(int size) {
this.array = new double[size];
}

// ...
}
----

Now you have a couple of options to create lists for other primitive types:

. You copy and paste the class a couple of times followed by a search/replace frenzy. This
is cumbersome, time-consuming, and will eventually lead to implementations drifting
apart because you'll forget to apply that one fix to the `float` implementation.

. You fire up a generic template engine, convert this nice, working, unit-tested,
syntax-highlighted, auto-formatted, error-checked class into a template text
file that immediately loses all those nice properties, and you start configuring
the template engine.

. Or you annotate the class as follows:

[source,java]
----
package com.kt.codegen.demo.double2;

import com.kt.codegen.Derive;
import com.kt.codegen.Replace;

@Derive(name = "MyFloatList", replace = @Replace(from = "\\bdouble\\b", to = "float", regex = true))
@Derive(name = "MyLongList", replace = @Replace(from = "\\bdouble\\b", to = "long", regex = true))
public class MyDoubleList {
private double[] array;

MyDoubleList(int size) {
this.array = new double[size];
}

// ...
}
----

This will generate two derived classes:
[source,java]
----
// generated from com.kt.codegen.demo.double2.MyDoubleList
package com.kt.codegen.demo.double2;

public class MyFloatList {
private float[] array;

MyFloatList(int size) {
this.array = new float[size];
}

// ...
}
----

And:

[source,java]
----
// generated from com.kt.codegen.demo.double2.MyDoubleList
package com.kt.codegen.demo.double2;

public class MyLongList {
private long[] array;

MyLongList(int size) {
this.array = new long[size];
}

// ...
}
----

=== Notes

* The relative source directory can also be changed using {SOURCE_DIRECTORY}.
* Custom string replacements can be specified in `{DERIVE}.replace`.