Kotlin生成コードガイド

Java用に生成されるコードに加えて、protocol bufferコンパイラが任意のプロトコル定義に対して生成するKotlinコードについて正確に説明します。

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

コンパイラの呼び出し

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

メッセージ

単純なメッセージ宣言を考えます。

message FooBar {}

protocol bufferコンパイラは、生成された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

フィールド

前のセクションで説明したメソッドに加えて、protocol bufferコンパイラは、.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>、repeatedフィールド内の現在の要素のリストの読み取り専用ビュー
  • fun DslList<String, FooProxy>.add(value: String)、repeatedフィールドに要素を追加できる拡張関数
  • operator fun DslList<String, FooProxy>.plusAssign(value: String)addのエイリアス
  • fun DslList<String, FooProxy>.addAll(values: Iterable<String>)、repeatedフィールドに要素の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()、repeatedフィールドの内容をクリアする拡張関数

この珍しい構造により、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>、mapフィールド内の現在のエントリの読み取り専用ビュー
  • fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): このmapフィールドにエントリを追加します
  • 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>): 指定されたマップからすべてのエントリをこのmapフィールドに追加し、既に存在するキーの以前の値を上書きします
  • fun DslMap<Int, Int, WeightProxy>.clear(): このmapフィールドからすべてのエントリをクリアします

拡張

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

message Foo {
  extensions 100 to 199;
}

protocol bufferコンパイラは、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内のrepeated拡張フィールドの現在の値を読み取り専用の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): repeated拡張フィールドに値を追加します
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E): 演算子構文を使用したaddのエイリアス
  • operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): repeated拡張フィールドに複数の値を追加します
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): 演算子構文を使用したaddAllのエイリアス
  • operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): 指定されたインデックスのrepeated拡張フィールドの要素を設定します
  • inline fun ExtensionList<Foo, *>.clear(): repeated拡張フィールドの要素をクリアします

ここでのジェネリクスは複雑ですが、その効果として、this[extension] = valueはrepeated拡張を除くすべての拡張型で機能し、repeated拡張は拡張でないrepeatedフィールドと同様に機能する「自然な」リスト構文を持ちます。

拡張定義が与えられた場合:

extend Foo {
  int32 bar = 123;
}

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