Vert.x API Generation

Build Status

Render documentation

> mvn clean package -Pdocs
> open target/docs/vertx-codegen/java/index.html

Helper projects

API generator

A code generator is a class extending io.vertx.codegen.Generator loaded by a custom io.vertx.codegen.GeneratorLoader declared as a META-INF/services/io.vertx.codegen.GeneratorLoader JVM service.

There can be as many generators as you like.

Generated output

A generator can create 3 different kinds of output: Java classes, resources and anything else

Generated Java classes

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:

Generated resources

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:

Other generated files

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:

When the codegen.output is not specified, the generated files are discarded.

Relocation

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.

Processor configuration

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>
        <!-- Important: there are issues with apt and incremental compilation in the maven-compiler-plugin -->
        <useIncrementalCompilation>false</useIncrementalCompilation>
      </configuration>
      <executions>
        <execution>
          <id>default-compile</id>
          <configuration>
            <annotationProcessors>
              <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
            </annotationProcessors>
            <compilerArgs>
              <arg>-Acodegen.output=${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",
    "-Acodegen.output=${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 codegen.output 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

API constraints

In order for code generation to work effectively, certain constraints are put on the Java interfaces.

The constraints are

Permitted types

We define the following set Basic of basic types:

We define Json as the set of types io.vertx.core.json.JsonObject and io.vertx.core.json.JsonArray

We define 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 JavaType as the set of any Java type that does not belong to Basic, Json, DataObject, TypeVar and Api, e.g java.net.Socket. Methods are not allowed to declare such type by default and can be annotated with @GenIgnore(GenIgnore.PERMITTED_TYPE) to allow them. Such method limit the translation of the method to other languages, so it should be used with care. It is useful to allow method previously annotated with @GenIgnore to be available in code generator like RxJava that can handle Java types.

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:

We define ContainerValueType as the set of any Java type that belongs to:

The following set Return of types are permitted as return types from any API method:

The following set Param of types are permitted as parameters to any API method:

In addition any `Api method can have as parameter:

Notes:

Instance Methods

You can declare methods in your interfaces, e.g.

interface MyInterface {

    void doSomething(String foo);

}

Default method works as well

interface MyInterface {

    default String doSomething(String foo) {
      return foo != null ? new StringBuilder(foo).reverse().toString() : null;
    }

}

Static methods

You can declare static methods in your interfaces, e.g.

interface MyInterface {

    static MyInterface newInterface(String foo) {
      return new ....
    }

}

Fields

You can declare fields in your interfaces, e.g.

interface MyInterface {

    int SOME_CONSTANT = 4;

}

Super interfaces

Interfaces can extend other interfaces which also have the @VertxGen annotation.

Concrete/abstract interfaces

Interfaces annotated with @VertxGen can either be concrete or abstract, such information is important for languages not supporting multiple class inheritance like Groovy:

Ignoring methods

If you do not wish a method to be used for generation you can annotate it with the @GenIgnore annotation.

Modules

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:

@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:

For this particular com.acme.myservice module we have:

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.

Data objects

A Data object is a type that can be converted back and forth to a Json type.

You can declare data objects by:

Json mappers

A json mapper for type T is a method that maps any object of type Type, where J can be:

Json mapped types can be used anywhere a json types used are.

A json mapper turns any Java type into a data object type.

You can declare them as public static methods:

@Mapper
public static String serialize(ZonedDateTime date) {
  return date.toString();
}

@Mapper
public static ZonedDateTime deserialize(String s) {
  return ZonedDateTime.parse(s);
}

@DataObject annotated types

A @DataObject annotated type is a Java class with the only purpose to be a container for data.

Data object conversion recognize the following types as member of any @DataObject:

This is also used for data object cheatsheet generation.

Enums

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.