') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - kasparthommen/java-code-gen: Annotation-based Java code generation · GitHub
Skip to content

Repository files navigation

Java Code Generator Annotations

badge

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:

/* you write... */@Instantiate(String.class)
classMyList<T> {
privateT[] array;
// ...
}
/* ... and you'll get */classMyListString {
privateString[] array;
// ...
}
  • @Derive generates 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;
// ...
}

Why should I use it?

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

Motivation

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.

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:

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];
}
}

Primitives

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.

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:

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;
// ...
}

Notes

  • For projects that don’t follow the Maven directory layout you can specify the relative source directory with @SourceDirectory 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:

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:

  1. 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.

  2. 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.

  3. 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];
}
// ...
}

Notes

  • The relative source directory can also be changed using @SourceDirectory.

  • Custom string replacements can be specified in @Derive.replace.