Skip navigation links

Package io.vertx.codegen

= Vert.x Codegen Vert.x Codegen is an annotation processing tool for processing Vert.x API and create API in different JVM lauguages.

See: Description

Package io.vertx.codegen Description

= Vert.x Codegen Vert.x Codegen is an annotation processing tool for processing Vert.x API and create API in different JVM lauguages. Vert.x polyglot langs use code generation for creating https://en.wikipedia.org/wiki/Shim_(computing)[shim] for APIs, that are thin wrappers in front of the API type. A shim is the adaptation of a Vert.x API to a JVM language. A shim uses code generation to generate wrappers in its own language delegating to the Vert.x API, code generation is based on Java annotation processing. == Vert.x API A Vert.x API is a collection of Java types annotated with Codegen annotations that respect a set of constraints to ensure easy portability of the API to most languages and enable an asynchronous programming model. Such collection of API types are grouped in modules. === Modules A module contains a collection of Vert.x API and data objects declarations, some shim uses modules for organizing and loading the Vert.x api: - the JavaScript shim uses https://en.wikipedia.org/wiki/CommonJS[CommonJS] modules - the Ruby shim uses Ruby modules - Ceylon uses Ceylon native modules NOTE: so far in Java or Groovy the notion of module is not used because it delegates loading to classloaders. Modules are declared by annotating a Java package with a @ModuleGen annotation. [source,java] ---- @ModuleGen(name = "acme", groupPackage="com.acme") package com.acme.myservice; import io.vertx.codegen.annotations.ModuleGen; ---- The module _name_ is a namespace for shims that don't support package like naming, like JavaScript `acme-js` or Ruby `acme`, whereas the _group package_ determines the created package, for instance `com.acme.groovy.myservice` in Groovy. The group package must be a prefix of the annotated module. Vert.x modules use the reserved name _vertx-XYZ_ and the reserved group package _io.vertx_. An API module contains various Vert.x types annotated with VertxGen or DataObject. 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 Java class with the only purpose to be a container for data. They are transformed to and from Json. In its simplest form, a data object is a Java class following these rules: 1. the class is annotated with DataObject 2. provide a constructor with the `io.vertx.core.json.JsonObject` argument A data object can also be an interface annotated with `@DataObject`, this is useful when multiple inheritance is needed. For instance Vert.x Core defines the `KeyCertOptions` and `TrustOptions` data object interfaces and the `JksOptions` is a data object class that implements both of them because a Java keystore provides support for both. Data object can also inherit from other data objects. It inherits from the properties of the parent classes. ==== Data object properties DataObject properties are declared via _getters_, _setters_ or _adders_: .a getter and a setter [source,java] ---- public String getHost() { return host; } public WebServerOptions setHost(String host) { this.host = host; return this; } ---- Here is the list of supported property single valued types: 1. any primitive or boxed primitive type 2. `java.lang.String` 3. `io.vertx.core.json.JsonObject` and `io.vertx.core.json.JsonArray` 4. the specific `io.vertx.core.buffer.Buffer` type providing support for byte array 5. Java enums 6. another data object In addition a data object can also have multi-valued properties as a `java.util.List`/`java.util.Set` or a `java.util.Map` where the `` is a supported single valued type or `java.lang.Object` that stands for anything converted by `io.vertx.core.json.JsonObject` and `io.vertx.core.json.JsonArray`. List/set multi-valued properties can be declared via a _setter_ : .a multi valued setter [source,java] ---- public WebServerOptions setCertificates(List certificates) { this.certificates = certificates; return this; } ---- Or an _adder_ : .a multi valued adder [source,java] ---- public WebServerOptions addCertificate(String certificate) { this.certificates.add(certificate); return this; } ---- Map properties can only be declared with a _setter_. NOTE: these examples uses a _fluent_ return types for providing a better API, this is not mandatory but encouraged. ==== Json conversion Vert.x requires data object to be convertible from json with the json argument constructor. Altough there is no strict rules of mapping between the data object properties and the json structure, it is a good thing to follow a common mapping for users using json data objects (like in JavaScript shim). Json properties should be named after properties according to JavaBean conversion rules: - a single valued property follows the JavaBean convention - a multi valued property declared with a list setter follows the same convention - a multi valued property declared with an adder must use a singular form and the json property name gets a trailing _s_ In all case, property names are _normalized_, i.e: - _red_ -> _red_ - _Red_ -> _red_ - _URL_ -> _url_ - _URLFactory_ -> _urlFactory_ ==== Jsonifiable data object When a data object declares a `public JsonObject toJson()` method it can be converted to the json format and is said _jsonifiable_. Vert.x API types have restriction in the declared method return types, a jsonifiable data object can be used in Vert.x API method return types or handlers because it can be converted to a json format, otherwise it is not permitted. ==== Data object converter Vert.x requires only the conversion from json, but the conversion to json can be useful when the API uses data objects as return types. The implementation of the data object / json conversion can be tedious and error prone. The data object / json conversion can be automated based on the rules given above. Vert.x Core allows to generate auxilliary classes that implement the conversion logic. The generated converters handle the type mapping as well as the json naming convention. Converters are generated when the data object is annotated with `@DataObject(generateConverter=true)`. The generation happens for the data object properties, not for the ancestor properties, unless `inheritConverter` is set: `@DataObject(generateConverter=true,inheritConverter=true)`. The converter is named by appending the `Converter` suffix to the data object class name, e.g, `ContactDetails` -> `ContactDetailsConverter`. The generated converter has two static methods: - `public static void fromJson(JsonObject json, ContactDetails obj)` - `public static void toJson(ContactDetails obj, JsonObject json)` The former should be used in the json constructor, the later in the `toJson` method. [source,java] ---- public ContactDetails(JsonObject json) { this(); ContactDetailsConverter.fromJson(json, this); } public JsonObject toJson() { JsonObject json = new JsonObject(); ContactDetailsConverter.toJson(this, json); return json; } ---- === Building types A few types used throughout Vert.x API are not annotated with `@VertxGen` yet are used for building the API: - `io.vertx.core.Handler` - `java.util.function.Function` - `io.vertx.core.AsyncResult` - `io.vertx.core.json.JsonObject` - `io.vertx.core.json.JsonArray` - `java.lang.Object` - `java.lang.Throwable` - `java.lang.Void` - `java.lang.String` - `java.util.List` - `java.util.Set` - `java.util.Map` - primitive and boxed primitives These types are usually handled natively by shims, for instance the `Handler` type is a function in JavaScript, a block in Ruby, the same `Handler` in Groovy, a function in Ceylon, etc... === Generated types An API type is a Java interface annotated with VertxGen. Vert.x provides a async / non blocking / polyglot programming model, code generated API shall follow some rules to make this possible: 1. the API must be described as a set of Java interfaces, classes are not permitted 2. nested interfaces are not permitted 3. all interfaces to have generation performed on them must be annotated with the `io.vertx.codegen.annotations.VertxGen` annotation 4. fluent methods (methods which return a reference to `this`) must be annotated with the `io.vertx.codegen.annotations.Fluent` annotation 5. methods where the return value must be cached in the API shim must be annotated with the `io.vertx.codegen.annotations.CacheReturn` annotation 6. only certain types are allowed as parameter or return value types for any API methods 7. custom enums should be annotated with `@VertxGen`, although this is not mandatory to allow the usage of existing Java enums 8. nested enums are not permitted 9. default implementations are allowed An API type can be generic or declare generic methods, type parameters must be unbounded, e.g `` is forbidden. In the perspective of codegen, Java types can be categorized as follow: . _basic_ type : any primitive/boxed type or `java.lang.String` . _json_ type : `io.vertx.core.json.JsonObject` or `io.vertx.core.json.JsonArray` . _api_ type : any type annotated with `io.vertx.codegen.annotations.VertxGen` . _data object_ type : any type annotated with `io.vertx.codegen.annotations.DataObject` . _enum_ type : any Java enum . _collection_ type : `java.util.List`, `java.util.Set` or `java.util.Map` Parameterized types are supported but wildcards are not, that is the following type arguments declarations are *forbidden*: - `Foo` - `Foo` - `Foo` Parameterized types are only supported for _api_ generic types and _collection_ types. Type variables are allowed and carry a special meaning: a type variable is a dynamic form of a _basic_ type and _json_ type. ==== Inheritance _api_ type can extend other _api_ types. An _api_ type can either be *concrete* or *abstract*, such information is important for languages not supporting multiple class inheritance like Groovy: - _api_ types annotated with VertxGen`(concrete = false)` are meant to be extended by *concrete* interfaces an can inherit from *abstract* interfaces only. - _api_ types 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 ==== Method parameter types The following method parameter types are allowed: . any _basic_ type . any _api_ type or parameterized _api_ type having type variable parameters . any _json_ type . the `java.lang.Throwable` type . any _enum_ type . any _data object_ type that is a non abstract class . an https://docs.oracle.com/javase/tutorial/java/generics/bounded.html[unbounded type variable], i.e `T extends Number` or `T super Number` are not permitted . `java.lang.Object` . a `java.util.List`, `java.util.Set` or `java.util.Map` where `` can be a _basic_ type, a _json_ type, an _API_ type. For list and set `V` can also be an _enum_ type or a _data object_ type Callback parameters are allowed, i.e types declaring `io.vertx.core.Handler` or `io.vertx.core.Handler>` where `` can be: . the `java.lang.Void` type . any _basic_ type . any _API_ type . any _json_ type . the `java.lang.Throwable` type - only for `Handler` . any _enum_ type . any _data object_ type with a `toJson` method . an https://docs.oracle.com/javase/tutorial/java/generics/bounded.html[unbounded type variable], i.e `T extends Number` or `T super Number` are not permitted . a `java.util.List`, `java.util.Set` or `java.util.Map` where `` can be a _basic_ type, a _json_ type. For list and set `V` can also be an _API_ type, an _enum_ type or a _data object_ type Function parameters are allowed, i.e types declaring `java.util.function.Function` where `` is defined to be same than for handlers and `` can be: . any _basic_ type . any _API_ type . any _json_ type . the `java.lang.Throwable` type . any _enum_ type . any _data object_ type that is a non abstract class . an unbounded type variable . a `java.util.List`, `java.util.Set` or `java.util.Map` where `` can be a _basic_ type, a _json_ type. For list and set `V` can also be an _API_ type, an _enum_ type or a _data object_ type ==== Method return type The following return types are allowed: . `void` type . any _basic_ type . any _api_ type or parameterized _api_ type having type variable parameters . any _json_ type . the `java.lang.Throwable` type . any _enum_ type . any _data object_ type with a `toJson` method . an https://docs.oracle.com/javase/tutorial/java/generics/bounded.html[unbounded type variable], i.e `T extends Number` or `T super Number` are not permitted . a `java.util.List`, `java.util.Set` or `java.util.Map` where `` can be a _basic_ type, a _json_ type. For list and set `V` can also be an _API_ type, an _enum_ type or a _data object_ type . an `Handler` where T is is a among the method parameter types . an `Handler>` where T is is a among the method parameter types ==== Method overloading Some languages don't support method overloading at all. Ruby, JavaScript or Ceylon to name a few of them. However the same restriction for Vert.x API would limit API usability. To accomodate both, overloading is supported when there are no ambiguities between overloaded signatures. When an API is analyzed an _overload check_ is performed to ensure there is no ambiguity. Here is an example of possible ambiguity: .an overload check failure [source,java] ---- void add(int x, int y); void add(double x, double y); ---- The JavaScript language use the type number in both cases: at runtime there is no possibility for the JavaScript shim to know which method to use. ==== Nullable types Null values have an impact on shim design: - shims based on value types for dispatching overloaded methods fail for null values, for example a `foo(String)` method overloaded by a `foo(Buffer)` method invoked with `foo(null)` cannot delegate to the correct underlying method in JavaScript. - some shims can leverage this information to provide a better API, for instance an `Optional` Java type or the `String?` in Ceylon, etc... Codegen provides the Nullable annotations for annotating types. Method return type can be Nullable: [source,java] ---- @Nullable String getAttribute(String name); ---- As well as method parameter type: [source,java] ---- void close(@Nullable Handler closeHandler); ---- WARNING: type validation is a non goal of this feature, its purpose is to give hints to the shim for generating correct code. These rules apply to Nullable types: . primitive types cannot be Nullable . method parameter type can be Nullable . method return type can be Nullable but not for Fluent . `io.vertx.core.Handler` type argument can be Nullable but not for `java.lang.Void` or `io.vertx.core.AsyncResult` . `io.vertx.core.Handler` type argument can be Nullable but not for `java.lang.Void` . the `java.lang.Object` type is always nullable . the `` in `` parameter/return, `Handler` or `Handler>` is implicitly nullable . the `java.lang.Object` parameter is implicitly nullable . a method overriding another method `inherits` the Nullable usage of the overriden method . a method overriding another method cannot declare Nullable in its types In addition these rules apply to Nullable type arguments: . methods cannot declare generic api types with nullable type arguments, e.g ` void method(GenericApi api)` is not permitted . methods can declare nullable collection, e.g `void method(List list)` is allowed Besides these rules, nullable types of method parameters have an impact on method overloading: the parameter at the same position cannot be Nullable more than one time when the number of method parameters is the same, e.g: [source,java] ---- void write(@Nullable String s); void write(@Nullable Buffer s); ---- is not permitted, however: [source,java] ---- void write(@Nullable String s); void write(@Nullable String s, String encoding); ---- is permitted because the number of parameters differs. === Static methods Vert.x generated types allow _static_ methods, such methods often plays the role of factory. For instance `Buffer` instance are obtained by the static method `Buffer.buffer()`, this method is translated to an equivalent in the shim. In Javascript: [source,javascript] ---- var Buffer = require('vertx-js/buffer'); var buf = Buffer.buffer(); ---- In Ruby: [source,ruby] ---- require 'vertx/buffer' buf = Vertx::Buffer.buffer() ---- In Groovy: [source,groovy] ---- def buf = io.vertx.groovy.core.Buffer.buffer(); ---- === Ignored methods Methods annotated with GenIgnore are simply ignored by codegen, this is useful when the API provides Java specific methods, for instance a method uses a type not permitted by codegen. == Shim proxies A code generated API creates shim proxies delegating method invocation to the API. .a simplified Buffer API [source,java] ---- @VertxGen public interface Buffer { static Buffer buffer(String s) { return new BufferImpl(s); } int length(); } ---- A JavaScript generated shim could look like: .the JavaScript shim [source,javascript] ---- var JBuffer = io.vertx.core.buffer.Buffer; var Buffer = function(j_val) { // delegate object var j_buffer = j_val; var that = this; this.length = function() { return j_buffer.length(); }; } Buffer.buffer = function(s) { return new Buffer(JBuffer.buffer(s)); } module.exports = Buffer; ---- The static `buffer` method is translated into the `buffer` method of the `Buffer` module, this method delegates the call to the Java static method and returns a `Buffer` proxy wrapping the returned buffer. The instance `length` method is translated into the `length` method of the proxy instance, this method delegates the call to the Java instance method of the proxied buffer and simply returns the value. The Nashorn interoperability takes care of converting the `int` type to a JavaScript `Number`. === Return values A shim implements several strategies when returning values from the Vert.x API: 1. a _basic_ value is usually handled by the shim interop 2. an _API_ value creates a proxy to wrap the value 3. a _json_ (object or array) value is translated to the shim equivalent 4. a jsonifiable _data object_ is converted to json or an equivalent 5. an _enum_ value is converted to a string or an equivalent 6. a _collection_ is usually translated to the shim equivalent 7. a `java.lang.Throwable` is usually translated to the shim equivalent 8. a type variable is converted dynamically converted to a _basic_ type or a _json_ type 9. an `Handler` value is what is used in the target language to represent an handler, when this handler is called it invokes the handler with the value converted using the argument value rules 10. an `Handler>` value is what is used in the target language to represent an async result handler, when this handler is _succeeded_ it invokes the handler with the `AsyncResult` wrapping the converted value using the argument value rules, otherwise it invokes the handler with the `AsyncResult` wrapping the throwable === Argument values A shim implements several strategies when passing values to the Vert.x API: 1. a _basic_ value is usually handled by the shim interop 2. an _API_ value is unwrapped from the shim proxy 3. a _json_ (object or array) value is translated from the shim equivalent 4. a _data object_ is instantiated from the shim equivalent by its `JsonObject` constructor 5. an _enum_ is converted from a string or an equivalent 6. a _collection_ is usually translated from the shim equivalent 7. a type variable or `java.lang.Object` is converted dynamically converted to a _basic_ type or a _json_ type === Argument handlers Argument handlers have a special treatment as the handlers gets a callback. Usually a shim creates a `io.vertx.core.Handler` instance whose `handle(E)` implementation calls back the handler argument applying the return value conversion strategy. For instance the `HttpClient#getNow` method: [source,java] ---- void getNow(int port, String host, String requestURI, Handler responseHandler); ---- Can be translated to [source,javascript] ---- function(port, host, requestURI, responseHandler) { j_httpClient.getNow(port, host, requestURI, function(jVal) { responseHandler(new HttpClientResponse(jVal)); } } ---- The JavaScript code calling passes a `function(result)`: [source,javascript] ---- vertx.setTimer(1000, function(id) { // Timer fired }); ---- `AsyncResult` types also gets a specific treatment, for instance the `HttpServer#listen` method: [source,java] ---- void listen(int port, String host, Handler> listenHandler); ---- Can be translated to [source,javascript] ---- function(port, host, listenHandler) { j_httpServer.listen(port, host, function(ar) { if (ar.succeeded()) { listenHandler(new HttpServer(ar.result()), null); } else { listenHandler(null, ar.cause()); } } } ---- The JavaScript code calling passes a `function(result, err)`: [source,javascript] ---- server.listen(80, "localhost", function(result, err) { if (result != null) { // It worked } else { // It failed } }); ---- === Argument function Function arguments are `java.util.function.Function` instances, they are usually mapped to the function type in the target language or an equivalent. === Exceptions todo === Method dispatching When a shim does not support overloading, it needs to handle the dispatch itself to the Java method, usually based on the argument types when invocation occurs. todo provide example ? == Codegen types The TypeInfo provides a codegen view of the Java type system. A type info has a ClassKind usually used to determine the conversion to apply: [cols="1,4"] .Class kinds |=== | ClassKind.STRING | `java.lang.String` | ClassKind.PRIMITIVE | any Java primitive type | ClassKind.BOXED_PRIMITIVE | any Java boxed primitive type | ClassKind.ENUM | any Java enum | ClassKind.JSON_OBJECT | `io.vertx.core.json.JsonObject` | ClassKind.JSON_ARRAY | `io.vertx.core.json.JsonArray` | ClassKind.THROWABLE | `java.lang.Throwable` | ClassKind.VOID | `java.lang.Void` | ClassKind.OBJECT | `java.lang.Object` or an unbounded type variable | ClassKind.LIST | `java.util.List` | ClassKind.SET | `java.util.Set` | ClassKind.MAP | `java.util.Map` | ClassKind.API | any _api_ type | ClassKind.DATA_OBJECT | any _data object_ type | ClassKind.HANDLER | `io.vertx.core.Handler` | ClassKind.FUNCTION | `java.util.function.Function` | ClassKind.ASYNC_RESULT | `io.vertx.core.AsyncResult` | ClassKind.OTHER | anything else |=== The `TypeInfo` base class provides common type information - TypeInfo.getKind() the type ClassKind - TypeInfo.getName() the type name - TypeInfo.getSimpleName() the simple name - TypeInfo.getErased() returns the corresponding erased type - TypeInfo.getRaw() returns the raw type of a parameter type or this type Besides it provides the TypeInfo.translateName(java.lang.String) method to translate the type name using a shim identifier, this is useful for shim using a hierarchical naming, for instance the translated name of `io.vertx.core.eventbus.EventBus` for the `groovy` identifier is `io.vertx.groovy.core.eventbus.EventBus`. The position where the identifier is applied is determined by the ModuleGen.groupPackage() value. Several subclasses of `TypeInfo` provides specialization when needed: - ClassTypeInfo : a java class - ApiTypeInfo : `TypeInfo.Class` specialization for _api_ types - EnumTypeInfo : `TypeInfo.Class` specialization for _enum_ types - ParameterizedTypeInfo : a parameterized type - PrimitiveTypeInfo : a primitive type - VoidTypeInfo : `void` (and not `java.lang.Void`) - TypeVariableInfo : an unbounded type variable == Codegen models The codegen processor _validates_ annotated Java program elements (i.e type declaration) and _transforms_ them into models: 1. `ClassModel` 2. `DataObjectModel` 3. `EnumModel` 4. `PackageModel` 5. `ModuleModel` 6. `ProxyModel` Models are processed by https://en.wikisource.org/wiki/MVEL_Language_Guide[MVEL] templates, when a template is executed it gets access to implicit properties (i.e properties that are declared by the model). For ClassModel and DataObjectModel, annotations on class and methods can be accessed too. For ModuleModel, annotations on package can be accessed. === Class model For each Java interface annotated with VertxGen a `ClassModel` is created. [cols="1,4"] .Template properties |=== | `importedTypes` | the full list of used types including `java.lang.*` types as `ClassTypeInfo` that are not in the same package | `referencedTypes` | the full list of used types including `java.lang.*` types as `ClassTypeInfo` | `referencedDataObjectTypes` | the full list of used _data object_ types as `ClassTypeInfo` | `type` | the type `ClassTypeInfo` or `ParameterizedTypeInfo` | `typeParams` | the list of class type params as `List<`TypeParamInfo.Class`>` | `concrete` | a boolean value indicating if the model is _abstract_ or _concrete_ | `superTypes` | all direct super types | `concreteSuperType` |the concrete direct super type or null | `abstractSuperTypes` | a list of all abstract direct super types | `handlerSuperType` | the type `io.vertx.core.Handler` when the type implements directly the `Handler` interface | `methods` | all the methods as `List<`MethodInfo`>` | `instanceMethods` | all the instance methods as `List<`MethodInfo`>` | `staticMethods` | all the static methods as `List<`MethodInfo`>` | `methodsByName` | a map of methods keyed by name as `MapMethodInfo`>>` | `doc` | the documentation as Doc |=== todo method info / param info / type param info === Data object model todo === Enum model todo === Package model todo === Module model todo === Proxy model todo == Code generation The CodeGenProcessor is a Java Annotation Processor that validates and applies _code generators_ on codegen models. The processor is declared in the compiler configuration, here is a typical Maven configuration: [source,xml] ---- maven-compiler-plugin default-testCompile io.vertx.codegen.CodeGenProcessor -Acodegen.output=${project.basedir}/src/test <1> ---- <1> the base output directory for generated files Code generators are determined from the classpath by looking at the `codegen.json` descriptors, there can be several generators executed in the same compilation phase. The configuration of a code generator is quite simple: [source,json] ---- { "name": "Groovy", <1> "generators": [ { <2> "kind": "class", <3> "fileName": "'groovy/' + module.translateQualifiedName(fqn, 'groovy').replace('.', '/') + '.groovy'", <4> "templateFileName": "vertx-groovy/template/groovy.templ" <5> } ] } ---- <1> the processor name <2> an array of generators <3> the kind of model the generator process : _class_, _dataObject_, _enum_, _package_, _module_, _proxy_ <4> the MVEL expression of the generated file <5> the MVEL template file name === Templating Templates are written in the MVEL language, documented here. Some characters have a special meaning: - the tab char is used for formatting purpose and is removed - the *\n* sequence has the same meaning than in a Java string literal === Incremental templating 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: [source] ---- @if{incrementalIndex==0} \n \n
    \n @end{}
  • @{type.name}
  • \n @if{incrementalIndex==incrementalSize-1}
\n \n \n @end{} ---- With `codegen.json`: [source,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.
Skip navigation links

Copyright © 2017. All rights reserved.