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
- Host: GitHub
- URL: https://github.com/kasparthommen/java-code-gen
- Owner: kasparthommen
- License: apache-2.0
- Created: 2020-05-12T12:17:15.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2023-05-02T19:50:26.000Z (about 3 years ago)
- Last Synced: 2025-07-30T18:06:16.935Z (11 months ago)
- Topics: annotation, annotation-processor, code-generation, concrete-types, generic, generics, instantiation, java, parametric-polymorphism, primitives, reification, template, template-engine
- Language: Java
- Homepage:
- Size: 131 KB
- Stars: 3
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
- License: LICENSE
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`.