This library provides the following code-generating annotations:
@Instantiategenerates concrete instantiations of generic classes analogous to C++ templates:
/* you write... */@Instantiate(String.class)
classMyList<T> {
privateT[] array;
// ...
} |
/* ... and you'll get */classMyListString {
privateString[] array;
// ...
} |
@Derivegenerates new classes from existing ones by applying string or regex replacements to the source code:
/* you write... */@Derive(name = "MyFloatList", replace = @Replace(from = "double", to = "float"))
classMyDoubleList {
privatedouble[] array;
// ...
} |
/* ... and you'll get */classMyFloatList {
privatefloat[] array;
// ...
} |
The main advantage of this library over generic template engines such as StringTemplate, Velocity or 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
Consider the following generic class (which, of course, would require a lot more work before it’s a reasonable list implementation):
packagecom.kt.codegen.demo.list1;
classMyList<T> {
privateT[] array;
MyList(intsize) {
this.array = (T[]) newObject[size];
}
Tget(intindex) {
returnarray[index];
}
}You can annotate it with @Instantiate to e.g. create a concrete String instantiation,
analogous to C++ templates:
packagecom.kt.codegen.demo.list2;
importcom.kt.codegen.Instantiate;
@Instantiate(String.class)
classMyList<T> {
privateT[] array;
MyList(intsize) {
this.array = (T[]) newObject[size];
}
Tget(intindex) {
returnarray[index];
}
}This will generate the following class:
// generated from com.kt.codegen.demo.list2.MyListpackagecom.kt.codegen.demo.list2;
classMyListString {
privateString[] array;
MyListString(intsize) {
this.array = (String[]) newObject[size];
}
Stringget(intindex) {
returnarray[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.
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:
packagecom.kt.codegen.demo.list3;
importcom.kt.codegen.Instantiate;
importcom.kt.codegen.Replace;
@Instantiate(value = String.class,
replace = @Replace(from = "(T[]) new Object[size]", to = "new String[size]"))
classMyList<T> {
privateT[] array;
MyList(intsize) {
this.array = (T[]) newObject[size];
}
Tget(intindex) {
returnarray[index];
}
}Now the generated string list is safe:
// generated from com.kt.codegen.demo.list3.MyListpackagecom.kt.codegen.demo.list3;
classMyListString {
privateString[] array;
MyListString(intsize) {
this.array = newString[size];
}
Stringget(intindex) {
returnarray[index];
}
}How about adding a primitive version of our list? Simple: just add a double instantiation:
packagecom.kt.codegen.demo.list4;
importcom.kt.codegen.Instantiate;
importcom.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]"))
classMyList<T> {
privateT[] array;
MyList(intsize) {
this.array = (T[]) newObject[size];
}
Tget(intindex) {
returnarray[index];
}
}This will additionally geenrate the following class:
// generated from com.kt.codegen.demo.list4.MyListpackagecom.kt.codegen.demo.list4;
classMyListDouble {
privatedouble[] array;
MyListDouble(intsize) {
this.array = newdouble[size];
}
doubleget(intindex) {
returnarray[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.
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:
packagecom.kt.codegen.demo.map;
importcom.kt.codegen.Instantiate;
importjava.time.Instant;
@Instantiate({String.class, Instant.class}) // <-- two concrete typesclassMyMap<K, V> { // <-- two type parametersprivateK[] keys;
privateV[] values;
// ...
}For projects that don’t follow the Maven directory layout you can specify the relative source directory with
@SourceDirectoryon the source class.If normal string replacement won’t cut it, you can set
to@Replace.regextrue.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.,
StringMyListrather thanMyListString) then setto@Instantiate.appendfalse.
Generate derived classes with @Derive
Say you are working on a primitive collection library. You have just finished writing
a double list implementation:
packagecom.kt.codegen.demo.double1;
publicclassMyDoubleList {
privatedouble[] array;
MyDoubleList(intsize) {
this.array = newdouble[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
floatimplementation.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:
packagecom.kt.codegen.demo.double2;
importcom.kt.codegen.Derive;
importcom.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))
publicclassMyDoubleList {
privatedouble[] array;
MyDoubleList(intsize) {
this.array = newdouble[size];
}
// ...
}This will generate two derived classes:
// generated from com.kt.codegen.demo.double2.MyDoubleListpackagecom.kt.codegen.demo.double2;
publicclassMyFloatList {
privatefloat[] array;
MyFloatList(intsize) {
this.array = newfloat[size];
}
// ...
}And:
// generated from com.kt.codegen.demo.double2.MyDoubleListpackagecom.kt.codegen.demo.double2;
publicclassMyLongList {
privatelong[] array;
MyLongList(intsize) {
this.array = newlong[size];
}
// ...
}The relative source directory can also be changed using
@SourceDirectory.Custom string replacements can be specified in
.@Derive.replace