Na tej stronie opisujemy dokładnie, jaki kod w Kotlinie generuje kompilator bufora dla danej definicji protokołu, a nie tylko kod wygenerowany w przypadku Java. Zwróć uwagę na wszystkie różnice między kodem wygenerowanym przez proto2 a proto3 – pamiętaj, że różnice dotyczą wygenerowanego kodu zgodnie z opisem w tym dokumencie, a nie podstawowych klas/interfejsów, które są takie same w obu wersjach. Przed przeczytaniem tego dokumentu przeczytaj przewodnik po języku proto2 lub przewodnik po języku proto3.
Wywołanie kompilatora
Kompilator buforów protokołu tworzy kod Kotlin oparty na kodzie Java. W związku z tym musi być wywoływana z 2 flagami w wierszu poleceń: --java_out=
i --kotlin_out=
.
Parametr opcji --java_out=
to katalog, w którym kompilator ma zapisać dane wyjściowe Java, i ten sam parametr dla --kotlin_out=
. Kompilator tworzy dla każdego pliku .proto
otoki plik .java
zawierający klasę Java, która reprezentuje sam plik .proto
.
Niezależnie od tego, czy plik .proto
zawiera wiersz:
option java_multiple_files = true;
Kompilator utworzy oddzielne pliki .kt
dla każdej klasy i metody fabryczne, które wygeneruje dla każdej wiadomości najwyższego poziomu zadeklarowanej w pliku .proto
.
Nazwa pakietu Java dla każdego pliku jest taka sama jak nazwa wygenerowanego kodu Java w sposób podany w przewodniku po kodzie Java.
Plik wyjściowy jest wybierany przez połączenie parametru z atrybutem --kotlin_out=
, nazwą pakietu (fragmenty .
zastąpione znakami /
) i nazwą pliku Kt.kt
.
Załóżmy na przykład, że wywołujesz kompilator w ten sposób:
protoc --proto_path=src --java_out=build/gen/java --kotlin_out=build/gen/kotlin src/foo.proto
Jeśli pakiet Java foo.proto
ma wartość com.example
i zawiera komunikat o nazwie Bar
, kompilator bufora protokołu wygeneruje plik build/gen/kotlin/com/example/BarKt.kt
.
Kompilator bufora protokołu automatycznie utworzy katalogi build/gen/kotlin/com
i build/gen/kotlin/com/example
w razie potrzeby. Nie spowoduje to jednak utworzenia znaczników build/gen/kotlin
, build/gen
ani build
, które już istnieją.
Możesz określić wiele plików .proto
w jednym wywołaniu. Wszystkie pliki wyjściowe zostaną wygenerowane jednocześnie.
Wiadomości
Oto prosta deklaracja wiadomości:
message FooBar {}
Kompilator bufora protokołu generuje – oprócz wygenerowanego kodu Java – obiekt o nazwie FooBarKt
oraz 2 funkcje najwyższego poziomu o tej strukturze:
object FooBarKt { class Dsl private constructor { ... } } inline fun fooBar(block: FooBarKt.Dsl.() -> Unit): FooBar inline fun FooBar.copy(block: FooBarKt.Dsl.() -> Unit): FooBar
Typy zagnieżdżone
Daną wiadomość można zadeklarować w innej wiadomości. Na przykład:
message Foo {
message Bar {
}
}
W tym przypadku kompilator zagnieżdża obiekt BarKt
i metodę bar
w narzędziu FooKt
, ale metoda copy
pozostaje najwyższego poziomu:
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
Pola
Oprócz metod opisanych w poprzedniej sekcji kompilator bufora protokołu generuje w DSL zmodyfikowane właściwości dla każdego pola określonego w komunikacie w pliku .proto
.
(Kotlin określa już właściwości tylko do odczytu w obiekcie wiadomości z elementów pobierających wygenerowanych przez Java).
Pamiętaj, że we wszystkich nazwach liter wielbłądy są zawsze stosowane, nawet jeśli nazwa pola w pliku .proto
zawiera małe litery z podkreśleniami (jak powinna). Konwersja według zgłoszenia działa w ten sposób:
- W przypadku każdego podkreślenia w nazwie zostanie ono usunięte, a kolejna litera wielką literą.
- Jeśli do nazwy zostanie dołączony prefiks (np. „wyczyść”), pierwsza litera jest zapisana wielkimi literami. W innym przypadku jest pisana małymi literami.
W związku z tym pole foo_bar_baz
zmieni się w fooBarBaz
.
W pewnych przypadkach, gdy nazwa pola koliduje z zarezerwowanymi słowami w Kotlinie lub metodami zdefiniowanymi w bibliotece protobuf, dołączany jest dodatkowy podkreślenie.
Na przykład bardziej przejrzyste pole o nazwie in
to clearIn_()
.
Pola pojedyncze (proto2)
W przypadku dowolnej z tych pól:
optional int32 foo = 1; required int32 foo = 1;
Kompilator wygeneruje te akcesory w DSL:
fun hasFoo(): Boolean
: zwracatrue
, jeśli pole jest ustawione.var foo: Int
: bieżąca wartość pola. Jeśli pole nie jest skonfigurowane, zwraca wartość domyślną.fun clearFoo()
: czyści wartość pola. Po jego wywołaniuhasFoo()
zwraca wartośćfalse
, agetFoo()
zwraca wartość domyślną.
W przypadku innych prostych typów pól wybierany jest odpowiedni typ Java zgodnie z tabelą typów wartości skalarnych. W przypadku typów wiadomości i wyliczeń typ wartości jest zastępowany komunikatem lub klasą wyliczenia. Ponieważ typ wiadomości jest nadal zdefiniowany w Javie, niepodpisane typy w wiadomości są reprezentowane przy użyciu standardowych odpowiadających im typów w DSL, aby zapewnić zgodność z Javą i starszymi wersjami Kotlin.
Umieszczone pola wiadomości
Pamiętaj, że nie ma specjalnego sposobu obsługi wiadomości podrzędnych. Jeśli na przykład masz pole
optional Foo my_foo = 1;musisz napisać
myFoo = foo { ... }
Zasadniczo wynika to z kompilacji, że element Foo
w ogóle nie obsługuje Kotlin DSL lub że generowane są tylko interfejsy API Java. Oznacza to, że nie musisz czekać na wiadomości, których używasz do dodania kodu Kotlin.
W przypadku wszystkich opcjonalnych pól wiadomości dostępnych jest też dodatkowe pole FooOrNull
, jeśli jest ono wypełnione i jest puste.
Pola pojedyncze (proto3)
Dla tej definicji pola:
int32 foo = 1;
Kompilator wygeneruje tę właściwość w DSL:
var foo: Int
: zwraca bieżącą wartość pola. Jeśli pole nie jest ustawione, zwraca wartość domyślną typu pola.fun clearFoo()
: czyści wartość pola. Po jego wywołaniugetFoo()
zwraca wartość domyślną typu pola.
W przypadku innych prostych typów pól wybierany jest odpowiedni typ Java zgodnie z tabelą typów wartości skalarnych. W przypadku typów wiadomości i wyliczeń typ wartości jest zastępowany komunikatem lub klasą wyliczenia. Ponieważ typ wiadomości jest nadal zdefiniowany w Javie, niepodpisane typy w wiadomości są reprezentowane przy użyciu standardowych odpowiadających im typów w DSL, aby zapewnić zgodność z Javą i starszymi wersjami Kotlin.
Umieszczone pola wiadomości
W przypadku typów pól wiadomości w DSL generowana jest dodatkowa metoda metody dostępu:
boolean hasFoo()
: zwracatrue
, jeśli pole jest ustawione.
Pamiętaj, że nie ma skrótu do ustawiania komunikatu podrzędnego na podstawie lustrzanki cyfrowej. Jeśli na przykład masz pole
Foo my_foo = 1;musisz napisać
myFoo = foo { ... }
Zasadniczo wynika to z kompilacji, że element Foo
w ogóle nie obsługuje Kotlin DSL lub że generowane są tylko interfejsy API Java. Oznacza to, że nie musisz czekać na wiadomości, których używasz do dodania kodu Kotlin.
Pola powtarzane
Dla tej definicji pola:
repeated string foo = 1;
Kompilator wygeneruje te treści w DSL:
class FooProxy: DslProxy
– typ niezrozumiały, który jest używany tylko w ogólnych ustawieniach.val fooList: DslList<String, FooProxy>
– widok tylko do odczytu, który zawiera listę bieżących elementów w polu powtarzanymfun DslList<String, FooProxy>.add(value: String)
, funkcja rozszerzenia umożliwiająca dodawanie elementów do pola powtarzanegooperator fun DslList<String, FooProxy>.plusAssign(value: String)
, alias domenyadd
fun DslList<String, FooProxy>.addAll(values: Iterable<String>)
, funkcja rozszerzenia umożliwiająca dodanieIterable
elementów do pola powtarzanegooperator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>)
, alias domenyaddAll
operator fun DslList<String, FooProxy>.set(index: Int, value: String)
, funkcja rozszerzenia ustawiająca wartość elementu w danym indeksie o zerowej wartościfun DslList<String, FooProxy>.clear()
, funkcja rozszerzenia usuwająca zawartość powtarzanego pola
Ta nietypowa konstrukcja pozwala usłudze fooList
na zachowanie listy zmiennych w ramach DSL, która obsługuje tylko metody obsługiwane przez konstruktora cyfrowego, jednocześnie uniemożliwiając zmianę znaczenia elementu DSL, co może wprowadzać w błąd.
W przypadku innych prostych typów pól wybierany jest odpowiedni typ Java zgodnie z tabelą typów wartości skalarnych. W przypadku wiadomości i wycen wyliczenia typ to enum lub enum.
Pola uniwersalne
Dla tej definicji pola jednorazowego:
oneof oneof_name { int32 foo = 1; ... }
Kompilator wygeneruje te metody dostępu w DSL:
val oneofNameCase: OneofNameCase
: określa, które polaoneof_name
są ustawione (jeśli zostały ustawione); typ zwrotu znajdziesz w dokumentacji kodu Java.fun hasFoo(): Boolean
(tylko proto2): zwracatrue
, jeśli jeden z przypadków toFOO
.val foo: Int
: zwraca bieżącą wartośćoneof_name
, jeśli jeden z przypadków toFOO
. W przeciwnym razie zwraca wartość domyślną tego pola.
W przypadku innych prostych typów pól odpowiedni typ Java jest wybierany zgodnie z tabelą typów wartości skalarnych. W przypadku typów wiadomości i wyliczeń typ wartości jest zastępowany komunikatem lub klasą wyliczenia.
Pola mapy
W przypadku definicji pola mapy:
map<int32, int32> weight = 1;
Kompilator wygeneruje te elementy w klasie DSL:
class WeightProxy private constructor(): DslProxy()
– typ niezrozumiały, który jest używany tylko w ogólnych ustawieniach.val weight: DslMap<Int, Int, WeightProxy>
– widok tylko do odczytu bieżących wpisów w polu mapy,fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
: dodaj wpis do tego pola mapyoperator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
: alias dla domenyput
przy użyciu składni operatorafun DslMap<Int, Int, WeightProxy>.remove(key: Int)
: usuwa wpis powiązany z elementemkey
, jeśli występujefun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>)
: dodaje do tego pola mapy wszystkie wpisy z określonej mapy, zastępując wcześniejsze wartości istniejących kluczy.fun DslMap<Int, Int, WeightProxy>.clear()
: usuwa wszystkie wpisy z tego pola mapy
Rozszerzenia (tylko proto2)
Widoczna wiadomość z zakresem rozszerzenia:
message Foo { extensions 100 to 199; }
Kompilator bufora protokołu dodaje te metody do metody FooKt.Dsl
:
operator fun <T> get(extension: ExtensionLite<Foo, T>): T
: pobiera bieżącą wartość pola rozszerzenia w DLPoperator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>
: pobiera bieżącą wartość pola powtarzanego rozszerzenia z DSL jako plik tylko do odczytuList
operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>)
: ustawia bieżącą wartość pola rozszerzenia w DLP (w przypadku typów pólComparable
)operator fun <T : MessageLite<T>> set(extension: ExtensionLite<Foo, T>)
: ustawia bieżącą wartość pola rozszerzenia w DLP (w przypadku typów pól wiadomości)operator fun set(extension: ExtensionLite<Foo, ByteString>)
: ustawia bieżącą wartość pola rozszerzenia w DLP (w polachbytes
)operator fun contains(extension: ExtensionLite<Foo, *>): Boolean
: zwraca wartość „prawda”, jeśli pole rozszerzenia ma wartośćfun clear(extension: ExtensionLite<Foo, *>)
: czyści pole rozszerzeniafun <E> ExtensionList<Foo, E>.add(value: E)
: dodaje wartość do pola powtarzanego rozszerzenia.operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E)
: alias dla domenyadd
przy użyciu składni operatoraoperator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>)
: dodaje wiele wartości do pola powtarzanego rozszerzeniaoperator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>)
: alias dla domenyaddAll
przy użyciu składni operatoraoperator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E)
: ustawia element pola powtarzanego rozszerzenia w podanym indeksieoperator fun ExtensionList<Foo, *>.clear()
: czyści elementy powtarzanego pola rozszerzenia
Ogólne są złożone, ale efekt jest taki, że this[extension] = value
działa w przypadku każdego typu rozszerzenia z wyjątkiem rozszerzeń powtarzających się, a rozszerzenia te mają składnię na liście „naturalnej”, która działa podobnie jak pola bez rozszerzeń.
Biorąc pod uwagę definicję rozszerzenia:
extend Foo { optional int32 bar = 123; }
Java generuje „identyfikator rozszerzenia” bar
, który jest używany do powyższego działania rozszerzenia.