JPSG is temporarily extracted from Koloboke tree to make new releases.
Java Primitive Specializations Generator (JPSG) is a template processor with the primary goal of producing code specialized for any Java primitive types and for object (generic) types, while Java doesn't have generics over primitives.
Source code of the Koloboke collections library is generated with JPSG. The difference between JPSG and similar template processing engines powering other libraries of collections for primitive types in Java (fastutil, Eclipse, Trove, HPPC - all have their own template processing engine) is that JPSG's template files can be valid Java source files that correspond to one of the specializations produced from the respective template.
This allows to make the development of templates more convenient and less error prone by configuring directories with template files as source directories in your IDE so that IDE features like autocomplete and inspections will work in the template files. On the other hand, IDE will think that there are duplicate classes in the project (because a template file's name (class name) and package are the same as in one of the generated specializations), so you will lose the ability to build the project from IDE (you will need to use Maven or Gradle every time to build the project).
This is possible because JPSG captures and replaces actual type occurrences in code
instead of relying on patterns that can't be part of Java code. For example, the following file
CharIntPair.java is a valid JPSG template:
/* with char|byte|short|int|long|float|double|object key
int|byte|char|short|long|float|double|object value */
/* if !(object key object value) */ // Excludes ObjectObjectPair, because there is Pair already
interface CharIntPair/*<>*/ extends Pair<Character, Integer> {
/* if !(object key) */ char getCharKey(); /* endif */
/* if !(object value) */ int getIntValue(); /* endif */
}
JPSG produces the following specialization FloatObjectPair.java from the above template, along
with 62 others:
interface FloatObjectPair<V> extends Pair<Float, V> {
float getFloatKey();
}
As you can see in this example, the second thing that enables JPSG's templates to be valid Java code
is that control structures such as /* if ... */ ... /* endif */ use Java comment syntax. (More on
this in the section about /* if */ blocks in the tutorial below.)
The above example highlights another difference between JPSG and some other template processing
engines: JPSG allows specialization for primitive types as well as object (generic) types. This
makes it possible to produce all possible specializations of types such as Pair<A, B>,
Map<K, V>, Triple<A, B, C>, etc. from a single source template, while with some other template
processing engines usually 4 or 8 different templates are required to cover all {object, primitive}
combinations across all dimensions.
Although primarily designed for specializing Java types, JPSG also supports specialization over
arbitrarily defined (non Java type) "dimensions". For example, for the following template file
Foo.java:
/* with Foo|Bar myDimension */
class Foo {
void getFoo() {
int foo = /* if Foo myDimension */0// elif Bar myDimension //1// endif */;
return foo;
}
}
JPSG produces Foo.java:
class Foo {
void getFoo() {
int foo = 0;
return foo;
}
}
and Bar.java:
class Bar {
void getBar() {
int bar = 1;
return bar;
}
}
Dimensions for specializing Java type options and dimensions for specializing non Java type options can be mixed in a single JPSG template file.
JPSG is available as a Gradle plugin and a Maven plugin.
By convention, in both Gradle and Maven JPSG plugins JPSG template files are located in
src/main/javaTemplates/com/mycompany/MyShortSpecializedType.java, as well as
src/test/javaTemplates/.... In other words, conventional template directories javaTemplates/ are
sibling to conventional java/ directories. The package structure is reflected in the directory
tree structure as well as in ordinary sources. Gradle plugin additionally supports
resourceTemplates/, for example
src/main/resourceTemplates/META-INF/services/com.mycompany.MyShortSpecializedType.
A template file usually begins with a /* with */ construction that defines specialization
dimensions of the template. For example:
/* with char|byte|short|int|long|float|double|object key
int|byte|char|short|long|float|double|object value */
/* license header */
package com.mypackage;
...
Here there are two dimension definitions: dimension "key" has "char|byte|short|int|long|float|double|object" options, dimension "value" has "int|byte|char|short|long|float|double|object" options. Another example:
/* with integer|long|double|obj tType long|double|integer|obj uType Immutable|Mutable mutability */
...
Here dimension "tType" has "integer|long|double|obj" options, dimension "uType" has "long|double|integer|obj" and dimension "mutability" has "Immutable|Mutable" options.
Here is the formal BNF syntax of dimension definitions:
<dimensionName> := alphanumeric string
<javaTypeOption> := "bool" "ean"? | "byte" | "char" "acter"? | "short" | "int" "eger"? |
"long" | "float" | "double" | "obj" "ect"?
<nonJavaTypeOption> := alphanumeric string which isn't a <javaTypeOption>
<javaTypeOptions> := ( <javaTypeOption> "|" )* <javaTypeOption>
<nonJavaTypeOptions> := ( <nonJavaTypeOption> "|" )* <nonJavaTypeOption>
<options> := (<javaTypeOptions> | <nonJavaTypeOptions>)
<dimension> := <options> " " <dimensionName>
<dimensions> := ( <dimension> " "+ )* <dimension>
The first option in the |-delimited list defined for each dimension is the source option. In the
previous example, the source option of dimension "tType" is "integer", "long" of "uType" dimension
and "Immutable" of "mutability" dimension.
JPSG searches for occurrences of the source options in the contents and the file name of the
template file and replaces them to produce specializations corresponding to all combinations of
options for all dimensions (a cartesian product), except, maybe, some combinations that are
filtered. See the section about /* if */ blocks below for more info about filtering.
The file name can be customized further with a special /* define ClassName */ construction. See
the section about /* define */ blocks below.
If there is no /* with */ block in the beginning of a template file, JPSG attempts to deduce
dimensions from the name of the template file, based on defaultTypes configuration (available in
both Gradle and Maven plugins) that defaults to "byte|char|short|int|long|float|double", i. e. all
primitive numeric Java types. For example, for a template file named FloatShortIntTriple.java
deduced dimension definitions will be:
float|byte|char|short|int|long|double t
short|byte|char|int|long|float|double u
int|byte|char|short|long|float|double v
JPSG doesn't deduce dimensions with non Java type options from the template file name to enforce
giving these dimensions more meaningful names than "t", "u", and "v". /* with */ block should be
used to specify non Java type dimensions. And if such block is present,
JPSG doesn't try to deduce more dimensions from the template file name combine them with
explicitly defined dimensions.
If there is no /* with */ block in the beginning of a template file and its file name doesn't
include parts that correspond to java primitive type names, JPSG assumes that there are no file-wide
dimensions for specialization. In this case JPSG generates exactly one output file from the template
with the same name. It's not pointless because such template file could have inner generation,
for example:
class PrimitiveUtils {
/* with byte|char|short|int|long|float|double primType */
... some code that repeats for each primitive type
/* endwith */
}
See the section about /* with */ blocks below.
Each dimension can have either all Java type options or all non Java type options (see BNF
definitions of <javaTypeOptions> and <nonJavaTypeOptions> above). That is,
int|long|Foo myDimension is an illegal dimension definition for JPSG because there are both Java
type options "int", "long" and a non Java type option "Foo". JPSG specializes dimensions with Java
type options (also called Java type dimensions) differently than dimensions with non Java type
options.
Java type options are "bool", "boolean", "byte", "short", "char", "character", "int", "integer", "long", "float", "double", "obj", and "object". Java type dimension specifies a subset of those options. "bool"/"boolean", "char"/"character", "int"/"integer", and "obj"/"object" can't both appear in one dimension definition (but there is a difference between the short and long forms - see below.) "bool"/"boolean" and "obj"/"object" can't be source options, i. e. they can't go first in the options list of a dimension.
JPSG analyzes the template and replaces primitive type appearances in all possible forms (the
following examples assume that the source option is "char" or "character"):
- Primitive Java type occurrences: char value = ..., public char myFunction();
- Boxed primitive class occurrences: List<Character> myValues = ...,
/** @see Character#MAX_VALUE */
- Lowercase: "char" and "character", title case: "Char" and "Character", and upper case: "CHAR" and
"CHARACTER" appearances in any Java identifiers (variable names, method names, class names, block
labels, etc), as well as in all other (non Java) contexts in the template file, such as in
comments and inside string literals. Plural forms: "chars", "characters" are recognized.
Examples: Object myChar = ..., int numChars = ...,
void sortCharacters();, interface CharFunctor ..., int CHARACTER_LIMIT = ...,
/** Get a char value. */.
Specialization to "obj"/"object" by default replaces both primitive Java type occurrences (char)
and boxed primitive class occurrences (Character) with a single letter generic parameter: the
first letter of the dimension name. For example, for dimension char|object key, template
char getChars() { List<Character> list = getList(); return list.get(0); }
is specialized to "object" as
K getObjects() { List<K> list = getList(); return list.get(0); }
For this reason, dimensions that have "object" or "obj" options must have names all starting with different letters.
If some primitive type occurrence or a boxed primitive class occurrence should be replaced with
Object rather than a generic parameter reference, that occurrence should be prepended with
a special /* raw */ modifier in the template:
/* raw */char getChars() { List</* raw */Character> list = getList(); return list.get(0); }
is specialized to "object" as
Object getObjects() { List<Object> list = getList(); return list.get(0); }
Choosing either of the options in "bool"/"boolean", "char"/"character", "int"/"integer", and
"obj"/"object" pairs affects how the specialization for the corresponding option appears in
identifiers: short getShort(); (assuming the source option is "short") could be specialized as
either int getInt(); or int getInteger();, depending on whether "int" or "integer" option is
specified for the dimension. If the source option also allows two potential variants (i. e. the
source option is either "char", "character", "int" or "integer", because booleans and objects can't
be source options), the occurrences in the template that correspond to the specified source option
are recognized as neutral, other occurrences are recognized as deliberately long or short:
| Source option | Occurrence in template | Recognized as | Target option | Specialization outcome |
|---|---|---|---|---|
| "char" | CharCursor |
neutral | "bool" | BoolCursor |
| "char" | CharCursor |
neutral | "boolean" | BooleanCursor |
| "char" | CharacterCursor |
long | "bool" | BooleanCursor |
| "char" | CharacterCursor |
long | "boolean" | BooleanCursor |
| "character" | CharCursor |
short | "bool" | BoolCursor |
| "character" | CharCursor |
short | "boolean" | BoolCursor |
| "character" | CharacterCursor |
neutral | "bool" | BoolCursor |
| "character" | CharacterCursor |
neutral | "boolean" | BooleanCursor |
It's often convenient to choose "short" (also "char" and "byte", if the template has several Java
type dimensions, such as Map, Pair, Triple, etc.) as the source option, because short is rarely
used in Java code, so there will be little type occurrences that need to be excluded from
specialization (see the section about /* with */ blocks below). If identifiers in different styles
(getChar vs getCharacter - see above) appear in the code, choosing "char"/"character" and
"int"/"integer" as source options might allow to express the specialization logic without /* if */
and /* define */ blocks.
Options of non Java type dimensions intended for specialization must start with a capital letter:
/* with Foo|Bar myDimension */, not /* with foo|bar myDimension */ (but lowercase non Java type
opt
$ claude mcp add java-primitive-specializations-generator \
-- python -m otcore.mcp_server <graph>