Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement JSON for boxed oneofs. #1877

Merged
merged 1 commit into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package com.squareup.wire
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.squareup.wire.internal.FieldBinding
import com.squareup.wire.internal.FieldOrOneOfBinding
import com.squareup.wire.internal.RuntimeMessageAdapter
import java.io.IOException

Expand Down Expand Up @@ -79,6 +79,6 @@ internal class MessageTypeAdapter<M : Message<M, B>, B : Message.Builder<M, B>>(

data class JsonField<M : Message<M, B>, B : Message.Builder<M, B>>(
val adapter: TypeAdapter<Any?>,
val fieldBinding: FieldBinding<M, B>
val fieldBinding: FieldOrOneOfBinding<M, B>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import com.squareup.wire.WireEnum
import com.squareup.wire.WireEnumConstant
import com.squareup.wire.WireField
import com.squareup.wire.WireRpc
import com.squareup.wire.internal.boxedOneOfKeysFieldName
import com.squareup.wire.internal.camelCase
import com.squareup.wire.schema.EnclosingType
import com.squareup.wire.schema.EnumConstant
Expand Down Expand Up @@ -96,12 +97,12 @@ import com.squareup.wire.schema.internal.eligibleAsAnnotationMember
import com.squareup.wire.schema.internal.javaPackage
import com.squareup.wire.schema.internal.optionValueToInt
import com.squareup.wire.schema.internal.optionValueToLong
import java.util.Locale
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import okio.ByteString
import okio.ByteString.Companion.encode
import java.util.Locale

class KotlinGenerator private constructor(
val schema: Schema,
Expand Down Expand Up @@ -403,12 +404,15 @@ class KotlinGenerator private constructor(
}
}
message.boxOneOfs().forEach { oneOf ->
newName(oneOf.name, oneOf)
newName(oneOf.name + "Keys",oneOf.name + "Keys")
val fieldName = newName(oneOf.name, oneOf)
val keysFieldName = boxedOneOfKeysFieldName(fieldName)
check(newName(keysFieldName) == keysFieldName) {
"unexpected name collision for keys set of boxed one of, ${oneOf.name}"
}
newName(oneOf.name.capitalize(), oneOf.name.capitalize())
oneOf.fields.forEach { field ->
newName(oneOf.name + field.name.capitalize(), oneOf.name + field.name.capitalize())
}
oneOf.fields.forEach { field ->
newName(oneOf.name + field.name.capitalize(), oneOf.name + field.name.capitalize())
}
}
}
}
Expand Down Expand Up @@ -1406,7 +1410,7 @@ class KotlinGenerator private constructor(
val choiceKey = nameAllocator.newName("choiceKey")
for (boxOneOf in message.boxOneOfs()) {
val fieldName = nameAllocator[boxOneOf]
val choiceKeys = nameAllocator[boxOneOf.name + "Keys"]
val choiceKeys = boxedOneOfKeysFieldName(fieldName)
beginControlFlow("for (%L in %L)", choiceKey, choiceKeys)
beginControlFlow("if (%L == %L.tag)", tag, choiceKey)
addStatement("%L = %L.decode(reader)", fieldName, choiceKey)
Expand Down Expand Up @@ -1995,11 +1999,14 @@ class KotlinGenerator private constructor(
companionBuilder.addProperty(oneOfKey)
}

val fieldName = nameAllocator[oneOf]
val keysFieldName = boxedOneOfKeysFieldName(fieldName)
val allKeys = PropertySpec
.builder(
nameAllocator[oneOf.name + "Keys"],
keysFieldName,
Set::class.asClassName().parameterizedBy(boxClassName.parameterizedBy(STAR))
)
.addAnnotation(JvmStatic::class.java)
.initializer(
CodeBlock.of(
"""setOf(${keyFieldNames.map { "%L" }.joinToString(", ")})""",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,8 @@ fun sanitize(value: String): String {
fun sanitize(values: List<String>): String {
return values.map(::sanitize).joinToString(prefix = "[", postfix = "]")
}

/** Maps [fieldName] to the companion object field of type `Set` containing the eligible keys. */
fun boxedOneOfKeysFieldName(fieldName: String): String {
return "${fieldName}Keys"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.squareup.wire.internal

import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.Syntax
import com.squareup.wire.WireField
import java.lang.reflect.Field
import java.lang.reflect.Method
Expand All @@ -29,16 +30,16 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
wireField: WireField,
private val messageField: Field,
builderType: Class<B>
) {
val label: WireField.Label = wireField.label
val name: String = messageField.name
val declaredName: String =
) : FieldOrOneOfBinding<M, B> {
override val label: WireField.Label = wireField.label
override val name: String = messageField.name
override val wireFieldJsonName: String = wireField.jsonName
override val declaredName: String =
if (wireField.declaredName.isEmpty()) messageField.name else wireField.declaredName
val jsonName: String = if (wireField.jsonName.isEmpty()) declaredName else wireField.jsonName
val tag: Int = wireField.tag
override val tag: Int = wireField.tag
private val keyAdapterString = wireField.keyAdapter
private val adapterString = wireField.adapter
val redacted: Boolean = wireField.redacted
override val redacted: Boolean = wireField.redacted
private val builderField = getBuilderField(builderType, name)
private val builderMethod = getBuilderMethod(builderType, name, messageField.type)

Expand All @@ -47,7 +48,7 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
private var keyAdapter: ProtoAdapter<*>? = null
private var adapter: ProtoAdapter<Any>? = null

val isMap: Boolean
override val isMap: Boolean
get() = keyAdapterString.isNotEmpty()

private fun getBuilderField(builderType: Class<*>, name: String): Field {
Expand All @@ -66,15 +67,15 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
}
}

fun singleAdapter(): ProtoAdapter<*> {
override fun singleAdapter(): ProtoAdapter<*> {
return singleAdapter ?: ProtoAdapter.get(adapterString).also { singleAdapter = it }
}

fun keyAdapter(): ProtoAdapter<*> {
override fun keyAdapter(): ProtoAdapter<*> {
return keyAdapter ?: ProtoAdapter.get(keyAdapterString).also { keyAdapter = it }
}

internal fun adapter(): ProtoAdapter<Any> {
override fun adapter(): ProtoAdapter<Any> {
val result = adapter
if (result != null) return result
if (isMap) {
Expand All @@ -88,7 +89,7 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
}

/** Accept a single value, independent of whether this value is single or repeated. */
internal fun value(builder: B, value: Any) {
override fun value(builder: B, value: Any) {
when {
label.isRepeated -> {
when (val list = getFromBuilder(builder)) {
Expand Down Expand Up @@ -123,7 +124,7 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
}

/** Assign a single value for required/optional fields, or a list for repeated/packed fields. */
fun set(builder: B, value: Any?) {
override fun set(builder: B, value: Any?) {
if (label.isOneOf) {
// In order to maintain the 'oneof' invariant, call the builder setter method rather
// than setting the builder field directly.
Expand All @@ -133,7 +134,18 @@ class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constr
}
}

operator fun get(message: M): Any? = messageField.get(message)
override operator fun get(message: M): Any? = messageField.get(message)

internal fun getFromBuilder(builder: B): Any? = builderField.get(builder)
override fun getFromBuilder(builder: B): Any? = builderField.get(builder)

override fun omitFromJson(syntax: Syntax, value: Any?): Boolean {
return omitIdentity(syntax) && value == adapter().identity
}

private fun omitIdentity(syntax: Syntax): Boolean {
if (label == WireField.Label.OMIT_IDENTITY) return true
if (label.isRepeated && syntax == Syntax.PROTO_3) return true
if (isMap && syntax == Syntax.PROTO_3) return true
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2021 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.wire.internal

import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.Syntax
import com.squareup.wire.WireField

interface FieldOrOneOfBinding<M : Message<M, B>, B : Message.Builder<M, B>> {
val tag: Int

val label: WireField.Label

val redacted: Boolean

val isMap: Boolean

/**
* The name of the field in generated code. If the declared name is a keyword like `fun`, this
* will be a transformed name like `fun_`.
*/
val name: String

/**
* The name of the field as declared in the `.proto` file.
*/
val declaredName: String

/**
* The JSON name as determined at code-generation name. This is usually camelCase even if the
* field is declared in snake_case.
*/
val wireFieldJsonName: String

fun keyAdapter(): ProtoAdapter<*>

fun adapter(): ProtoAdapter<Any>

fun value(builder: B, value: Any)

fun set(builder: B, value: Any?)

operator fun get(message: M): Any?

fun getFromBuilder(builder: B): Any?

fun singleAdapter(): ProtoAdapter<*>

fun omitFromJson(syntax: Syntax, value: Any?): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ abstract class JsonIntegration<F, A> {
fun <M : Message<M, B>, B : Message.Builder<M, B>> jsonAdapter(
framework: F,
syntax: Syntax,
field: FieldBinding<M, B>
field: FieldOrOneOfBinding<M, B>
): A {
if (field.singleAdapter().isStruct) {
return structAdapter(framework)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2021 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.wire.internal

import com.squareup.wire.Message
import com.squareup.wire.OneOf
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.Syntax
import com.squareup.wire.WireField
import java.lang.reflect.Field

internal class OneOfBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constructor(
private val messageField: Field,
builderType: Class<B>,
private val key: OneOf.Key<*>
) : FieldOrOneOfBinding<M, B> {
private val builderField: Field = builderType.getDeclaredField(messageField.name)

override val tag: Int
get() = key.tag

override val label: WireField.Label
get() = WireField.Label.OPTIONAL

override val redacted: Boolean
get() = key.redacted

override val wireFieldJsonName: String
get() = key.jsonName

override val name: String
get() = key.declaredName

override val declaredName: String
get() = key.declaredName

override val isMap: Boolean
get() = false

override fun keyAdapter(): ProtoAdapter<*> {
error("not a map")
}

override fun adapter(): ProtoAdapter<Any> {
return key.adapter as ProtoAdapter<Any>
}

override fun value(builder: B, value: Any) {
set(builder, value)
}

override fun set(builder: B, value: Any?) {
builderField.set(builder, OneOf(key as OneOf.Key<Any>, value!!))
}

override fun get(message: M): Any? {
val oneOfOrNull = messageField.get(message) as OneOf<*, *>?
return oneOfOrNull?.getOrNull(key)
}

override fun getFromBuilder(builder: B): Any? {
val oneOfOrNull = builderField.get(builder) as OneOf<*, *>?
return oneOfOrNull?.getOrNull(key)
}

override fun singleAdapter(): ProtoAdapter<*> {
return adapter()
}

override fun omitFromJson(syntax: Syntax, value: Any?): Boolean {
return value == null
}
}
Loading