This projects contains tools which allow idiomatic other language API shims to be generated from Java APIs.
- Codegen CLI: a codegen CLI to help code generating files.
- Codegen starter: a codegen Starter that can be forked to create a new Vert.x code generator
A code generator consist of an MVEL template declared in a codegen.json
descriptor:
{
"name": "Groovy",
"generators": [ {
"kind": "class",
"filename": "'groovy/' + fqn.replace('io.vertx', 'io.vertx.groovy').replace('.', '/') + '.groovy'",
"templateFilename": "vertx-groovy/template/groovy.templ"
} ]
}
filename
is an MVEL expression for the file name, returning null skips the generation.templateFilename
is the name of the MVEL template to applyincremental
true when the template performs incremental processing, false or absent otherwisekind
: there are several kinds of generators for different use casesclass
: applied on each API classespackage
: applied on each Java packagemodule
: applied on each declared module, a module uniquely identifies an APIdataObject
: applied on each data object classproxy
: applied on each proxy classenum
: applied on each enum class annotated with@VertxGen
There can be as many generators as you like.
A generator can create 3 different kinds of output: Java classes, resources and anything else
A generator declaring a filename that matches a Java FQN followed by .java
suffix will have its content generated
as a Java class. This class will be automatically compiled by the same compiler (that's a Java compiler feature).
The generated files are handled by the Java compiler (-s
option), usually build tools configures the compiler to store
them in a specific build location, for instance Maven by default uses the target/generated-sources/annotations
directory.
The following generators use it:
- Data object converters
- Service proxy and service handler
- RxJava-ified classes API
- Groovy extension methods API
A generator declaring a filename prefixed by resources/
will have its content generated as a compiler resource. This
resource will be stored in the generated sources directory and the generated classes directory.
The generated files are handled by the Java compiler (-s
option), usually build tools configures the compiler to store
them in a specific build location, for instance Maven by default uses the target/generated-sources/annotations
directory.
The following generators use it:
- JavaScript generator
- Ruby generator
Anything else will be stored in the file system using the filename, when the filename
is relative (it usually is)
the target path will be resolved agains the codegen.output
directory.
The following generators use it:
- Ceylon generator
- Scala generator
- Kotlin extension methods
When the codegen.output
is not specified, the generated files are discarded.
Sometimes you want to have a generator to output its files in another directory, you can do that with the
codegen.output.generator-name
compiler option:
<codegen.output.data_object_converters>generated</codegen.output.data_object_converters>
The generator will store its content in the codegen.output/generated
directory instead as a Java class.
You can configure the CodeGenProcessor
as any Java annotation processor, here is how to do with Maven:
<pluginManagement>
<plugins>
<!-- Configure the execution of the compiler to execute the codegen processor -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessors>
<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<arg>-AoutputDirectory=${project.basedir}/src/main</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
And here is a configuration example for Gradle:
task annotationProcessing(type: JavaCompile, group: 'build') { // codegen
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
destinationDir = project.file('src/main/generated')
options.compilerArgs = [
"-proc:only",
"-processor", "io.vertx.codegen.CodeGenProcessor",
"-AoutputDirectory=${project.projectDir}/src/main"
]
}
compileJava {
targetCompatibility = 1.8
sourceCompatibility = 1.8
dependsOn annotationProcessing
}
sourceSets {
main {
java {
srcDirs += 'src/main/generated'
}
}
}
Besides you can use the processor
classified dependency that declares the annotation processor as a
META-INF/services/javax.annotation.processing.Processor
, if you do so, code generation happens automatically:
<dependency>
<groupid>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<classifier>processor</classifier>
</dependency>
You still need to configure the outputDirectory
for generating files non resources/classes as the processors
requires this option to know where to place them.
The processor is configured by a few options
codegen.output
: where the non Java classes / non resources are storedcodegen.output.<generator-name>
: relocate the output of to another directorycodegen.generators
: a comma separated list of generators, each expression is a regex, allow to filter undesired generators
In order for code generation to work effectively, certain constraints are put on the Java interfaces.
The constraints are
- The API must be described as a set of Java interfaces, classes are not permitted
- Default methods are not permitted
- Nested interfaces are not permitted
- All interfaces to have generation performed on them must be annotated with the
io.vertx.codegen.annotations.VertxGen
annotation - Fluent methods (methods which return a reference to this) must be annotated with the
io.vertx.codegen.annotations.Fluent
annotation - Data object classes (classes which provide data (e.g. configuration) to methods) must be annotated with the
io.vertx.codegen.annotations.DataObject
annotation - Data object classes must provide a constructor which takes a single
io.vertx.core.json.JsonObject
parameter. - Methods where the return value must be cached in the API shim must be annotated with the
io.vertx.codegen.annotations.CacheReturn
annotation - Only certain types are allowed as parameter or return value types for any API methods (defined below).
- Custom enums should be annotated with
@VertxGen
, although this is not mandatory to allow the usage of existing Java enums
We define the following set Basic
of basic types:
- any primitive type
- any boxed primitive type
java.lang.String
We define Json
as the set of types io.vertx.core.json.JsonObject
and io.vertx.core.json.JsonArray
We define DataObject
as the set of user defined API types which are defined in its own class and annotated with @DataObject
We define TypeVar
as the set of of types variables where the variable is either declared by its generic method or its generic type
We define Api
as the set of user defined API types which are defined in its own interface and annotated with @VertxGen
We define Parameterized
as the set of user defined API types which are defined in its own interface and annotated with
@VertxGen
where the type parameters belong to:
- the type
java.lang.Void
- the set
Basic
- the set
Json
- the set
DataObject
- any enum type
- the set
Api
- the set
TypeVar
The following set Return
of types are permitted as return types from any API method:
void
- the set
Basic
- the set
Json
- the set
DataObject
- any enum type
java.lang.Throwable
- the set
TypeVar
java.lang.Object
- the set
Api
- the set
Parameterized
- type
java.util.List<C>
orjava.util.Set<C>
whereC
contains- the set
Basic
- the set
Json
- any enum type
- the set
Api
- the set
Da taObject
- the set
java.util.Map<String, C>
whereC
contains- the set
Basic
- the set
Json
- the set
The following set Param
of types are permitted as parameters to any API method:
- the set
Basic
- the set
Json
- the set
DataObject
- any enum type
- the type
java.lang.Throwable
- the set
TypeVar
java.lang.Object
- the set
Api
- the set
Parameterized
- the type
java.lang.Class<T>
where<T>
is among- the set
Basic
- the set
Json
- the set
Api
- the set
- type
java.util.List<C>
orjava.util.Set<C>
whereC
contains- the set
Basic
- the set
Json
- the set
DataObject
- the set
Api
- the set
- type
java.util.Map<String, C>
whereC
contains- the set
Basic
- the set
Json
- the set
Api
- the set
In addition any API method can have as parameter:
io.vertx.java.core.Handler<io.vertx.java.core.AsyncResult<HA>>
whereHA
contains- the set
Return
wherevoid
is interpreted asjava.lang.Void
minusjava.lang.Throwable
- the set
io.vertx.java.core.Handler<H>
whereH
contains- the set
Return
wherevoid
is interpreted asjava.lang.Void
- the set
java.util.Function<T, R>
whereT
containsReturn
andR
containsParam
Notes:
- Why no support for data object in
Map
param values ?
You may add static factory methods in your interfaces, e.g.
interface MyInterface {
static MyInterface newInterface(String foo) {
return new ....
}
}
Interfaces can extend other interfaces which also have the @VertxGen
annotation.
Interfaces annotated with @VertxGen
can either be concrete or abstract, such information is important
for languages not supporting multiple class inheritance like Groovy:
- interfaces annotated with
@VertxGen(concrete = false)
are meant to be extended by concrete interfaces and can inherit from abstract interfaces only. - interfaces annotated with
@VertxGen
or@VertxGen(concrete = true)
are implemented directly by Vertx and can inherit at most one other concrete interface and any abstract interface
If you do not wish a method to be used for generation you can annotate it with the @GenIgnore
annotation.
Generated types must belong to a module: a java package annotated with @ModuleGen
that defines a module. Such
file is created in a file package-info.java.
A module must define:
- a
name
used when generating languages that don't follow Java package naming, like JavaScript or Ruby. - a
groupPackage
to define the package of the group used for generating the generated package names (for Groovy, RxJava or Ceylon generation):
@ModuleGen(name = "acme", groupPackage="com.acme")
package com.acme.myservice;
The group package must be a prefix of the annotated module, it defines the naming of the generate packages o for the modules that belongs to the same group, in this case:
com.acme.groovy...
for Groovy APIcom.acme.rxjava...
for RxJava API
For this particular com.acme.myservice
module we have:
com.acme.groovy.myservice
for Groovy APIcom.acme.rxjava.myservice
for RxJava API
Vert.x Apis uses the io.vertx
group package and vertx-XYZ
name, this naming is exclusively reserved
to Vert.x Apis.
NOTE: using Maven coordinates for name and group package is encouraged: the name corresponding to the
Maven artifactId and the group package corresponding to the groupId
.
A data object is a plain Java public class annotated with @DataObject
that follows these minimum requirements:
- A constructor with
io.vertx.core.json.JsonObject
parameter type
Optionally a data object can define a public io.vertx.core.json.JsonObject toJson()
method: such method makes the
data object convertible to JsonObject
, the data object can then be used as an Api return type.
By default, a data object is responsible to decode from Json (via the JsonObject
constructor) and encode
to Json (via the toJson
method).
Data object converter can be generated with @DataObject(generateConverter=true)
by Vert.x Core. Such
Data object conversion recognize the following types as member of any @DataObject
:
- the specific
io.vertx.core.Buffer
type - the set
B
- the set
J
- any data object class annotated with
@DataObject
- type
java.util.List<C>
whereC
contains- the specific
io.vertx.core.Buffer
type - the set
B
- the set
J
- any
@DataObject
- the Object type : the
List<Object>
acts like aJsonArray
- the specific
- type
java.util.Map<String, C>
whereC
contains- the specific
io.vertx.core.Buffer
type - the set
B
- the set
J
- any
@DataObject
- the Object type : the
Map<String, Object>
acts like aJsonMap
- the specific
This is also used for data object cheatsheet generation.
Enum types can be freely used in an API, custom enum types should be annotated with @VertxGen
to allow processing of the enum. This is not mandatory to allow the reuse the existing Java enums.
Enums can be processed for providing more idiomatic APIs in some languages.
We use MVEL templating to generate APIs.
There should be a single MVEL template for each language API that is to be generated.
The template will be called once for each interface that is annotated with @VertxGen
in the Java API. One
output file (e.g. one .js file) will be created for each Java interface.
The following variables are made available to templates:
ifaceSimpleName
- the simple name of the Java interfaceifaceFQCN
- the fully qualified class name of the Java interfaceifacePackageName
- the name of the Java package the Java interface belongs toifaceComment
- the class comment from the Java interfaceconcrete
- true when the interface is implemented by vert.x useful to decide the generation of a class or interface in the API shimhelper
- a helper class that of typeio.vertx.codegen.Helper
which contains useful methods for things such as converting CamelCase to underscores.methods
- a list ofMethodInfo
objects describing each method in the interface.referencedTypes
- a list of strings representing the set of user defined types (also annotated withVertxGen
) which are referenced from the current interfacesuperTypes
- a list ofTypeInfo
representing the set of user defined types which the current interface extends fromconcreteSuperType
- the concrete super type or nullabstractSuperTypes
- subset ofsuperTypes
which are abstractmethodMap
- this is a Map<String, MethodInfo> - which allows you to look up all methods with a given nameimportedTypes
- this is aSet<TypeInfo>
containing the types used by this class
The TypeInfo
represents a Java type:
name
. Generates a string of a form suitable for representing this type in source code using qualified names, for instanceio.vertx.core.Handler<io.vertx.core.buffer.Buffer>
simpleName
. Generates a string of a form suitable for representing this type in source code using simple names, for instanceHandler<Buffer>
toString
. Same asname
collectImports(Collection<TypeInfo.Class> imports)
. Collect all imports required by this type
The TypeInfo.Class
is a subclass of TypeInfo
representing a Java class:
kind
. An enum providing more information about the typeSTRING
,BOXED_PRIMITIVE
,PRIMITIVE
: basic typesJSON_OBJECT
,JSON_ARRAY
: io.vertx.core.json.JsonObject and io.vertx.core.json.JsonArrayTHROWABLE
: java.lang.ThrowableVOID
: java.lang.VoidOBJECT
: java.lang.ObjectLIST
,SET
: corresponding java collectionsAPI
: a type annotated with @VertxGenDATA_OBJECT
: a type annotations with @DataObjectHANDLER
: io.vertx.core.HandlerASYNC_RESULT
: io.vertx.core.AsyncResultENUM
: An enumOTHER
: anything else
The MethodInfo
object has the following fields:
name
. The name of the methodkind
. The method kindHANDLER
: last parameter type isio.vertx.core.Handler<T>
and avoid
or fluent returnFUTURE
: last parameter type isio.vertx.core.Handler<io.vertx.core.AsyncResult<T>>
and avoid
or fluent returnINDEX_GETTER
: an index getterINDEX_SETTER
: an index setterOTHER
: anything else
returnType
. The fully qualified return type (orvoid
) of the methodfluent
.true
if the method is fluent (i.e. returns a reference to the interface itself for chaining calls)cacheReturn
.true
if the generated API method should cache return valuecomment
. Method comment.params
. List ofParamInfo
objects representing the parameters of the method.staticMethod
.true
if it's a static method.typeParams
. The list of the type parameters declared by the method
The ParamInfo
object has the following fields:
name
. The name of the parametertype
. The type of the parameter as aTypeInfo
dataObject
.true
If the parameter is a data object type.
type
- theTypeInfo
of the current optiondoc
- theDoc
objectconcrete
" - true when the option is implemented by vert.x - useful to decide the generation of a class or interface in the API shimgenerateConverter
- whether a converter should be generated or notinheritConverter
- whether the generated converter should convert all propertiesproperties
- aSet<PropertyInfo>
of the available properties in this optionimportedTypes
- the imported types by the optionsuperTypes
- a list ofTypeInfo
representing the set of user defined types which the current interface extends fromsuperType
- the supertype of this option ???jsonifiable
- True if the object has atoJson()
method
The PropertyInfo
object has the following fields:
name
. The name of the propertytype
. The type of the property asTypeInfo
doc
- theDoc
objectdeclared
. True if the property is declared by its data object and does not override the same property from an ancestorwriterMethod
. The name of the setter/adder method in JavareaderMethod
. The optional name of the getter method in Javaarray
. True if the property is an arrayadder
. True if the property is an adder (addSomething)jsonifiable
. True if the object can be converted somehow to json
Incremental templating allows the same template to process several models and create a single result. This is
useful when several sources files needs to generate a same file and the output is the result of the models. To
achieve incremental processing, a generator must declares "incremental": true
in its descriptor.
During the processing phase, the codegen processors collects all the files generated by incremental templates and groups them by file name. Obviously, the filename expression of the generator needs to return an appropriate string.
At the end of the processing phase, templates are invoked for each model, pretty much like the normal templating but with the following differences:
- the variable
incrementalIndex
gives the sequence number of the current model, starting at 0 - the variable
incrementalSize
gives the total number of models processed by the template - the variable
session
is a map provided that allows the template to maintain state - the generated content are appended instead of overwritten
For instance the template:
@if{incrementalIndex==0}
<html>\n
<body>\n
<ul>\n
@end{}
<li>@{type.name}</li>\n
@if{incrementalIndex==incrementalSize-1}
</ul>\n
</body>\n
</html>\n
@end{}
With codegen.json
:
{
"name": "index",
"generators": [ {
"kind": "class",
"incremental": true,
"filename": "'index.html'",
"templateFilename": "html-index.templ"
} ]
}
Generates an HTML page with the name of all the API classes.
Sometimes a template can skip the generation of the file. Setting the value of
skipFile
to true will do that:
@code{skipFile=true}