Kotlin generierter Code-Leitfaden
Alle Unterschiede zwischen proto2, proto3 und Edges generiertem Code werden hervorgehoben – beachten Sie, dass diese Unterschiede im generierten Code liegen, wie in diesem Dokument beschrieben, nicht in den zugrunde liegenden Nachrichtenklassen/-schnittstellen, die in beiden Versionen gleich sind. Sie sollten den proto2 Sprachleitfaden, proto3 Sprachleitfaden und/oder den Editionsleitfaden lesen, bevor Sie dieses Dokument lesen.
Compiler-Aufruf
Der Protocol Buffer Compiler erzeugt Kotlin-Code, der auf Java-Code aufbaut. Daher muss er mit zwei Kommandozeilenflags aufgerufen werden: --java_out= und --kotlin_out=. Der Parameter für die Option --java_out= ist das Verzeichnis, in dem der Compiler Ihren Java-Output schreiben soll, und dasselbe gilt für --kotlin_out=. Für jede eingegebene .proto-Datei erstellt der Compiler eine Wrapper-.java-Datei mit einer Java-Klasse, die die .proto-Datei selbst darstellt.
Unabhängig davon, ob Ihre .proto-Datei eine Zeile wie die folgende enthält oder nicht
option java_multiple_files = true;
Der Compiler erstellt separate .kt-Dateien für jede der Klassen und Factory-Methoden, die er für jede in der .proto-Datei deklarierte Top-Level-Nachricht generieren wird.
Der Java-Paketname für jede Datei ist derselbe wie der des generierten Java-Codes, wie im Java generierter Code Referenz beschrieben.
Die Ausgabedatei wird durch die Verkettung des Parameters für --kotlin_out=, des Paketnamens (mit durch Schrägstriche [/] ersetzten Punkten [.]) und der Dateiendung Kt.kt ausgewählt.
Nehmen wir also zum Beispiel an, Sie rufen den Compiler wie folgt auf:
protoc --proto_path=src --java_out=build/gen/java --kotlin_out=build/gen/kotlin src/foo.proto
Wenn das Java-Paket von foo.proto com.example ist und eine Nachricht namens Bar enthält, generiert der Protocol Buffer Compiler die Datei build/gen/kotlin/com/example/BarKt.kt. Der Protocol Buffer Compiler erstellt automatisch die Verzeichnisse build/gen/kotlin/com und build/gen/kotlin/com/example, falls erforderlich. Er erstellt jedoch nicht build/gen/kotlin, build/gen oder build; diese müssen bereits existieren. Sie können mehrere .proto-Dateien in einem einzigen Aufruf angeben; alle Ausgabedateien werden gleichzeitig generiert.
Nachrichten
Angesichts einer einfachen Nachrichten Deklaration:
message FooBar {}
Der Protocol Buffer Compiler generiert zusätzlich zum generierten Java-Code ein Objekt namens FooBarKt sowie zwei Top-Level-Funktionen mit folgender Struktur
object FooBarKt {
class Dsl private constructor { ... }
}
inline fun fooBar(block: FooBarKt.Dsl.() -> Unit): FooBar
inline fun FooBar.copy(block: FooBarKt.Dsl.() -> Unit): FooBar
Verschachtelte Typen
Eine Nachricht kann innerhalb einer anderen Nachricht deklariert werden. Zum Beispiel:
message Foo {
message Bar { }
}
In diesem Fall verschachtelt der Compiler das BarKt-Objekt und die bar-Factory-Methode innerhalb von FooKt, obwohl die copy-Methode auf Top-Level-Ebene verbleibt.
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
Felder
Zusätzlich zu den im vorherigen Abschnitt beschriebenen Methoden generiert der Protocol Buffer Compiler mutable Eigenschaften in der DSL für jedes Feld, das innerhalb der Nachricht in der .proto-Datei definiert ist. (Kotlin leitet bereits Lese-/Schreibe-Eigenschaften aus den von Java generierten Gettern für das Nachrichtenobjekt ab.)
Beachten Sie, dass Eigenschaften immer im Camelcase-Stil benannt werden, auch wenn der Feldname in der .proto-Datei kleingeschrieben mit Unterstrichen ist (wie er sein sollte). Die Fallumwandlung funktioniert wie folgt
- Für jeden Unterstrich im Namen wird der Unterstrich entfernt und der folgende Buchstabe großgeschrieben.
- Wenn dem Namen ein Präfix hinzugefügt wird (z. B. "clear"), wird der erste Buchstabe großgeschrieben. Andernfalls wird er kleingeschrieben.
Somit wird das Feld foo_bar_baz zu fooBarBaz.
In einigen Sonderfällen, in denen ein Feldname mit reservierten Wörtern in Kotlin oder bereits im Protobuf-Bibliothek definierten Methoden kollidiert, wird ein zusätzlicher Unterstrich angehängt. Beispielsweise ist der "clearer" für ein Feld namens in clearIn_().
Singuläre Felder
Für diese Felddefinition:
int32 foo = 1;
Der Compiler generiert die folgenden Accessoren in der DSL
fun hasFoo(): Boolean: Gibttruezurück, wenn das Feld gesetzt ist. Dies wird nicht für Felder mit impliziter Anwesenheit generiert.var foo: Int: Der aktuelle Wert des Feldes. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.fun clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()falsezurück undgetFoo()gibt den Standardwert zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der skalaren Werttypen gewählt. Für Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt. Da der Nachrichtentyp weiterhin in Java definiert ist, werden unsignierte Typen in der Nachricht zur Kompatibilität mit Java und älteren Versionen von Kotlin mithilfe der standardmäßigen entsprechenden signierten Typen in der DSL dargestellt.
Eingebettete Nachrichtenfelder
Beachten Sie, dass es keine spezielle Behandlung von Subnachrichten gibt. Wenn Sie beispielsweise ein Feld haben
optional Foo my_foo = 1;
müssen Sie Folgendes schreiben
myFoo = foo {
...
}
Dies liegt im Allgemeinen daran, dass der Compiler nicht weiß, ob Foo überhaupt eine Kotlin-DSL hat oder z. B. nur die Java-APIs generiert hat. Das bedeutet, dass Sie nicht darauf warten müssen, dass Nachrichten, von denen Sie abhängen, die Kotlin-Code-Generierung hinzufügen.
Wiederholte Felder
Für diese Felddefinition:
repeated string foo = 1;
Der Compiler generiert die folgenden Member in der DSL
class FooProxy: DslProxy, ein nicht instanziierbarer Typ, der nur in Generika verwendet wirdval fooList: DslList<String, FooProxy>, eine schreibgeschützte Ansicht der Liste der aktuellen Elemente im wiederholten Feldfun DslList<String, FooProxy>.add(value: String), eine Erweiterungsfunktion, die das Hinzufügen von Elementen zum wiederholten Feld ermöglichtoperator fun DslList<String, FooProxy>.plusAssign(value: String), ein Alias füraddfun DslList<String, FooProxy>.addAll(values: Iterable<String>), eine Erweiterungsfunktion, die das Hinzufügen einesIterablevon Elementen zum wiederholten Feld ermöglichtoperator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>), ein Alias füraddAlloperator fun DslList<String, FooProxy>.set(index: Int, value: String), eine Erweiterungsfunktion zum Festlegen des Werts des Elements am angegebenen nullbasierten Indexfun DslList<String, FooProxy>.clear(), eine Erweiterungsfunktion zum Löschen des Inhalts des wiederholten Feldes
Diese ungewöhnliche Konstruktion ermöglicht es fooList, sich "wie" eine mutable Liste im Geltungsbereich der DSL zu verhalten, unterstützt nur die vom zugrunde liegenden Builder unterstützten Methoden, verhindert aber, dass die Mutabilität die DSL "verlässt", was zu verwirrenden Nebeneffekten führen könnte.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der skalaren Werttypen gewählt. Für Nachrichten- und Enum-Typen ist der Typ die Nachrichten- oder Enum-Klasse.
Oneof-Felder
Für diese Oneof-Felddatendefinition
oneof oneof_name {
int32 foo = 1;
...
}
Der Compiler generiert die folgenden Zugriffsmethoden in der DSL
val oneofNameCase: OneofNameCase: gibt an, welches, falls vorhanden, der Felder vononeof_namegesetzt ist; siehe die Java-Code-Referenz für den Rückgabetypfun hasFoo(): Boolean: Gibttruezurück, wenn der Oneof-FallFOOist.val foo: Int: Gibt den aktuellen Wert vononeof_namezurück, wenn der Oneof-FallFOOist. Andernfalls gibt er den Standardwert dieses Feldes zurück.
Für andere einfache Feldtypen wird der entsprechende Java-Typ gemäß der Tabelle der skalaren Werttypen gewählt. Für Nachrichten- und Enum-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Map-Felder
Für diese Map-Felddefinition
map<int32, int32> weight = 1;
Der Compiler generiert die folgenden Member in der DSL-Klasse
class WeightProxy private constructor(): DslProxy(), ein nicht instanziierbarer Typ, der nur in Generika verwendet wirdval weight: DslMap<Int, Int, WeightProxy>, eine schreibgeschützte Ansicht der aktuellen Einträge im Map-Feldfun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): fügt den Eintrag zu diesem Map-Feld hinzuoperator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): Alias fürputunter Verwendung der Operator-Syntaxfun DslMap<Int, Int, WeightProxy>.remove(key: Int): entfernt den Eintrag, der mitkeyassoziiert ist, falls vorhandenfun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>): fügt alle Einträge aus der angegebenen Map zu diesem Map-Feld hinzu und überschreibt frühere Werte für bereits vorhandene Schlüsselfun DslMap<Int, Int, WeightProxy>.clear(): löscht alle Einträge aus diesem Map-Feld
Erweiterungen (Extensions)
Gegeben eine proto2- oder Edges-Nachricht mit einem Erweiterungsbereich
message Foo {
extensions 100 to 199;
}
Der Protocol Buffer Compiler fügt der FooKt.Dsl die folgenden Methoden hinzu
operator fun <T> get(extension: ExtensionLite<Foo, T>): T: Ruft den aktuellen Wert des Erweiterungsfeldes in der DSL aboperator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>: Ruft den aktuellen Wert des wiederholten Erweiterungsfeldes in der DSL als schreibgeschützteListaboperator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>): Setzt den aktuellen Wert des Erweiterungsfeldes in der DSL (fürComparableFeldtypen)operator fun <T : MessageLite> set(extension: ExtensionLite<Foo, T>): Setzt den aktuellen Wert des Erweiterungsfeldes in der DSL (für Nachrichtenfeldtypen)operator fun set(extension: ExtensionLite<Foo, ByteString>): Setzt den aktuellen Wert des Erweiterungsfeldes in der DSL (fürbytesFelder)operator fun contains(extension: ExtensionLite<Foo, *>): Boolean: Gibttruezurück, wenn das Erweiterungsfeld einen Wert hatfun clear(extension: ExtensionLite<Foo, *>): Löscht das Erweiterungsfeldfun <E> ExtensionList<Foo, E>.add(value: E): Fügt einen Wert zum wiederholten Erweiterungsfeld hinzuoperator fun <E> ExtensionList<Foo, E>.plusAssign(value: E): Alias füraddunter Verwendung der Operator-Syntaxoperator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): Fügt mehrere Werte zum wiederholten Erweiterungsfeld hinzuoperator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): Alias füraddAllunter Verwendung der Operator-Syntaxoperator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): Setzt das Element des wiederholten Erweiterungsfeldes am angegebenen Indexinline fun ExtensionList<Foo, *>.clear(): Löscht die Elemente des wiederholten Erweiterungsfeldes
Die Generika sind hier komplex, aber das Ergebnis ist, dass this[extension] = value für jeden Erweiterungstyp außer wiederholten Erweiterungen funktioniert, und wiederholte Erweiterungen haben eine "natürliche" Listen-Syntax, die ähnlich wie bei nicht-erweiterten wiederholten Feldern funktioniert.
Gegeben eine Erweiterungsdefinition
extend Foo {
int32 bar = 123;
}
Java generiert die "Erweiterungs-ID" bar, die verwendet wird, um die obigen Erweiterungsoperationen zu "schlüsseln".