Kotlin 生成コードガイド

指定されたプロトコル定義に対してプロトコルバッファコンパイラが生成する Kotlin コード(Java 用に生成されるコードに加えて)を正確に説明します。

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 という名前のフィールドのクリアラーは clearIn_() です。

単数フィールド

このフィールド定義について:

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 コード生成を追加するのを待つ必要がないことを意味します。

繰り返しフィールド

このフィールド定義について:

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), 指定されたゼロベースのインデックスの要素の値を設定する拡張関数
  • 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: oneof ケースが FOO の場合、true を返します。
  • val foo: Int: oneof ケースが FOO の場合、oneof_name の現在の値を返します。それ以外の場合は、このフィールドのデフォルト値を返します。

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

マップフィールド

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

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 {
  int32 bar = 123;
}

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