REMARQUE:Ce site est obsolète. Le site sera désactivé après le 31 janvier 2023 et le trafic redirigera vers le nouveau site à l'adresse https://protobuf.dev. En attendant, les mises à jour ne seront effectuées que sur protobuf.dev.

Code généré par Kotlin

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Cette page décrit précisément le code Kotlin généré par le compilateur de tampon de protocole pour toute définition de protocole donnée, en plus du code généré pour Java. Toute différence entre le code généré proto2 et le code généré proto3 est mise en évidence. Notez que ces différences résident dans le code généré tel que décrit dans ce document et non dans les classes/interfaces de messages de base, qui sont identiques dans les deux versions. Lisez le guide du langage proto2 et/ou le guide du langage proto3 avant de lire ce document.

Appel du compilateur

Le compilateur de tampon de protocole génère du code Kotlin qui s'appuie sur le code Java. Par conséquent, il doit être appelé avec deux options de ligne de commande, --java_out= et --kotlin_out=. Le paramètre de l'option --java_out= correspond au répertoire dans lequel vous souhaitez que le compilateur écrive votre sortie Java, et il s'agit de la même chose que --kotlin_out=. Pour chaque entrée de fichier .proto, le compilateur crée un fichier .java de wrapper contenant une classe Java qui représente le fichier .proto lui-même.

Que votre fichier .proto contienne ou non une ligne de ce type :

option java_multiple_files = true;

Le compilateur crée des fichiers .kt distincts pour chacune des classes et des méthodes de fabrique qu'il génère pour chaque message de niveau supérieur déclaré dans le fichier .proto.

Le nom du package Java de chaque fichier est identique à celui utilisé par le code Java généré, comme décrit dans la documentation de référence sur le code généré par Java.

Pour choisir le fichier de sortie, vous devez concaténer le paramètre en --kotlin_out=, le nom du package (avec les éléments . remplacés par des éléments /) et le suffixe du fichier Kt.kt.

Par exemple, imaginons que vous invoquez le compilateur comme suit :

protoc --proto_path=src --java_out=build/gen/java --kotlin_out=build/gen/kotlin src/foo.proto

Si le package Java de foo.proto est com.example et qu'il contient un message nommé Bar, le compilateur de tampon de protocole générera le fichier build/gen/kotlin/com/example/BarKt.kt. Le compilateur de tampon de protocole créera automatiquement les répertoires build/gen/kotlin/com et build/gen/kotlin/com/example, si nécessaire. Cependant, build/gen/kotlin, build/gen ou build ne seront pas créés, car ils doivent déjà exister. Vous pouvez spécifier plusieurs fichiers .proto dans un même appel. Tous les fichiers de sortie seront générés en même temps.

Messages

Voici une déclaration de message simple :

message FooBar {}

En plus du code Java généré, le compilateur de tampon de protocole génère un objet appelé FooBarKt ainsi que deux fonctions de niveau supérieur ayant la structure suivante:

object FooBarKt {
  class Dsl private constructor { ... }
}
inline fun fooBar(block: FooBarKt.Dsl.() -> Unit): FooBar
inline fun FooBar.copy(block: FooBarKt.Dsl.() -> Unit): FooBar

Types imbriqués

Un message peut être déclaré dans un autre message. Exemple : message Foo { message Bar { } }

Dans ce cas, le compilateur imbrique l'objet BarKt et la méthode d'usine bar dans FooKt, bien que la méthode copy reste de premier niveau:

object FooKt {
  class Dsl { ... }
  object BarKt {
    class Dsl private constructor { ... }
  }
  inline fun bar(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar
}
inline fun foo(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.copy(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.Bar.copy(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar

Champs

Outre les méthodes décrites dans la section précédente, le compilateur de tampon de protocole génère des propriétés modifiables dans la DSL pour chaque champ défini dans le message du fichier .proto. (Kotlin utilise déjà les propriétés "read-only" de l'objet de message à partir des "getters" générés par Java).

Notez que les propriétés utilisent toujours une dénomination en camel case, même si le nom du champ du fichier .proto utilise des minuscules et des traits de soulignement (comme cela devrait être le cas). La conversion de la casse se déroule comme suit:

  1. Chaque trait de soulignement dans le nom est supprimé, et la lettre suivante est en majuscule.
  2. Si un préfixe est ajouté au nom (par exemple, "effacer"), la première lettre est en majuscule. Sinon, il est en minuscules.

Ainsi, le champ foo_bar_baz devient fooBarBaz.

Dans certains cas particuliers, lorsqu'un nom de champ entre en conflit avec des mots réservés en Kotlin ou avec des méthodes déjà définies dans la bibliothèque protobuf, un trait de soulignement est ajouté. Par exemple, la valeur la plus claire pour un champ nommé in est clearIn_().

Champs singuliers (proto2)

Pour l'une des définitions de champs suivantes :

optional int32 foo = 1;
required int32 foo = 1;

Le compilateur génère les accesseurs suivants dans le DSL:

  • fun hasFoo(): Boolean : renvoie true si le champ est défini.
  • var foo: Int : valeur actuelle du champ. Si le champ n'est pas défini, affiche la valeur par défaut.
  • fun clearFoo(): efface la valeur du champ. Après avoir appelé cela, hasFoo() renvoie false et getFoo() renvoie la valeur par défaut.

Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération. Le type de message étant toujours défini en Java, les types non signés dans le message sont représentés à l'aide des types signés correspondants dans le DSL, afin d'assurer la compatibilité avec Java et les versions antérieures de Kotlin.

Champs de message intégrés

Notez qu'il n'existe pas de traitement particulier des sous-messages. Par exemple, si vous avez un champ

optional Foo my_foo = 1;
vous devez écrire
myFoo = foo {
  ...
}

En général, cela est dû au fait que le compilateur ne sait pas si Foo dispose d'une DSL Kotlin ou ne dispose que des API Java générées. Cela signifie que vous n'avez pas à attendre les messages dont vous dépendez pour ajouter la génération de code Kotlin.

Nous fournissons également un accesseur de champ supplémentaire FooOrNull pour tous les champs de message facultatifs qui renvoient la valeur du champ, le cas échéant.

Champs singuliers (proto3)

Pour cette définition de champ :

int32 foo = 1;

Le compilateur génère la propriété suivante dans le DSL:

  • var foo: Int : affiche la valeur actuelle du champ. Si le champ n'est pas défini, affiche la valeur par défaut correspondant au type du champ.
  • fun clearFoo() : efface la valeur du champ. Une fois la méthode appelée, getFoo() affiche la valeur par défaut correspondant au type du champ.

Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération. Le type de message étant toujours défini en Java, les types non signés dans le message sont représentés à l'aide des types signés correspondants dans le DSL, afin d'assurer la compatibilité avec Java et les versions antérieures de Kotlin.

Champs de message intégrés

Pour les types de champs de message, une méthode d'accesseur supplémentaire est générée dans le DSL:

  • boolean hasFoo() : renvoie true si le champ a été défini.

Notez qu'il n'existe aucun raccourci pour définir un sous-message basé sur une DSL. Par exemple, si vous avez un champ

Foo my_foo = 1;
vous devez écrire
myFoo = foo {
  ...
}

En général, cela est dû au fait que le compilateur ne sait pas si Foo dispose d'une DSL Kotlin ou ne dispose que des API Java générées. Cela signifie que vous n'avez pas à attendre les messages dont vous dépendez pour ajouter la génération de code Kotlin.

Champs répétés

Pour cette définition de champ :

repeated string foo = 1;

Le compilateur génère les membres suivants dans le DSL:

  • class FooProxy: DslProxy, qui n'est pas accrocheur et ne s'utilise que dans des caractères génériques
  • val fooList: DslList<String, FooProxy>, une vue en lecture seule de la liste des éléments actuels dans le champ répété
  • fun DslList<String, FooProxy>.add(value: String), une fonction d'extension permettant d'ajouter des éléments au champ répété
  • operator fun DslList<String, FooProxy>.plusAssign(value: String), alias de add
  • fun DslList<String, FooProxy>.addAll(values: Iterable<String>), une fonction d'extension qui permet d'ajouter un Iterable d'éléments au champ répété
  • operator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>), alias de addAll
  • operator fun DslList<String, FooProxy>.set(index: Int, value: String), une fonction d'extension définissant la valeur de l'élément à l'index basé sur zéro donné
  • fun DslList<String, FooProxy>.clear(), une fonction d'extension effaçant le contenu du champ répété

Cette construction inhabituelle permet à fooList de "comporter comme" une liste modifiable dans le cadre de la DSL, en n'acceptant que les méthodes compatibles avec le compilateur sous-jacent, tout en empêchant la mutabilité d'"échapper" la DSL, ce qui peut entraîner des effets secondaires prêtant à confusion.

Pour les autres types de champs simples, le type Java correspondant est choisi conformément à la table des types de valeurs scalaires. Pour les types de message et d'énumération, le type est le message ou la classe d'énumération.

Un champ

Pour cette définition de champ :

oneof oneof_name {
    int32 foo = 1;
    ...
}

Le compilateur génère les méthodes d'accesseur suivantes dans le DSL:

  • val oneofNameCase: OneofNameCase: quels sont, parmi les champs oneof_name, ceux qui sont définis (voir la référence de code Java pour le type renvoyé)
  • fun hasFoo(): Boolean (proto2 uniquement): renvoie true si l'un des cas est FOO.
  • val foo: Int: renvoie la valeur actuelle de oneof_name si l'un des cas est FOO. Sinon, renvoie la valeur par défaut de ce champ.

Pour les autres types de champs simples, le type Java correspondant est choisi conformément au tableau des types de valeurs scalaires. Pour les types de messages et d'énumération, le type de valeur est remplacé par la classe de message ou d'énumération.

Champs de carte

Pour cette définition de champ de mappage :

map<int32, int32> weight = 1;

Le compilateur génère les membres suivants dans la classe DSL:

  • class WeightProxy private constructor(): DslProxy(), qui n'est pas accrocheur et ne s'utilise que dans des caractères génériques
  • val weight: DslMap<Int, Int, WeightProxy>, une vue en lecture seule des entrées actuelles dans le champ de carte.
  • fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int) : ajoute l'entrée à ce champ de mappage.
  • operator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): alias de put à l'aide de la syntaxe d'opérateur
  • fun DslMap<Int, Int, WeightProxy>.remove(key: Int): supprime l'entrée associée à key, le cas échéant.
  • fun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>) : ajoute toutes les entrées de la carte spécifiée à ce champ de mappage et remplace les valeurs précédentes pour les clés déjà présentes.
  • fun DslMap<Int, Int, WeightProxy>.clear() : efface toutes les entrées de ce champ de mappage.

Extensions (proto2 uniquement)

Message reçu avec une plage d'extensions :

message Foo {
  extensions 100 to 199;
}

Le compilateur de tampon de protocole ajoutera les méthodes suivantes à FooKt.Dsl :

  • operator fun <T> get(extension: ExtensionLite<Foo, T>): T : récupère la valeur actuelle du champ de l'extension dans le DSL.
  • operator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>: récupère la valeur actuelle du champ d'extension répété dans le DSL en tant que List en lecture seule.
  • operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>) : définit la valeur actuelle du champ d'extension dans le DSL (pour les types de champs Comparable).
  • operator fun <T : MessageLite<T>> set(extension: ExtensionLite<Foo, T>) : définit la valeur actuelle du champ d'extension dans le DSL (pour les types de champs de message).
  • operator fun set(extension: ExtensionLite<Foo, ByteString>) : définit la valeur actuelle du champ de l'extension dans le DSL (pour les champs bytes).
  • operator fun contains(extension: ExtensionLite<Foo, *>): Boolean: renvoie la valeur "true" si le champ de l'extension comporte une valeur
  • fun clear(extension: ExtensionLite<Foo, *>) : efface le champ d'extension.
  • fun <E> ExtensionList<Foo, E>.add(value: E) : ajoute une valeur au champ d'extension répété.
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E) : alias de add à l'aide de la syntaxe d'opérateur
  • operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): ajoute plusieurs valeurs au champ d'extension répété.
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): alias de addAll à l'aide de la syntaxe d'opérateur
  • operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): définit l'élément du champ d'extension répété sur l'index spécifié.
  • operator fun ExtensionList<Foo, *>.clear(): efface les éléments du champ d'extension répété.

Ici, les caractères génériques sont complexes, mais le résultat est que this[extension] = value fonctionne pour tous les types d'extension, à l'exception des extensions répétées et des extensions répétées ayant une syntaxe de liste "naturelle" qui fonctionne de la même manière que les champs répétés non associés à une extension.

Selon une définition d'extension :

extend Foo {
  optional int32 bar = 123;
}

Java génère l'"identifiant d'extension" bar, qui est utilisé pour les opérations d'extension de "key" ci-dessus.