Kotlin で生成されたコードのガイド

プロトコル定義が与えられた場合に、プロトコル バッファー コンパイラーが Java 用に生成するコードに加えて、正確にどのような Kotlin コードを生成するかを説明します。

proto2 と proto3 で生成されるコードの相違点はすべて強調表示されています。これらの相違点は、このドキュメントで説明されている生成されたコードにあり、両方のバージョンで同じであるベース メッセージ クラス/インターフェイスにはないことに注意してください。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読む必要があります。

コンパイラの起動

プロトコル バッファー コンパイラーは、Java コードの上に構築される Kotlin コードを生成します。その結果、--java_out= および --kotlin_out= の 2 つのコマンドライン フラグを指定して呼び出す必要があります。--java_out= オプションのパラメーターは、コンパイラーが Java 出力を書き込むディレクトリであり、--kotlin_out= も同様です。各 .proto ファイル入力に対して、コンパイラーは .proto ファイル自体を表す Java クラスを含むラッパー .java ファイルを作成します。

.proto ファイルに次のような行が含まれているかどうかは関係なく

option java_multiple_files = true;

コンパイラーは、.proto ファイルで宣言されたトップレベル メッセージごとに生成するクラスとファクトリ メソッドごとに、個別の .kt ファイルを作成します。

各ファイルの Java パッケージ名は、Java で生成されたコードのリファレンスで説明されているように、生成された Java コードで使用されるものと同じです。

出力ファイルは、--kotlin_out= へのパラメーター、パッケージ名 (ピリオド [.] をスラッシュ [/] に置き換えたもの)、およびサフィックス Kt.kt ファイル名を連結することによって選択されます。

たとえば、次のようにコンパイラーを呼び出すとします。

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

foo.proto の Java パッケージが com.example であり、Bar という名前のメッセージが含まれている場合、プロトコル バッファー コンパイラーはファイル build/gen/kotlin/com/example/BarKt.kt を生成します。プロトコル バッファー コンパイラーは、必要に応じて build/gen/kotlin/com および build/gen/kotlin/com/example ディレクトリを自動的に作成します。ただし、build/gen/kotlinbuild/gen、または build は作成しません。これらはすでに存在している必要があります。単一の呼び出しで複数の .proto ファイルを指定できます。すべての出力ファイルは一度に生成されます。

メッセージ

単純なメッセージ宣言が与えられた場合

message FooBar {}

プロトコル バッファー コンパイラーは、生成された Java コードに加えて、FooBarKt という名前のオブジェクトと、次の構造を持つ 2 つのトップレベル関数を生成します。

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

ネストされた型

メッセージは別のメッセージ内で宣言できます。例:

message Foo {
  message Bar { }
}

この場合、コンパイラーは BarKt オブジェクトと bar ファクトリ メソッドを FooKt 内にネストしますが、copy メソッドはトップレベルのままです。

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

フィールド

前のセクションで説明したメソッドに加えて、プロトコル バッファー コンパイラーは、.proto ファイル内のメッセージ内で定義された各フィールドの DSL に可変プロパティを生成します。(Kotlin は、Java によって生成されたゲッターからメッセージ オブジェクトの読み取り専用プロパティをすでに推論しています。)

プロパティは、.proto ファイルのフィールド名がアンダースコア付きの小文字を使用している場合でも (推奨されているように)、常にキャメルケースの名前を使用することに注意してください。ケース変換は次のように機能します。

  1. 名前の各アンダースコアに対して、アンダースコアが削除され、後続の文字が大文字になります。
  2. 名前の先頭にプレフィックス (たとえば、「clear」) が付加される場合、最初の文字は大文字になります。それ以外の場合は、小文字になります。

したがって、フィールド foo_bar_bazfooBarBaz になります。

フィールド名が Kotlin の予約語または protobuf ライブラリで既に定義されているメソッドと競合するいくつかの特別なケースでは、追加のアンダースコアが付加されます。たとえば、in という名前のフィールドの clearer は clearIn_() です。

単数フィールド (proto2)

これらのフィールド定義のいずれかについて

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

コンパイラーは、DSL で次のアクセサーを生成します。

  • fun hasFoo(): Boolean: フィールドが設定されている場合は true を返します。
  • var foo: Int: フィールドの現在の値。フィールドが設定されていない場合は、デフォルト値を返します。
  • fun clearFoo(): フィールドの値をクリアします。これを呼び出した後、hasFoo()false を返し、getFoo() はデフォルト値を返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。メッセージ型は Java で定義されているため、メッセージ内の符号なし型は、Java および古いバージョンの Kotlin との互換性のために、DSL で標準の対応する符号付き型を使用して表されます。

埋め込みメッセージ フィールド

サブメッセージの特別な処理はないことに注意してください。たとえば、フィールドがある場合

optional Foo my_foo = 1;

次のように記述する必要があります。

myFoo = foo {
  ...
}

一般に、これは、コンパイラーが Foo に Kotlin DSL があるかどうか、またはたとえば Java API のみが生成されているかどうかを認識しないためです。これは、依存するメッセージが Kotlin コード生成を追加するのを待つ必要がないことを意味します。

単数フィールド (proto3)

このフィールド定義の場合

int32 foo = 1;

コンパイラーは、DSL で次のプロパティを生成します。

  • var foo: Int: フィールドの現在の値を返します。フィールドが設定されていない場合は、フィールドの型のデフォルト値を返します。
  • fun clearFoo(): フィールドの値をクリアします。これを呼び出した後、getFoo() はフィールドの型のデフォルト値を返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。メッセージ型は Java で定義されているため、メッセージ内の符号なし型は、Java および古いバージョンの Kotlin との互換性のために、DSL で標準の対応する符号付き型を使用して表されます。

埋め込みメッセージ フィールド

メッセージ フィールド型の場合、追加のアクセサー メソッドが DSL で生成されます。

  • boolean hasFoo(): フィールドが設定されている場合は true を返します。

DSL に基づいてサブメッセージを設定するためのショートカットはないことに注意してください。たとえば、フィールドがある場合

Foo my_foo = 1;

次のように記述する必要があります。

myFoo = foo {
  ...
}

一般に、これは、コンパイラーが Foo に Kotlin DSL があるかどうか、またはたとえば Java API のみが生成されているかどうかを認識しないためです。これは、依存するメッセージが Kotlin コード生成を追加するのを待つ必要がないことを意味します。

繰り返しフィールド

このフィールド定義の場合

repeated string foo = 1;

コンパイラーは、DSL で次のメンバーを生成します。

  • class FooProxy: DslProxy: ジェネリクスでのみ使用される構築不可能な型
  • val fooList: DslList<String, FooProxy>: 繰り返しフィールド内の現在の要素のリストの読み取り専用ビュー
  • fun DslList<String, FooProxy>.add(value: String): 要素を繰り返しフィールドに追加できるようにする拡張関数
  • operator fun DslList<String, FooProxy>.plusAssign(value: String): add のエイリアス
  • fun DslList<String, FooProxy>.addAll(values: Iterable<String>): 要素の Iterable を繰り返しフィールドに追加できるようにする拡張関数
  • operator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>): addAll のエイリアス
  • operator fun DslList<String, FooProxy>.set(index: Int, value: String): 指定された 0 ベースのインデックスにある要素の値を設定する拡張関数
  • fun DslList<String, FooProxy>.clear(): 繰り返しフィールドの内容をクリアする拡張関数

この珍しい構造により、fooList は DSL のスコープ内で「可変リストのように動作」し、基になるビルダーでサポートされているメソッドのみをサポートしますが、DSL から「エスケープ」する可変性を防ぎ、混乱を招く可能性のある副作用を引き起こす可能性があります。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、型はメッセージまたは enum クラスです。

Oneof フィールド

この oneof フィールド定義の場合

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

コンパイラーは、DSL で次のアクセサー メソッドを生成します。

  • val oneofNameCase: OneofNameCase: oneof_name フィールドのどれが設定されているか (または設定されていないか) を取得します。戻り値の型については、Java コード リファレンスを参照してください。
  • fun hasFoo(): Boolean (proto2 のみ): oneof ケースが FOO の場合は true を返します。
  • val foo: Int: oneof ケースが FOO の場合、oneof_name の現在の値を返します。それ以外の場合は、このフィールドのデフォルト値を返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。

マップ フィールド

このマップ フィールド定義の場合

map<int32, int32> weight = 1;

コンパイラーは、DSL クラスで次のメンバーを生成します。

  • class WeightProxy private constructor(): DslProxy(): ジェネリクスでのみ使用される構築不可能な型
  • val weight: DslMap<Int, Int, WeightProxy>: マップ フィールド内の現在のエントリの読み取り専用ビュー
  • fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): エントリをこのマップ フィールドに追加します
  • operator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): 演算子構文を使用した put のエイリアス
  • fun DslMap<Int, Int, WeightProxy>.remove(key: Int): key に関連付けられたエントリが存在する場合は削除します
  • fun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>): 指定されたマップのすべてのエントリをこのマップ フィールドに追加し、既に存在するキーの以前の値を上書きします
  • fun DslMap<Int, Int, WeightProxy>.clear(): このマップ フィールドからすべてのエントリをクリアします

拡張機能 (proto2 のみ)

拡張範囲を持つメッセージが与えられた場合

message Foo {
  extensions 100 to 199;
}

プロトコル バッファー コンパイラーは、次のメソッドを FooKt.Dsl に追加します。

  • operator fun <T> get(extension: ExtensionLite<Foo, T>): T: DSL の拡張フィールドの現在の値を取得します
  • operator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>: DSL の繰り返し拡張フィールドの現在の値を読み取り専用の List として取得します
  • operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>): DSL の拡張フィールドの現在の値を設定します (Comparable フィールド型の場合)
  • operator fun <T : MessageLite> set(extension: ExtensionLite<Foo, T>): DSL の拡張フィールドの現在の値を設定します (メッセージ フィールド型の場合)
  • operator fun set(extension: ExtensionLite<Foo, ByteString>): DSL の拡張フィールドの現在の値を設定します (bytes フィールドの場合)
  • operator fun contains(extension: ExtensionLite<Foo, *>): Boolean: 拡張フィールドに値がある場合は true を返します
  • fun clear(extension: ExtensionLite<Foo, *>): 拡張フィールドをクリアします
  • fun <E> ExtensionList<Foo, E>.add(value: E): 値を繰り返し拡張フィールドに追加します
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E): 演算子構文を使用した add のエイリアス
  • operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): 複数の値を繰り返し拡張フィールドに追加します
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): 演算子構文を使用した addAll のエイリアス
  • operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): 指定されたインデックスにある繰り返し拡張フィールドの要素を設定します
  • inline fun ExtensionList<Foo, *>.clear(): 繰り返し拡張フィールドの要素をクリアします

ここでのジェネリクスは複雑ですが、効果は、this[extension] = value が繰り返し拡張機能を除くすべての拡張機能タイプで機能し、繰り返し拡張機能には非拡張繰り返しフィールドと同様に機能する「自然な」リスト構文があることです。

拡張機能の定義が与えられた場合

extend Foo {
  optional int32 bar = 123;
}

Java は、「拡張機能識別子」bar を生成します。これは、上記の拡張機能操作の「キー」として使用されます。