All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.com.squareup.wire.KotlinConstructorBuilder.kt Maven / Gradle / Ivy

/*
 * 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

import com.squareup.wire.WireField.Label.REPEATED

internal class KotlinConstructorBuilder, B : Message.Builder>(
  private val messageType: Class,
) : Message.Builder() {
  private val fieldValueMap: MutableMap>
  private val repeatedFieldValueMap: MutableMap>>

  init {
    val fieldCount = messageType.declaredFields.size
    fieldValueMap = LinkedHashMap(fieldCount)
    repeatedFieldValueMap = LinkedHashMap(fieldCount)
  }

  fun set(
    field: WireField,
    value: Any?
  ) {
    if (field.label.isRepeated) {
      repeatedFieldValueMap[field.tag] = field to (value as MutableList<*>)
    } else {
      fieldValueMap[field.tag] = field to value
      if (value != null && field.label.isOneOf) {
        clobberOtherIsOneOfs(field)
      }
    }
  }

  private fun clobberOtherIsOneOfs(field: WireField) {
    fieldValueMap.values
        .map { it.first }
        .filter { it.oneofName == field.oneofName && it.tag != field.tag }
        .forEach {
          fieldValueMap.remove(it.tag)
        }
  }

  fun get(field: WireField): Any? {
    return if (field.label.isRepeated) {
      repeatedFieldValueMap[field.tag]?.second ?: emptyList()
    } else {
      fieldValueMap[field.tag]?.second
    }
  }

  @Suppress("UNCHECKED_CAST")
  override fun build(): M {
    val protoFields = messageType.declaredProtoFields()

    // KotlinGenerator emits different kinds of properties for various proto fields depending on
    // their type:
    //
    // - For certain types (group A), a primary constructor property is created
    // - For other types (group B), a primary constructor parameter and a simple property are
    //   created
    //
    // This leads to a mismatch between the order in which proto fields annotated with WireField
    // appear in generated code (which matches the order in which they appear in the proto file) and
    // the order of matching constructor parameters (https://github.com/square/wire/issues/2153).
    // Therefore, we'll partition fields from groups A and B separately, where fields in each group
    // will appear in the right order. This will allow us to iterate over the constructor parameters
    // and retrieve the matching proto field based on the parameter type.
    val fieldsWithSimpleProperties = ArrayDeque()
    val fieldsWithPrimaryConstructorProperties = ArrayDeque()
    for (protoField in protoFields) {
      // TODO(egor): Repeated fields aren't the only ones for which no primary constructor property
      // is generated. Need to extend this solution to cover other types.
      if (protoField.wireField.label == REPEATED) {
        fieldsWithSimpleProperties += protoField
      } else {
        fieldsWithPrimaryConstructorProperties += protoField
      }
    }

    // We'll assume there's only one constructor with this number of parameters.
    val constructor = messageType.constructors.first {
      it.parameterCount == protoFields.size + 1 // +1 for the unknown_fields.
    }
    val args = constructor.parameters.mapIndexed { index, param ->
      when {
        param.type == List::class.java -> get(fieldsWithSimpleProperties.removeFirst().wireField)
        index == protoFields.size -> buildUnknownFields()
        else -> get(fieldsWithPrimaryConstructorProperties.removeFirst().wireField)
      }
    }
    return constructor.newInstance(*args.toTypedArray()) as M
  }

  private fun Class.declaredProtoFields(): List = declaredFields
      .mapNotNull { field ->
        val wireField = field.declaredAnnotations.filterIsInstance(WireField::class.java)
            .firstOrNull()
        return@mapNotNull wireField?.let { ProtoField(field.type, wireField) }
      }

  private class ProtoField(
    val type: Class<*>,
    val wireField: WireField,
  )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy