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:
- Chaque trait de soulignement dans le nom est supprimé, et la lettre suivante est en majuscule.
- 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
: renvoietrue
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()
renvoiefalse
etgetFoo()
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()
: renvoietrue
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ériquesval 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 deadd
fun DslList<String, FooProxy>.addAll(values: Iterable<String>)
, une fonction d'extension qui permet d'ajouter unIterable
d'éléments au champ répétéoperator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>)
, alias deaddAll
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 champsoneof_name
, ceux qui sont définis (voir la référence de code Java pour le type renvoyé)fun hasFoo(): Boolean
(proto2 uniquement): renvoietrue
si l'un des cas estFOO
.val foo: Int
: renvoie la valeur actuelle deoneof_name
si l'un des cas estFOO
. 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ériquesval 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 deput
à l'aide de la syntaxe d'opérateurfun 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 queList
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 champsComparable
).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 champsbytes
).operator fun contains(extension: ExtensionLite<Foo, *>): Boolean
: renvoie la valeur "true" si le champ de l'extension comporte une valeurfun 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 deadd
à l'aide de la syntaxe d'opérateuroperator 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 deaddAll
à l'aide de la syntaxe d'opérateuroperator 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.