UWAGA: witryna została wycofana. Po 31 stycznia 2023 roku witryna zostanie wyłączona, a ruch będzie kierowany do nowej witryny na https://protobuf.dev. Do tego czasu aktualizacje będą dotyczyć tylko protobuf.dev.

Kod wygenerowany przez Kotlin

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

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:

  1. W przypadku każdego podkreślenia w nazwie zostanie ono usunięte, a kolejna litera wielką literą.
  2. 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: zwraca true, 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łaniu hasFoo() zwraca wartość false, a getFoo() 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łaniu getFoo() 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(): zwraca true, 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 powtarzanym
  • fun DslList<String, FooProxy>.add(value: String), funkcja rozszerzenia umożliwiająca dodawanie elementów do pola powtarzanego
  • operator fun DslList<String, FooProxy>.plusAssign(value: String), alias domeny add
  • fun DslList<String, FooProxy>.addAll(values: Iterable<String>), funkcja rozszerzenia umożliwiająca dodanie Iterable elementów do pola powtarzanego
  • operator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>), alias domeny addAll
  • operator fun DslList<String, FooProxy>.set(index: Int, value: String), funkcja rozszerzenia ustawiająca wartość elementu w danym indeksie o zerowej wartości
  • fun 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 pola oneof_name są ustawione (jeśli zostały ustawione); typ zwrotu znajdziesz w dokumentacji kodu Java.
  • fun hasFoo(): Boolean (tylko proto2): zwraca true, jeśli jeden z przypadków to FOO.
  • val foo: Int: zwraca bieżącą wartość oneof_name, jeśli jeden z przypadków to FOO. 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 mapy
  • operator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): alias dla domeny put przy użyciu składni operatora
  • fun DslMap<Int, Int, WeightProxy>.remove(key: Int): usuwa wpis powiązany z elementem key, jeśli występuje
  • fun 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 DLP
  • operator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>: pobiera bieżącą wartość pola powtarzanego rozszerzenia z DSL jako plik tylko do odczytu List
  • operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>): ustawia bieżącą wartość pola rozszerzenia w DLP (w przypadku typów pól Comparable)
  • 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 polach bytes)
  • operator fun contains(extension: ExtensionLite<Foo, *>): Boolean: zwraca wartość „prawda”, jeśli pole rozszerzenia ma wartość
  • fun clear(extension: ExtensionLite<Foo, *>): czyści pole rozszerzenia
  • fun <E> ExtensionList<Foo, E>.add(value: E): dodaje wartość do pola powtarzanego rozszerzenia.
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E): alias dla domeny add przy użyciu składni operatora
  • operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): dodaje wiele wartości do pola powtarzanego rozszerzenia
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): alias dla domeny addAll przy użyciu składni operatora
  • operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): ustawia element pola powtarzanego rozszerzenia w podanym indeksie
  • operator 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.