diff --git a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
index 555638b..b33a5ce 100644
--- a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
+++ b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
@@ -54,35 +54,36 @@ private val ABSENT_VALUE = Any()
  * constructor, and then by setting any additional properties that exist, if any.
  */
 internal class KotlinJsonAdapter<T>(
-  private val constructor: KFunction<T>,
-  private val bindings: List<Binding<T, Any?>?>,
-  private val options: JsonReader.Options
+  val constructor: KFunction<T>,
+  val allBindings: List<Binding<T, Any?>?>,
+  val nonTransientBindings: List<Binding<T, Any?>>,
+  val options: JsonReader.Options
 ) : JsonAdapter<T>() {

   override fun fromJson(reader: JsonReader): T {
     val constructorSize = constructor.parameters.size

     // Read each value into its slot in the array.
-    val values = Array<Any?>(bindings.size) { ABSENT_VALUE }
+    val values = Array<Any?>(allBindings.size) { ABSENT_VALUE }
     reader.beginObject()
     while (reader.hasNext()) {
       val index = reader.selectName(options)
-      val binding = if (index != -1) bindings[index] else null
-
-      if (binding == null) {
+      if (index == -1) {
         reader.skipName()
         reader.skipValue()
         continue
       }
+      val binding = nonTransientBindings[index]

-      if (values[index] !== ABSENT_VALUE) {
+      val propertyIndex = binding.propertyIndex
+      if (values[propertyIndex] !== ABSENT_VALUE) {
         throw JsonDataException(
             "Multiple values for '${binding.property.name}' at ${reader.path}")
       }

-      values[index] = binding.adapter.fromJson(reader)
+      values[propertyIndex] = binding.adapter.fromJson(reader)

-      if (values[index] == null && !binding.property.returnType.isMarkedNullable) {
+      if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
         throw Util.unexpectedNull(
             binding.property.name,
             binding.jsonName,
@@ -98,7 +99,7 @@ internal class KotlinJsonAdapter<T>(
         if (!constructor.parameters[i].type.isMarkedNullable) {
           throw Util.missingProperty(
               constructor.parameters[i].name,
-              bindings[i]?.jsonName,
+              allBindings[i]?.jsonName,
               reader
           )
         }
@@ -110,8 +111,8 @@ internal class KotlinJsonAdapter<T>(
     val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))

     // Set remaining properties.
-    for (i in constructorSize until bindings.size) {
-      val binding = bindings[i]!!
+    for (i in constructorSize until allBindings.size) {
+      val binding = allBindings[i]!!
       val value = values[i]
       binding.set(result, value)
     }
@@ -123,7 +124,7 @@ internal class KotlinJsonAdapter<T>(
     if (value == null) throw NullPointerException("value == null")

     writer.beginObject()
-    for (binding in bindings) {
+    for (binding in allBindings) {
       if (binding == null) continue // Skip constructor parameters that aren't properties.

       writer.name(binding.name)
@@ -135,11 +136,13 @@ internal class KotlinJsonAdapter<T>(
   override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"

   data class Binding<K, P>(
-      val name: String,
-      val jsonName: String?,
-      val adapter: JsonAdapter<P>,
-      val property: KProperty1<K, P>,
-      val parameter: KParameter?) {
+    val name: String,
+    val jsonName: String?,
+    val adapter: JsonAdapter<P>,
+    val property: KProperty1<K, P>,
+    val parameter: KParameter?,
+    val propertyIndex: Int
+  ) {
     fun get(value: K) = property.get(value)

     fun set(result: K, value: P) {
@@ -257,7 +260,8 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
           jsonAnnotation?.name ?: name,
           adapter,
           property as KProperty1<Any, Any?>,
-          parameter
+          parameter,
+          parameter?.index ?: -1
       )
     }

@@ -271,9 +275,13 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
       bindings += binding
     }

-    bindings += bindingsByName.values
+    var index = bindings.size
+    for (bindingByName in bindingsByName) {
+      bindings += bindingByName.value.copy(propertyIndex = index++)
+    }

-    val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\\u0000" }.toTypedArray())
-    return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
+    val nonTransientBindings = bindings.filterNotNull()
+    val options = JsonReader.Options.of(*nonTransientBindings.map { it.name }.toTypedArray())
+    return KotlinJsonAdapter(constructor, bindings, nonTransientBindings, options).nullSafe()
   }
 }
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
index 9466831..a583841 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt
@@ -535,6 +535,22 @@ class GeneratedAdaptersTest {
   @JsonClass(generateAdapter = true)
   class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)

+  @Test fun multipleTransientConstructorParameters() {
+    val moshi = Moshi.Builder().build()
+    val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
+
+    val encoded = MultipleTransientConstructorParameters(3, 5, 7)
+    assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
+
+    val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
+    assertThat(decoded.a).isEqualTo(-1)
+    assertThat(decoded.b).isEqualTo(6)
+    assertThat(decoded.c).isEqualTo(-1)
+  }
+
+  @JsonClass(generateAdapter = true)
+  class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
+
   @Test fun transientProperty() {
     val moshi = Moshi.Builder().build()
     val jsonAdapter = moshi.adapter<TransientProperty>()
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
index 0cb9dc2..739bad4 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt
@@ -294,6 +294,21 @@ class KotlinJsonAdapterTest {

   class TransientConstructorParameter(@Transient var a: Int = -1, var b: Int = -1)

+  @Test fun multipleTransientConstructorParameters() {
+    val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
+    val jsonAdapter = moshi.adapter(MultipleTransientConstructorParameters::class.java)
+
+    val encoded = MultipleTransientConstructorParameters(3, 5, 7)
+    assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
+
+    val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
+    assertThat(decoded.a).isEqualTo(-1)
+    assertThat(decoded.b).isEqualTo(6)
+    assertThat(decoded.c).isEqualTo(-1)
+  }
+
+  class MultipleTransientConstructorParameters(@Transient var a: Int = -1, var b: Int = -1, @Transient var c: Int = -1)
+
   @Test fun requiredTransientConstructorParameterFails() {
     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
     try {

Kotlin multiple @Transient fields
Having more than one @ Transient field is causing following exception.

```
Exception in thread "main" java.lang.IllegalArgumentException: duplicate option: [text=\\\\u0000"]
	at okio.Options.of(Options.java:66)
	at com.squareup.moshi.JsonReader$Options.of(JsonReader.java:538)
	at com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory.create(KotlinJsonAdapter.kt:260)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:137)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:97)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:71)

```

Code to reproduce

```
data class TransientTest(
        val a: String,
        @Transient
        val b: String? = "b",
        @Transient
        val c: String? = "c"
)

fun main(args: Array<String>) {

    val adapter = Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
            .adapter(TransientTest::class.java)

    val test = TransientTest("a")

    adapter.toJson(test)
}
```

/*
 * Copyright (C) 2017 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.moshi

import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory

@Deprecated(
        message = "this moved to avoid a package name conflict in the Java Platform Module System.",
        replaceWith = ReplaceWith("com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory")
)
class KotlinJsonAdapterFactory
    : JsonAdapter.Factory by KotlinJsonAdapterFactory()

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen

import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.util.Types

/**
 * A concrete type like `List<String>` with enough information to know how to resolve its type
 * variables.
 */
internal class AppliedType private constructor(
  val element: TypeElement,
  private val mirror: DeclaredType
) {
  /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */
  fun supertypes(
    types: Types,
    result: MutableSet<AppliedType> = mutableSetOf()
  ): Set<AppliedType> {
    result.add(this)
    for (supertype in types.directSupertypes(mirror)) {
      val supertypeDeclaredType = supertype as DeclaredType
      val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
      val appliedSupertype = AppliedType(supertypeElement, supertypeDeclaredType)
      appliedSupertype.supertypes(types, result)
    }
    return result
  }

  override fun toString() = mirror.toString()

  companion object {
    fun get(typeElement: TypeElement): AppliedType {
      return AppliedType(typeElement, typeElement.asType() as DeclaredType)
    }
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen

import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.moshi.JsonClass
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.Messager
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic

/**
 * An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
 * This generates Kotlin code, and understands basic Kotlin language features like default values
 * and companion objects.
 *
 * The generated class will match the visibility of the given data class (i.e. if it's internal, the
 * adapter will also be internal).
 */
@KotlinPoetMetadataPreview
@AutoService(Processor::class)
@IncrementalAnnotationProcessor(ISOLATING)
class JsonClassCodegenProcessor : AbstractProcessor() {

  companion object {
    /**
     * This annotation processing argument can be specified to have a `@Generated` annotation
     * included in the generated code. It is not encouraged unless you need it for static analysis
     * reasons and not enabled by default.
     *
     * Note that this can only be one of the following values:
     *   * `"javax.annotation.processing.Generated"` (JRE 9+)
     *   * `"javax.annotation.Generated"` (JRE <9)
     */
    const val OPTION_GENERATED = "moshi.generated"
    private val POSSIBLE_GENERATED_NAMES = setOf(
        "javax.annotation.processing.Generated",
        "javax.annotation.Generated"
    )
  }

  private lateinit var types: Types
  private lateinit var elements: Elements
  private lateinit var filer: Filer
  private lateinit var messager: Messager
  private val annotation = JsonClass::class.java
  private var generatedType: TypeElement? = null

  override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName)

  override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()

  override fun getSupportedOptions() = setOf(OPTION_GENERATED)

  override fun init(processingEnv: ProcessingEnvironment) {
    super.init(processingEnv)
    generatedType = processingEnv.options[OPTION_GENERATED]?.let {
      require(it in POSSIBLE_GENERATED_NAMES) {
        "Invalid option value for $OPTION_GENERATED. Found $it, " +
            "allowable values are $POSSIBLE_GENERATED_NAMES."
      }
      processingEnv.elementUtils.getTypeElement(it)
    }
    this.types = processingEnv.typeUtils
    this.elements = processingEnv.elementUtils
    this.filer = processingEnv.filer
    this.messager = processingEnv.messager
  }

  override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
    for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
      if (type !is TypeElement) {
        messager.printMessage(
            Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $type: must be a Kotlin class",
            type)
        continue
      }
      val jsonClass = type.getAnnotation(annotation)
      if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
        val generator = adapterGenerator(type) ?: continue
        generator
            .generateFile {
              it.toBuilder()
                  .apply {
                    generatedType?.asClassName()?.let { generatedClassName ->
                      addAnnotation(
                          AnnotationSpec.builder(generatedClassName)
                              .addMember("value = [%S]",
                                  JsonClassCodegenProcessor::class.java.canonicalName)
                              .addMember("comments = %S", "https://github.com/square/moshi")
                              .build()
                      )
                    }
                  }
                  .addOriginatingElement(type)
                  .build()
            }
            .writeTo(filer)
      }
    }

    return false
  }

  private fun adapterGenerator(element: TypeElement): AdapterGenerator? {
    val type = targetType(messager, elements, types, element) ?: return null

    val properties = mutableMapOf<String, PropertyGenerator>()
    for (property in type.properties.values) {
      val generator = property.generator(messager, element, elements)
      if (generator != null) {
        properties[property.name] = generator
      }
    }

    for ((name, parameter) in type.constructor.parameters) {
      if (type.properties[parameter.name] == null && !parameter.hasDefault) {
        messager.printMessage(
            Diagnostic.Kind.ERROR,
            "No property for required constructor parameter $name",
            element)
        return null
      }
    }

    // Sort properties so that those with constructor parameters come first.
    val sortedProperties = properties.values.sortedBy {
      if (it.hasConstructorParameter) {
        it.target.parameterIndex
      } else {
        Integer.MAX_VALUE
      }
    }

    return AdapterGenerator(type, sortedProperties)
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.classinspector.elements.ElementsClassInspector
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
import com.squareup.kotlinpoet.metadata.isAbstract
import com.squareup.kotlinpoet.metadata.isClass
import com.squareup.kotlinpoet.metadata.isEnum
import com.squareup.kotlinpoet.metadata.isInner
import com.squareup.kotlinpoet.metadata.isLocal
import com.squareup.kotlinpoet.metadata.isSealed
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
import com.squareup.kotlinpoet.metadata.specs.toTypeSpec
import com.squareup.kotlinpoet.metadata.toImmutableKmClass
import com.squareup.moshi.Json
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
import com.squareup.moshi.kotlin.codegen.api.TargetConstructor
import com.squareup.moshi.kotlin.codegen.api.TargetParameter
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
import com.squareup.moshi.kotlin.codegen.api.TargetType
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import javax.annotation.processing.Messager
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic

private val JSON_QUALIFIER = JsonQualifier::class.java
private val JSON = Json::class.asClassName()
private val OBJECT_CLASS = ClassName("java.lang", "Object")
private val VISIBILITY_MODIFIERS = setOf(
    KModifier.INTERNAL,
    KModifier.PRIVATE,
    KModifier.PROTECTED,
    KModifier.PUBLIC
)

private fun Collection<KModifier>.visibility(): KModifier {
  return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
}

@KotlinPoetMetadataPreview
internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): TargetConstructor? {
  val primaryConstructor = kotlinApi.primaryConstructor ?: return null

  val parameters = mutableMapOf<String, TargetParameter>()
  for ((index, parameter) in primaryConstructor.parameters.withIndex()) {
    val name = parameter.name
    parameters[name] = TargetParameter(
        name = name,
        index = index,
        hasDefault = parameter.defaultValue != null,
        qualifiers = parameter.annotations.qualifiers(elements),
        jsonName = parameter.annotations.jsonName()
    )
  }

  return TargetConstructor(parameters, primaryConstructor.modifiers.visibility())
}

/** Returns a target type for `element`, or null if it cannot be used with code gen. */
@KotlinPoetMetadataPreview
internal fun targetType(messager: Messager,
    elements: Elements,
    types: Types,
    element: TypeElement): TargetType? {
  val typeMetadata = element.getAnnotation(Metadata::class.java)
  if (typeMetadata == null) {
    messager.printMessage(
        Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
        element)
    return null
  }

  val kmClass = try {
    typeMetadata.toImmutableKmClass()
  } catch (e: UnsupportedOperationException) {
    messager.printMessage(
        Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Class type",
        element)
    return null
  }

  when {
    kmClass.isEnum -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR,
          "@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
          element)
      return null
    }
    !kmClass.isClass -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
          element)
      return null
    }
    kmClass.isInner -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR,
          "@JsonClass can't be applied to $element: must not be an inner class", element)
      return null
    }
    kmClass.isSealed -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be sealed", element)
      return null
    }
    kmClass.isAbstract -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be abstract",
          element)
      return null
    }
    kmClass.isLocal -> {
      messager.printMessage(
          Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be local",
          element)
      return null
    }
  }

  val elementHandler = ElementsClassInspector.create(elements, types)
  val kotlinApi = kmClass.toTypeSpec(elementHandler)
  val typeVariables = kotlinApi.typeVariables
  val appliedType = AppliedType.get(element)

  val constructor = primaryConstructor(kotlinApi, elements)
  if (constructor == null) {
    messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element",
        element)
    return null
  }
  if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
    messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: " +
        "primary constructor is not internal or public", element)
    return null
  }

  val properties = mutableMapOf<String, TargetProperty>()
  for (supertype in appliedType.supertypes(types)) {
    if (supertype.element.asClassName() == OBJECT_CLASS) {
      continue // Don't load properties for java.lang.Object.
    }
    if (supertype.element.kind != ElementKind.CLASS) {
      continue // Don't load properties for interface types.
    }
    if (supertype.element.getAnnotation(Metadata::class.java) == null) {
      messager.printMessage(Diagnostic.Kind.ERROR,
          "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
          element)
      return null
    }
    val supertypeProperties = if (supertype.element == element) {
      // We've already parsed this api above, reuse it
      declaredProperties(supertype.element, constructor, elementHandler, kotlinApi)
    } else {
      declaredProperties(
          supertype.element, constructor, elementHandler)
    }
    for ((name, property) in supertypeProperties) {
      properties.putIfAbsent(name, property)
    }
  }
  return TargetType(
      typeName = element.asType().asTypeName(),
      constructor = constructor,
      properties = properties,
      typeVariables = typeVariables,
      isDataClass = KModifier.DATA in kotlinApi.modifiers,
      visibility = kotlinApi.modifiers.visibility())
}

/** Returns the properties declared by `typeElement`. */
@KotlinPoetMetadataPreview
private fun declaredProperties(
    typeElement: TypeElement,
    constructor: TargetConstructor,
    elementHandler: ClassInspector,
    kotlinApi: TypeSpec = typeElement.toTypeSpec(elementHandler)
): Map<String, TargetProperty> {

  val result = mutableMapOf<String, TargetProperty>()
  for (property in kotlinApi.propertySpecs) {
    val name = property.name
    val parameter = constructor.parameters[name]
    result[name] = TargetProperty(
        propertySpec = property,
        parameter = parameter,
        visibility = property.modifiers.visibility(),
        jsonName = parameter?.jsonName ?: property.annotations.jsonName()
        ?: name.escapeDollarSigns()
    )
  }

  return result
}

private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.className == Transient::class.asClassName() }
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
private val TargetProperty.isVisible: Boolean
  get() {
    return visibility == KModifier.INTERNAL
        || visibility == KModifier.PROTECTED
        || visibility == KModifier.PUBLIC
  }

/**
 * Returns a generator for this property, or null if either there is an error and this property
 * cannot be used with code gen, or if no codegen is necessary for this property.
 */
internal fun TargetProperty.generator(
    messager: Messager,
    sourceElement: TypeElement,
    elements: Elements
): PropertyGenerator? {
  if (isTransient) {
    if (!hasDefault) {
      messager.printMessage(
          Diagnostic.Kind.ERROR, "No default value for transient property $name",
          sourceElement)
      return null
    }
    return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
  }

  if (!isVisible) {
    messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible",
        sourceElement)
    return null
  }

  if (!isSettable) {
    return null // This property is not settable. Ignore it.
  }

  // Merge parameter and property annotations
  val qualifiers = parameter?.qualifiers.orEmpty() + propertySpec.annotations.qualifiers(elements)
  for (jsonQualifier in qualifiers) {
    // Check Java types since that covers both Java and Kotlin annotations.
    val annotationElement = elements.getTypeElement(jsonQualifier.className.canonicalName)
        ?: continue
    annotationElement.getAnnotation(Retention::class.java)?.let {
      if (it.value != RetentionPolicy.RUNTIME) {
        messager.printMessage(Diagnostic.Kind.ERROR,
            "JsonQualifier @${jsonQualifier.className.simpleName} must have RUNTIME retention")
      }
    }
    annotationElement.getAnnotation(Target::class.java)?.let {
      if (ElementType.FIELD !in it.value) {
        messager.printMessage(Diagnostic.Kind.ERROR,
            "JsonQualifier @${jsonQualifier.className.simpleName} must support FIELD target")
      }
    }
  }

  val jsonQualifierSpecs = qualifiers.map {
    it.toBuilder()
        .useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
        .build()
  }

  return PropertyGenerator(this,
      DelegateKey(type, jsonQualifierSpecs))
}

private fun List<AnnotationSpec>?.qualifiers(elements: Elements): Set<AnnotationSpec> {
  if (this == null) return setOf()
  return filterTo(mutableSetOf()) {
    elements.getTypeElement(it.className.toString()).getAnnotation(JSON_QUALIFIER) != null
  }
}

/** Gross, but we can't extract values from AnnotationSpecs by member names alone. */
private fun List<AnnotationSpec>?.jsonName(): String? {
  if (this == null) return null
  return find { it.className == JSON }?.let {
    it.members[0].toString().removePrefix("name = \"").removeSuffix("\"")
  }
}

private fun String.escapeDollarSigns(): String {
  return replace("\$", "\${\'\$\'}")
}

package com.squareup.moshi.kotlin.reflect

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.NonNullJsonAdapter
import com.squareup.moshi.internal.NullSafeJsonAdapter
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.typeOf

/**
 * @return a [JsonAdapter] for [T], creating it if necessary. Note that while nullability of [T]
 *         itself is handled, nested types (such as in generics) are not resolved.
 */
@ExperimentalStdlibApi
inline fun <reified T> Moshi.adapter(): JsonAdapter<T> {
  return adapter(typeOf<T>())
}

@ExperimentalStdlibApi
inline fun <reified T> Moshi.Builder.addAdapter(adapter: JsonAdapter<T>) = add(typeOf<T>().toType(), adapter)

/**
 * @return a [JsonAdapter] for [ktype], creating it if necessary. Note that while nullability of
 *         [ktype] itself is handled, nested types (such as in generics) are not resolved.
 */
fun <T> Moshi.adapter(ktype: KType): JsonAdapter<T> {
  val adapter = adapter<T>(ktype.toType())
  return if (adapter is NullSafeJsonAdapter || adapter is NonNullJsonAdapter) {
    // TODO CR - Assume that these know what they're doing? Or should we defensively avoid wrapping for matching nullability?
    adapter
  } else if (ktype.isMarkedNullable) {
    adapter.nullSafe()
  } else {
    adapter.nonNull()
  }
}

@PublishedApi
internal fun KType.toType(): Type {
  classifier?.let {
    when (it) {
      is KTypeParameter -> throw IllegalArgumentException("Type parameters are not supported")
      is KClass<*> -> {
        val javaType = it.java
        if (javaType.isArray) {
          return Types.arrayOf(javaType.componentType)
        }

        return if (arguments.isEmpty()) {
          javaType
        } else {
          val typeArguments = arguments.toTypedArray { it.toType() }
          val enclosingClass = javaType.enclosingClass
          return if (enclosingClass != null) {
            Types.newParameterizedTypeWithOwner(enclosingClass, javaType, *typeArguments)
          } else {
            Types.newParameterizedType(javaType, *typeArguments)
          }
        }
      }
      else -> throw IllegalArgumentException("Unsupported classifier: $this")
    }
  }

  // Can happen for intersection types
  throw IllegalArgumentException("Unrepresentable type: $this")
}

internal fun KTypeProjection.toType(): Type {
  val javaType = type?.toType() ?: return Any::class.java
  return when (variance) {
    null -> Any::class.java
    KVariance.INVARIANT -> javaType
    KVariance.IN -> Types.subtypeOf(javaType)
    KVariance.OUT -> Types.supertypeOf(javaType)
  }
}

private inline fun <T, reified R> List<T>.toTypedArray(mapper: (T) -> R): Array<R> {
  return Array(size) {
    mapper.invoke(get(it))
  }
}

/*
 * Copyright (C) 2017 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.moshi.kotlin.reflect

import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.Util
import com.squareup.moshi.internal.Util.generatedAdapter
import com.squareup.moshi.internal.Util.resolve
import java.lang.reflect.Modifier
import java.lang.reflect.Type
import java.util.AbstractMap.SimpleEntry
import kotlin.collections.Map.Entry
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaType

/** Classes annotated with this are eligible for this adapter. */
private val KOTLIN_METADATA = Metadata::class.java

/**
 * Placeholder value used when a field is absent from the JSON. Note that this code
 * distinguishes between absent values and present-but-null values.
 */
private val ABSENT_VALUE = Any()

/**
 * This class encodes Kotlin classes using their properties. It decodes them by first invoking the
 * constructor, and then by setting any additional properties that exist, if any.
 */
internal class KotlinJsonAdapter<T>(
  private val constructor: KFunction<T>,
  private val bindings: List<Binding<T, Any?>?>,
  private val options: JsonReader.Options
) : JsonAdapter<T>() {

  override fun fromJson(reader: JsonReader): T {
    val constructorSize = constructor.parameters.size

    // Read each value into its slot in the array.
    val values = Array<Any?>(bindings.size) { ABSENT_VALUE }
    reader.beginObject()
    while (reader.hasNext()) {
      val index = reader.selectName(options)
      val binding = if (index != -1) bindings[index] else null

      if (binding == null) {
        reader.skipName()
        reader.skipValue()
        continue
      }

      if (values[index] !== ABSENT_VALUE) {
        throw JsonDataException(
            "Multiple values for '${binding.property.name}' at ${reader.path}")
      }

      values[index] = binding.adapter.fromJson(reader)

      if (values[index] == null && !binding.property.returnType.isMarkedNullable) {
        throw Util.unexpectedNull(
            binding.property.name,
            binding.jsonName,
            reader
        )
      }
    }
    reader.endObject()

    // Confirm all parameters are present, optional, or nullable.
    for (i in 0 until constructorSize) {
      if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
        if (!constructor.parameters[i].type.isMarkedNullable) {
          throw Util.missingProperty(
              constructor.parameters[i].name,
              bindings[i]?.jsonName,
              reader
          )
        }
        values[i] = null // Replace absent with null.
      }
    }

    // Call the constructor using a Map so that absent optionals get defaults.
    val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))

    // Set remaining properties.
    for (i in constructorSize until bindings.size) {
      val binding = bindings[i]!!
      val value = values[i]
      binding.set(result, value)
    }

    return result
  }

  override fun toJson(writer: JsonWriter, value: T?) {
    if (value == null) throw NullPointerException("value == null")

    writer.beginObject()
    for (binding in bindings) {
      if (binding == null) continue // Skip constructor parameters that aren't properties.

      writer.name(binding.name)
      binding.adapter.toJson(writer, binding.get(value))
    }
    writer.endObject()
  }

  override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"

  data class Binding<K, P>(
      val name: String,
      val jsonName: String?,
      val adapter: JsonAdapter<P>,
      val property: KProperty1<K, P>,
      val parameter: KParameter?) {
    fun get(value: K) = property.get(value)

    fun set(result: K, value: P) {
      if (value !== ABSENT_VALUE) {
        (property as KMutableProperty1<K, P>).set(result, value)
      }
    }
  }

  /** A simple [Map] that uses parameter indexes instead of sorting or hashing. */
  class IndexedParameterMap(
    private val parameterKeys: List<KParameter>,
    private val parameterValues: Array<Any?>
  ) : AbstractMap<KParameter, Any?>() {

    override val entries: Set<Entry<KParameter, Any?>>
      get() {
        val allPossibleEntries = parameterKeys.mapIndexed { index, value ->
          SimpleEntry<KParameter, Any?>(value, parameterValues[index])
        }
        return allPossibleEntries.filterTo(LinkedHashSet<Entry<KParameter, Any?>>()) {
          it.value !== ABSENT_VALUE
        }
      }

    override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE

    override fun get(key: KParameter): Any? {
      val value = parameterValues[key.index]
      return if (value !== ABSENT_VALUE) value else null
    }
  }
}

class KotlinJsonAdapterFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi)
      : JsonAdapter<*>? {
    if (annotations.isNotEmpty()) return null

    val rawType = Types.getRawType(type)
    if (rawType.isInterface) return null
    if (rawType.isEnum) return null
    if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
    if (Util.isPlatformType(rawType)) return null
    try {
      val generatedAdapter = generatedAdapter(moshi, type, rawType)
      if (generatedAdapter != null) {
        return generatedAdapter
      }
    } catch (e: RuntimeException) {
      if (e.cause !is ClassNotFoundException) {
        throw e
      }
      // Fall back to a reflective adapter when the generated adapter is not found.
    }

    require(!rawType.isLocalClass) {
      "Cannot serialize local class or object expression ${rawType.name}"
    }
    val rawTypeKotlin = rawType.kotlin
    require(!rawTypeKotlin.isAbstract) {
      "Cannot serialize abstract class ${rawType.name}"
    }
    require(!rawTypeKotlin.isInner) {
      "Cannot serialize inner class ${rawType.name}"
    }
    require(rawTypeKotlin.objectInstance == null) {
      "Cannot serialize object declaration ${rawType.name}"
    }
    require(!rawTypeKotlin.isSealed) {
      "Cannot reflectively serialize sealed class ${rawType.name}. Please register an adapter."
    }

    val constructor = rawTypeKotlin.primaryConstructor ?: return null
    val parametersByName = constructor.parameters.associateBy { it.name }
    constructor.isAccessible = true

    val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()

    for (property in rawTypeKotlin.memberProperties) {
      val parameter = parametersByName[property.name]

      if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
        require(parameter == null || parameter.isOptional) {
          "No default value for transient constructor $parameter"
        }
        continue
      }

      require(parameter == null || parameter.type == property.returnType) {
        "'${property.name}' has a constructor parameter of type ${parameter!!.type} but a property of type ${property.returnType}."
      }

      if (property !is KMutableProperty1 && parameter == null) continue

      property.isAccessible = true
      val allAnnotations = property.annotations.toMutableList()
      var jsonAnnotation = property.findAnnotation<Json>()

      if (parameter != null) {
        allAnnotations += parameter.annotations
        if (jsonAnnotation == null) {
          jsonAnnotation = parameter.findAnnotation()
        }
      }

      val name = jsonAnnotation?.name ?: property.name
      val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType)
      val adapter = moshi.adapter<Any>(
          resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name)

      @Suppress("UNCHECKED_CAST")
      bindingsByName[property.name] = KotlinJsonAdapter.Binding(
          name,
          jsonAnnotation?.name ?: name,
          adapter,
          property as KProperty1<Any, Any?>,
          parameter
      )
    }

    val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()

    for (parameter in constructor.parameters) {
      val binding = bindingsByName.remove(parameter.name)
      require(binding != null || parameter.isOptional) {
        "No property for required constructor $parameter"
      }
      bindings += binding
    }

    bindings += bindingsByName.values

    val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray())
    return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.internal.Util
import java.lang.reflect.Constructor
import java.lang.reflect.Type

private val MOSHI_UTIL = Util::class.asClassName()
private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren

/** Generates a JSON adapter for a target type. */
internal class AdapterGenerator(
    target: TargetType,
    private val propertyList: List<PropertyGenerator>
) {
  private val nonTransientProperties = propertyList.filterNot { it.isTransient }
  private val className = target.typeName.rawType()
  private val visibility = target.visibility
  private val typeVariables = target.typeVariables

  private val nameAllocator = NameAllocator()
  private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
  private val originalTypeName = target.typeName

  private val moshiParam = ParameterSpec.builder(
      nameAllocator.newName("moshi"),
      Moshi::class).build()
  private val typesParam = ParameterSpec.builder(
      nameAllocator.newName("types"),
      ARRAY.parameterizedBy(Type::class.asTypeName()))
      .build()
  private val readerParam = ParameterSpec.builder(
      nameAllocator.newName("reader"),
      JsonReader::class)
      .build()
  private val writerParam = ParameterSpec.builder(
      nameAllocator.newName("writer"),
      JsonWriter::class)
      .build()
  private val valueParam = ParameterSpec.builder(
      nameAllocator.newName("value"),
      originalTypeName.copy(nullable = true))
      .build()
  private val jsonAdapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(
      originalTypeName)

  // selectName() API setup
  private val optionsProperty = PropertySpec.builder(
      nameAllocator.newName("options"), JsonReader.Options::class.asTypeName(),
      KModifier.PRIVATE)
      .initializer("%T.of(${nonTransientProperties.joinToString(", ") {
        // We manually put in quotes because we know the jsonName is already escaped
        CodeBlock.of("\"%L\"", it.jsonName).toString()
      }})", JsonReader.Options::class.asTypeName())
      .build()

  private val constructorProperty = PropertySpec.builder(
      nameAllocator.newName("constructorRef"),
      Constructor::class.asClassName().parameterizedBy(originalTypeName).copy(nullable = true),
      KModifier.PRIVATE)
      .addAnnotation(Volatile::class)
      .mutable(true)
      .initializer("null")
      .build()

  fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec {
    for (property in nonTransientProperties) {
      property.allocateNames(nameAllocator)
    }

    val result = FileSpec.builder(className.packageName, adapterName)
    result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
    result.addType(generateType().let(typeHook))
    return result.build()
  }

  private fun generateType(): TypeSpec {
    val result = TypeSpec.classBuilder(adapterName)

    result.superclass(jsonAdapterTypeName)

    if (typeVariables.isNotEmpty()) {
      result.addTypeVariables(typeVariables)
    }

    // TODO make this configurable. Right now it just matches the source model
    if (visibility == KModifier.INTERNAL) {
      result.addModifiers(KModifier.INTERNAL)
    }

    result.primaryConstructor(generateConstructor())

    val typeRenderer: TypeRenderer = object : TypeRenderer() {
      override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock {
        val index = typeVariables.indexOfFirst { it == typeVariable }
        check(index != -1) { "Unexpected type variable $typeVariable" }
        return CodeBlock.of("%N[%L]", typesParam, index)
      }
    }

    result.addProperty(optionsProperty)
    for (uniqueAdapter in nonTransientProperties.distinctBy { it.delegateKey }) {
      result.addProperty(uniqueAdapter.delegateKey.generateProperty(
          nameAllocator, typeRenderer, moshiParam, uniqueAdapter.name))
    }

    result.addFunction(generateToStringFun())
    result.addFunction(generateFromJsonFun(result))
    result.addFunction(generateToJsonFun())

    return result.build()
  }

  private fun generateConstructor(): FunSpec {
    val result = FunSpec.constructorBuilder()
    result.addParameter(moshiParam)

    if (typeVariables.isNotEmpty()) {
      result.addParameter(typesParam)
    }

    return result.build()
  }

  private fun generateToStringFun(): FunSpec {
    val name = originalTypeName.rawType().simpleNames.joinToString(".")
    val size = TO_STRING_SIZE_BASE + name.length
    return FunSpec.builder("toString")
        .addModifiers(KModifier.OVERRIDE)
        .returns(String::class)
        .addStatement(
            "return %M(%L)·{ append(%S).append(%S).append('%L') }",
            MemberName("kotlin.text", "buildString"),
            size,
            TO_STRING_PREFIX,
            name,
            ")"
        )
        .build()
  }

  private fun generateFromJsonFun(classBuilder: TypeSpec.Builder): FunSpec {
    val result = FunSpec.builder("fromJson")
        .addModifiers(KModifier.OVERRIDE)
        .addParameter(readerParam)
        .returns(originalTypeName)

    for (property in nonTransientProperties) {
      result.addCode("%L", property.generateLocalProperty())
      if (property.hasLocalIsPresentName) {
        result.addCode("%L", property.generateLocalIsPresentProperty())
      }
    }

    val maskName = nameAllocator.newName("mask")
    val useConstructorDefaults = nonTransientProperties.any { it.hasConstructorDefault }
    if (useConstructorDefaults) {
      result.addStatement("var %L = -1", maskName)
    }
    result.addStatement("%N.beginObject()", readerParam)
    result.beginControlFlow("while (%N.hasNext())", readerParam)
    result.beginControlFlow("when (%N.selectName(%N))", readerParam, optionsProperty)

    // We track property index and mask index separately, because mask index is based on _all_
    // constructor arguments, while property index is only based on the index passed into
    // JsonReader.Options.
    var propertyIndex = 0
    var maskIndex = 0
    for (property in propertyList) {
      if (property.isTransient) {
        if (property.hasConstructorParameter) {
          maskIndex++
        }
        continue
      }
      if (property.hasLocalIsPresentName || property.hasConstructorDefault) {
        result.beginControlFlow("%L ->", propertyIndex)
        if (property.delegateKey.nullable) {
          result.addStatement("%N = %N.fromJson(%N)",
              property.localName, nameAllocator[property.delegateKey], readerParam)
        } else {
          val exception = unexpectedNull(property, readerParam)
          result.addStatement("%N = %N.fromJson(%N) ?: throw·%L",
              property.localName, nameAllocator[property.delegateKey], readerParam, exception)
        }
        if (property.hasConstructorDefault) {
          val inverted = (1 shl maskIndex).inv()
          result.addComment("\$mask = \$mask and (1 shl %L).inv()", maskIndex)
          result.addStatement("%1L = %1L and %2L", maskName, inverted)
        } else {
          // Presence tracker for a mutable property
          result.addStatement("%N = true", property.localIsPresentName)
        }
        result.endControlFlow()
      } else {
        if (property.delegateKey.nullable) {
          result.addStatement("%L -> %N = %N.fromJson(%N)",
              propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam)
        } else {
          val exception = unexpectedNull(property, readerParam)
          result.addStatement("%L -> %N = %N.fromJson(%N) ?: throw·%L",
              propertyIndex, property.localName, nameAllocator[property.delegateKey], readerParam,
              exception)
        }
      }
      propertyIndex++
      maskIndex++
    }

    result.beginControlFlow("-1 ->")
    result.addComment("Unknown name, skip it.")
    result.addStatement("%N.skipName()", readerParam)
    result.addStatement("%N.skipValue()", readerParam)
    result.endControlFlow()

    result.endControlFlow() // when
    result.endControlFlow() // while
    result.addStatement("%N.endObject()", readerParam)

    var separator = "\n"
    val parameterProperties = propertyList.filter { it.hasConstructorParameter }
    val useDefaultsConstructor = parameterProperties.any { it.hasDefault }

    val resultName = nameAllocator.newName("result")
    val hasNonConstructorProperties = nonTransientProperties.any { !it.hasConstructorParameter }
    val returnOrResultAssignment = if (hasNonConstructorProperties) {
      // Save the result var for reuse
      CodeBlock.of("val %N = ", resultName)
    } else {
      CodeBlock.of("return·")
    }
    if (useDefaultsConstructor) {
      classBuilder.addProperty(constructorProperty)
      // Dynamic default constructor call
      val rawOriginalTypeName = originalTypeName.rawType()
      val nonNullConstructorType = constructorProperty.type.copy(nullable = false)
      val coreLookupBlock = CodeBlock.of(
          "%T.lookupDefaultsConstructor(%T::class.java)",
          MOSHI_UTIL,
          rawOriginalTypeName
      )
      val lookupBlock = if (originalTypeName is ParameterizedTypeName) {
        CodeBlock.of("(%L·as·%T)", coreLookupBlock, nonNullConstructorType)
      } else {
        coreLookupBlock
      }
      val initializerBlock = CodeBlock.of(
          "this.%1N ?:·%2L.also·{ this.%1N·= it }",
          constructorProperty,
          lookupBlock
      )
      val localConstructorProperty = PropertySpec.builder(
          nameAllocator.newName("localConstructor"),
          nonNullConstructorType)
          .addAnnotation(AnnotationSpec.builder(Suppress::class)
              .addMember("%S", "UNCHECKED_CAST")
              .build())
          .initializer(initializerBlock)
          .build()
      result.addCode("%L", localConstructorProperty)
      result.addCode(
          "«%L%N.newInstance(",
          returnOrResultAssignment,
          localConstructorProperty
      )
    } else {
      // Standard constructor call
      result.addCode("«%L%T(", returnOrResultAssignment, originalTypeName)
    }

    for (property in parameterProperties) {
      result.addCode(separator)
      if (useDefaultsConstructor) {
        if (property.isTransient) {
          // We have to use the default primitive for the available type in order for
          // invokeDefaultConstructor to properly invoke it. Just using "null" isn't safe because
          // the transient type may be a primitive type.
          result.addCode(property.target.type.defaultPrimitiveValue())
        } else {
          result.addCode("%N", property.localName)
        }
      } else {
        result.addCode("%N = %N", property.name, property.localName)
      }
      if (!property.isTransient && property.isRequired) {
        val missingPropertyBlock =
            CodeBlock.of("%T.missingProperty(%S, %S, %N)",
                MOSHI_UTIL, property.localName, property.jsonName, readerParam)
        result.addCode(" ?: throw·%L", missingPropertyBlock)
      }
      separator = ",\n"
    }

    if (useDefaultsConstructor) {
      // Add the mask and a null instance for the trailing default marker instance
      result.addCode(",\n%L,\nnull", maskName)
    }

    result.addCode("\n»)\n")

    // Assign properties not present in the constructor.
    for (property in nonTransientProperties) {
      if (property.hasConstructorParameter) {
        continue // Property already handled.
      }
      if (property.hasLocalIsPresentName) {
        result.addStatement("%1N.%2N = if (%3N) %4N else %1N.%2N",
            resultName, property.name, property.localIsPresentName, property.localName)
      } else {
        result.addStatement("%1N.%2N = %3N ?: %1N.%2N",
            resultName, property.name, property.localName)
      }
    }

    if (hasNonConstructorProperties) {
      result.addStatement("return·%1N", resultName)
    }
    return result.build()
  }

  private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
    return CodeBlock.of("%T.unexpectedNull(%S, %S, %N)",
        MOSHI_UTIL, property.localName, property.jsonName, reader)
  }

  private fun generateToJsonFun(): FunSpec {
    val result = FunSpec.builder("toJson")
        .addModifiers(KModifier.OVERRIDE)
        .addParameter(writerParam)
        .addParameter(valueParam)

    result.beginControlFlow("if (%N == null)", valueParam)
    result.addStatement("throw·%T(%S)", NullPointerException::class,
        "${valueParam.name} was null! Wrap in .nullSafe() to write nullable values.")
    result.endControlFlow()

    result.addStatement("%N.beginObject()", writerParam)
    nonTransientProperties.forEach { property ->
      // We manually put in quotes because we know the jsonName is already escaped
      result.addStatement("%N.name(\"%L\")", writerParam, property.jsonName)
      result.addStatement("%N.toJson(%N, %N.%N)",
          nameAllocator[property.delegateKey], writerParam, valueParam, property.name)
    }
    result.addStatement("%N.endObject()", writerParam)

    return result.build()
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Types

/** A JsonAdapter that can be used to encode and decode a particular field. */
internal data class DelegateKey(
  private val type: TypeName,
  private val jsonQualifiers: List<AnnotationSpec>
) {
  val nullable get() = type.isNullable

  /** Returns an adapter to use when encoding and decoding this property. */
  fun generateProperty(
    nameAllocator: NameAllocator,
    typeRenderer: TypeRenderer,
    moshiParameter: ParameterSpec,
    propertyName: String
  ): PropertySpec {
    val qualifierNames = jsonQualifiers.joinToString("") {
      "At${it.className.simpleName}"
    }
    val adapterName = nameAllocator.newName(
        "${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)

    val adapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(type)
    val standardArgs = arrayOf(moshiParameter,
        typeRenderer.render(type))
    val (initializerString, args) = when {
      jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet"))
      else -> {
        ", %T.getFieldJsonQualifierAnnotations(javaClass, " +
            "%S)" to arrayOf(Types::class.asTypeName(), adapterName)
      }
    }
    val finalArgs = arrayOf(*standardArgs, *args, propertyName)

    return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
        .addAnnotations(jsonQualifiers)
        .initializer("%N.adapter(%L$initializerString, %S)", *finalArgs)
        .build()
  }
}

/**
 * Returns a suggested variable name derived from a list of type names. This just concatenates,
 * yielding types like MapOfStringLong.
 */
private fun List<TypeName>.toVariableNames() = joinToString("") { it.toVariableName() }

/** Returns a suggested variable name derived from a type name, like nullableListOfString. */
private fun TypeName.toVariableName(): String {
  val base = when (this) {
    is ClassName -> simpleName
    is ParameterizedTypeName -> rawType.simpleName + "Of" + typeArguments.toVariableNames()
    is WildcardTypeName -> (inTypes + outTypes).toVariableNames()
    is TypeVariableName -> name + bounds.toVariableNames()
    else -> throw IllegalArgumentException("Unrecognized type! $this")
  }

  return if (isNullable) {
    "Nullable$base"
  } else {
    base
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.PropertySpec

/** Generates functions to encode and decode a property as JSON. */
internal class PropertyGenerator(
  val target: TargetProperty,
  val delegateKey: DelegateKey,
  val isTransient: Boolean = false
) {
  val name = target.name
  val jsonName = target.jsonName ?: target.name
  val hasDefault = target.hasDefault

  lateinit var localName: String
  lateinit var localIsPresentName: String

  val isRequired get() = !delegateKey.nullable && !hasDefault

  val hasConstructorParameter get() = target.parameterIndex != -1

  /**
   * IsPresent is required if the following conditions are met:
   * - Is not transient
   * - Has a default
   * - Is not a constructor parameter (for constructors we use a defaults mask)
   * - Is nullable (because we differentiate absent from null)
   *
   * This is used to indicate that presence should be checked first before possible assigning null
   * to an absent value
   */
  val hasLocalIsPresentName = !isTransient && hasDefault && !hasConstructorParameter && delegateKey.nullable
  val hasConstructorDefault = hasDefault && hasConstructorParameter

  fun allocateNames(nameAllocator: NameAllocator) {
    localName = nameAllocator.newName(name)
    localIsPresentName = nameAllocator.newName("${name}Set")
  }

  fun generateLocalProperty(): PropertySpec {
    return PropertySpec.builder(localName, target.type.copy(nullable = true))
        .mutable(true)
        .apply {
          if (hasConstructorDefault) {
            // We default to the primitive default type, as reflectively invoking the constructor
            // without this (even though it's a throwaway) will fail argument type resolution in
            // the reflective invocation.
            initializer(target.type.defaultPrimitiveValue())
          } else {
            initializer("null")
          }
        }
        .build()
  }

  fun generateLocalIsPresentProperty(): PropertySpec {
    return PropertySpec.builder(localIsPresentName, BOOLEAN)
        .mutable(true)
        .initializer("false")
        .build()
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.KModifier

/** A constructor in user code that should be called by generated code. */
internal data class TargetConstructor(
  val parameters: Map<String, TargetParameter>,
  val visibility: KModifier
) {
  init {
    visibility.checkIsVisibility()
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.AnnotationSpec

/** A parameter in user code that should be populated by generated code. */
internal data class TargetParameter(
  val name: String,
  val index: Int,
  val hasDefault: Boolean,
  val jsonName: String? = null,
  val qualifiers: Set<AnnotationSpec>? = null
)

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName

/** A property in user code that maps to JSON. */
internal data class TargetProperty(
  val propertySpec: PropertySpec,
  val parameter: TargetParameter?,
  val visibility: KModifier,
  val jsonName: String?
) {
  val name: String get() = propertySpec.name
  val type: TypeName get() = propertySpec.type
  val parameterIndex get() = parameter?.index ?: -1
  val hasDefault get() = parameter?.hasDefault ?: true

  override fun toString() = name
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName

/** A user type that should be decoded and encoded by generated code. */
internal data class TargetType(
  val typeName: TypeName,
  val constructor: TargetConstructor,
  val properties: Map<String, TargetProperty>,
  val typeVariables: List<TypeVariableName>,
  val isDataClass: Boolean,
  val visibility: KModifier
) {

  init {
    visibility.checkIsVisibility()
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BYTE
import com.squareup.kotlinpoet.CHAR
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.SHORT
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.moshi.Types

/**
 * Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`.
 * Rendering is pluggable so that type variables can either be resolved or emitted as other code
 * blocks.
 */
abstract class TypeRenderer {
  abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock

  fun render(typeName: TypeName): CodeBlock {
    if (typeName.isNullable) {
      return renderObjectType(typeName.copy(nullable = false))
    }

    return when (typeName) {
      is ClassName -> CodeBlock.of("%T::class.java", typeName)

      is ParameterizedTypeName -> {
        // If it's an Array type, we shortcut this to return Types.arrayOf()
        if (typeName.rawType == ARRAY) {
          CodeBlock.of("%T.arrayOf(%L)",
              Types::class,
              renderObjectType(typeName.typeArguments[0]))
        } else {
          val builder = CodeBlock.builder().apply {
            add("%T.", Types::class)
            val enclosingClassName = typeName.rawType.enclosingClassName()
            if (enclosingClassName != null) {
              add("newParameterizedTypeWithOwner(%L, ", render(enclosingClassName))
            } else {
              add("newParameterizedType(")
            }
            add("%T::class.java", typeName.rawType)
            for (typeArgument in typeName.typeArguments) {
              add(", %L", renderObjectType(typeArgument))
            }
            add(")")
          }
          builder.build()
        }
      }

      is WildcardTypeName -> {
        val target: TypeName
        val method: String
        when {
          typeName.inTypes.size == 1 -> {
            target = typeName.inTypes[0]
            method = "supertypeOf"
          }
          typeName.outTypes.size == 1 -> {
            target = typeName.outTypes[0]
            method = "subtypeOf"
          }
          else -> throw IllegalArgumentException(
              "Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
        }
        CodeBlock.of("%T.%L(%T::class.java)", Types::class, method, target.copy(nullable = false))
      }

      is TypeVariableName -> renderTypeVariable(typeName)

      else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
    }
  }

  private fun renderObjectType(typeName: TypeName): CodeBlock {
    return if (typeName.isPrimitive()) {
      CodeBlock.of("%T::class.javaObjectType", typeName)
    } else {
      render(typeName)
    }
  }

  private fun TypeName.isPrimitive(): Boolean {
    return when (this) {
      BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true
      else -> false
    }
  }
}

/*
 * Copyright (C) 2018 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.moshi.kotlin.codegen.api

import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BYTE
import com.squareup.kotlinpoet.CHAR
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.SHORT
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.UNIT
import com.squareup.kotlinpoet.asTypeName

internal fun TypeName.rawType(): ClassName {
  return when (this) {
    is ClassName -> this
    is ParameterizedTypeName -> rawType
    else -> throw IllegalArgumentException("Cannot get raw type from $this")
  }
}

internal fun TypeName.defaultPrimitiveValue(): CodeBlock =
    when (this) {
      BOOLEAN -> CodeBlock.of("false")
      CHAR -> CodeBlock.of("0.toChar()")
      BYTE -> CodeBlock.of("0.toByte()")
      SHORT -> CodeBlock.of("0.toShort()")
      INT -> CodeBlock.of("0")
      FLOAT -> CodeBlock.of("0f")
      LONG -> CodeBlock.of("0L")
      DOUBLE -> CodeBlock.of("0.0")
      UNIT, Void::class.asTypeName() -> throw IllegalStateException("Parameter with void or Unit type is illegal")
      else -> CodeBlock.of("null")
    }

internal fun KModifier.checkIsVisibility() {
  require(ordinal <= ordinal) {
    "Visibility must be one of ${(0..ordinal).joinToString { KModifier.values()[it].name }}. Is $name"
  }
}