OBSERVAÇÃO:este site foi descontinuado. O site será desativado após 31 de janeiro de 2023, e o tráfego será redirecionado para o novo site em https://protobuf.dev. Enquanto isso, as atualizações serão feitas apenas para protobuf.dev.

Código gerado pelo Kotlin

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Esta página descreve exatamente o código Kotlin que o compilador de buffer de protocolo gera para qualquer definição de protocolo, além do código gerado para Java. Qualquer diferença entre o código gerado por proto2 e proto3 está destacada. Observe que essas diferenças estão no código gerado conforme descrito neste documento, não nas classes/interfaces de mensagem base, que são as mesmas nas duas versões. Leia o guia de idiomas do proto2 e/ou o guia do idioma do proto3 antes de ler este documento.

Invocação do compilador

O compilador de buffer de protocolo produz código Kotlin que se baseia no código Java. Por isso, ele precisa ser invocado com duas sinalizações de linha de comando, --java_out= e --kotlin_out=. O parâmetro para a opção --java_out= é o diretório em que você quer que o compilador grave sua saída Java, e o mesmo para --kotlin_out=. Para cada entrada de arquivo .proto, o compilador cria um arquivo .java wrapper que contém uma classe Java que representa o próprio arquivo .proto.

Independentemente de o arquivo .proto conter ou não uma linha como a seguinte:

option java_multiple_files = true;

O compilador vai criar arquivos .kt separados para cada uma das classes e métodos de fábrica que vão ser gerados para cada mensagem de nível superior declarada no arquivo .proto.

O nome do pacote Java de cada arquivo é o mesmo usado pelo código Java gerado, conforme descrito na referência de código gerado por Java.

O arquivo de saída é escolhido concatenando o parâmetro para --kotlin_out=, o nome do pacote (com .s substituídos por /s) e o nome do arquivo Kt.kt do sufixo.

Então, por exemplo, digamos que você invoque o compilador da seguinte maneira:

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

Se o pacote Java do foo.proto for com.example e ele contiver uma mensagem chamada Bar, o compilador de buffer de protocolo gerará o arquivo build/gen/kotlin/com/example/BarKt.kt. O compilador de buffer de protocolo criará automaticamente os diretórios build/gen/kotlin/com e build/gen/kotlin/com/example, se necessário. No entanto, ele não criará build/gen/kotlin, build/gen ou build. Eles já devem existir. É possível especificar vários arquivos .proto em uma única invocação. Todos os arquivos de saída serão gerados de uma só vez.

Mensagens

Se a declaração for simples:

message FooBar {}

O compilador de buffer de protocolo gera, além do código Java gerado, um objeto chamado FooBarKt, bem como duas funções de nível superior, com a seguinte estrutura:

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

Tipos aninhados

Uma mensagem pode ser declarada dentro de outra. Por exemplo: message Foo { message Bar { } }

Nesse caso, o compilador aninha o objeto BarKt e o método de fábrica bar dentro de FooKt, embora o método copy permaneça de nível superior:

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

Campos

Além dos métodos descritos na seção anterior, o compilador de buffer de protocolo gera propriedades mutáveis na DSL para cada campo definido na mensagem no arquivo .proto. O Kotlin já infere propriedades somente leitura no objeto de mensagem dos getters gerados pelo Java.

As propriedades sempre usam nomes concatenados, mesmo que o nome do campo no arquivo .proto use letras minúsculas com sublinhados (como ele deve ser usado). A conversão de caso funciona da seguinte maneira:

  1. Para cada sublinhado no nome, o sublinhado é removido e a letra a seguir é maiúscula.
  2. Se o nome tiver um prefixo anexado (por exemplo, "clear"), a primeira letra está em maiúscula. Caso contrário, ela está em letras minúsculas.

Assim, o campo foo_bar_baz se torna fooBarBaz.

Em alguns casos especiais, em que um nome de campo entra em conflito com palavras reservadas no Kotlin ou em métodos já definidos na biblioteca protobuf, um sublinhado extra é anexado. Por exemplo, a liberação de um campo chamado in é clearIn_().

Campos Singular (proto2)

Para qualquer uma dessas definições de campo:

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

O compilador gerará os seguintes acessadores na DSL:

  • fun hasFoo(): Boolean: retorna true se o campo estiver definido.
  • var foo: Int: o valor atual do campo. Se o campo não for definido, retornará o valor padrão.
  • fun clearFoo(): limpa o valor do campo. Depois de chamar isso, hasFoo() retornará false, e getFoo() retornará o valor padrão.

Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração. Como o tipo de mensagem ainda é definido em Java, os tipos não assinados na mensagem são representados usando os tipos assinados padrão na DSL, para compatibilidade com Java e versões mais antigas do Kotlin.

Campos de mensagens incorporados

Não há processamento especial de submensagens. Por exemplo, se você tiver um campo

optional Foo my_foo = 1;
é preciso escrever
myFoo = foo {
  ...
}

Em geral, isso ocorre porque o compilador não sabe se Foo tem uma DSL do Kotlin ou, por exemplo, apenas as APIs Java são geradas. Isso significa que você não precisa esperar por mensagens de que depende para adicionar a geração de código Kotlin.

Também fornecemos um acesso de campo extra FooOrNull para todos os campos de mensagens opcionais que retornam o valor do campo se ele estiver presente. Caso contrário, ele será nulo.

Campos Singular (proto3)

Para esta definição de campo:

int32 foo = 1;

O compilador gerará a seguinte propriedade na DSL:

  • var foo: Int: retorna o valor atual do campo. Se o campo não estiver definido, retorna o valor padrão para o tipo de campo.
  • fun clearFoo(): limpa o valor do campo. Depois de chamar isso, getFoo() retornará o valor padrão para o tipo de campo.

Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração. Como o tipo de mensagem ainda é definido em Java, os tipos não assinados na mensagem são representados usando os tipos assinados padrão na DSL, para compatibilidade com Java e versões mais antigas do Kotlin.

Campos de mensagens incorporados

Para tipos de campo de mensagem, um método acessador adicional é gerado na DSL:

  • boolean hasFoo(): retornará true se o campo tiver sido definido.

Não há um atalho para definir uma submensagem com base em uma DSL. Por exemplo, se você tiver um campo

Foo my_foo = 1;
é preciso escrever
myFoo = foo {
  ...
}

Em geral, isso ocorre porque o compilador não sabe se Foo tem uma DSL do Kotlin ou, por exemplo, apenas as APIs Java são geradas. Isso significa que você não precisa esperar por mensagens de que depende para adicionar a geração de código Kotlin.

Campos repetidos

Para esta definição de campo:

repeated string foo = 1;

O compilador gerará os seguintes membros na DSL:

  • class FooProxy: DslProxy, um tipo construtivo usado apenas em genéricos
  • val fooList: DslList<String, FooProxy>, uma visualização somente leitura da lista de elementos atuais no campo repetido
  • fun DslList<String, FooProxy>.add(value: String), uma função de extensão que permite que os elementos sejam adicionados ao campo repetido
  • operator fun DslList<String, FooProxy>.plusAssign(value: String), um alias de add
  • fun DslList<String, FooProxy>.addAll(values: Iterable<String>), uma função de extensão que permite que um Iterable de elementos seja adicionado ao campo repetido
  • operator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>), um alias de addAll
  • operator fun DslList<String, FooProxy>.set(index: Int, value: String), uma função de extensão que define o valor do elemento no índice baseado em zero específico.
  • fun DslList<String, FooProxy>.clear(), uma função de extensão que limpa o conteúdo do campo repetido

Essa construção incomum permite que fooList "se comporte como" uma lista mutável dentro do escopo da DSL, oferecendo suporte apenas aos métodos com suporte do builder subjacente, enquanto evita que a mutabilidade "escape" a DSL, o que pode causar efeitos colaterais confusos.

Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Para mensagens e tipos de enumeração, o tipo é a mensagem ou a classe de enumeração.

Oneof Fields

Para esta definição de campo única:

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

O compilador gerará os seguintes métodos de acesso na DSL:

  • val oneofNameCase: OneofNameCase: recebe quais, se houver, os campos oneof_name estão definidos. Consulte a referência do código Java para o tipo de retorno.
  • fun hasFoo(): Boolean (somente proto2): retorna true se o um dos casos for FOO.
  • val foo: Int: retorna o valor atual de oneof_name se o caso único for FOO. Caso contrário, retorna o valor padrão deste campo.

Para outros tipos de campo simples, o tipo Java correspondente é escolhido de acordo com a tabela de tipos de valores escalares. Nos tipos de mensagem e enum, o tipo de valor é substituído pela mensagem ou pela classe de enumeração.

Campos do mapa

Para esta definição de campo do mapa:

map<int32, int32> weight = 1;

O compilador gerará os seguintes membros na classe DSL:

  • class WeightProxy private constructor(): DslProxy(), um tipo construtivo usado apenas em genéricos
  • val weight: DslMap<Int, Int, WeightProxy>, uma visualização somente leitura das entradas atuais no campo do mapa
  • fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): adicione a entrada a esse campo do mapa
  • operator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int): alias de put usando a sintaxe do operador
  • fun DslMap<Int, Int, WeightProxy>.remove(key: Int): remove a entrada associada a key, se presente.
  • fun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>): adiciona todas as entradas do mapa especificado a esse campo de mapa, substituindo os valores anteriores de chaves já presentes
  • fun DslMap<Int, Int, WeightProxy>.clear(): limpa todas as entradas desse campo do mapa.

Extensões (somente proto2)

Você recebeu uma mensagem com um intervalo de extensões:

message Foo {
  extensions 100 to 199;
}

O compilador de buffer de protocolo adicionará os seguintes métodos a FooKt.Dsl:

  • operator fun <T> get(extension: ExtensionLite<Foo, T>): T: recebe o valor atual do campo de extensão na DSL.
  • operator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>: recebe o valor atual do campo de extensão repetido na DSL como um List somente leitura.
  • operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>): define o valor atual do campo de extensão na DSL (para tipos de campo Comparable).
  • operator fun <T : MessageLite<T>> set(extension: ExtensionLite<Foo, T>): define o valor atual do campo de extensão na DSL (para tipos de campo de mensagem).
  • operator fun set(extension: ExtensionLite<Foo, ByteString>): define o valor atual do campo de extensão na DSL (para campos bytes).
  • operator fun contains(extension: ExtensionLite<Foo, *>): Boolean: retornará "true" se o campo da extensão tiver um valor.
  • fun clear(extension: ExtensionLite<Foo, *>): limpa o campo de extensão.
  • fun <E> ExtensionList<Foo, E>.add(value: E): adiciona um valor ao campo de extensão repetida.
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E): alias de add usando a sintaxe do operador
  • operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>): adiciona vários valores ao campo de extensão repetida.
  • operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>): alias de addAll usando a sintaxe do operador
  • operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E): define o elemento do campo de extensão repetido no índice especificado
  • operator fun ExtensionList<Foo, *>.clear(): limpa os elementos do campo de extensão repetida.

Os genéricos aqui são complexos, mas o efeito é que this[extension] = value funciona para todos os tipos de extensão, exceto as extensões repetidas, e as extensões repetidas têm sintaxe de lista "natural" que funciona de maneira semelhante aos campos repetidos que não são de extensão.

Com uma definição de extensão:

extend Foo {
  optional int32 bar = 123;
}

O Java gera o "identificador de extensão" bar, que é usado para "principal" operações de extensão acima.