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

com.squareup.moshi.kotlin.codegen.ksp.shadedUtil.kt Maven / Gradle / Ivy

There is a newer version: 1.15.1
Show newest version
/*
 * Copyright 2020 Google LLC
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 *
 * 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.ksp

import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueArgument
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass

/*
 * Copied experimental utilities from KSP.
 */

/**
 * Find a class in the compilation classpath for the given name.
 *
 * @param name fully qualified name of the class to be loaded; using '.' as separator.
 * @return a KSClassDeclaration, or null if not found.
 */
internal fun Resolver.getClassDeclarationByName(name: String): KSClassDeclaration? =
  getClassDeclarationByName(getKSNameFromString(name))

internal fun  KSAnnotated.getAnnotationsByType(annotationKClass: KClass): Sequence {
  return this.annotations.filter {
    it.shortName.getShortName() == annotationKClass.simpleName && it.annotationType.resolve().declaration
      .qualifiedName?.asString() == annotationKClass.qualifiedName
  }.map { it.toAnnotation(annotationKClass.java) }
}

internal fun  KSAnnotated.isAnnotationPresent(annotationKClass: KClass): Boolean =
  getAnnotationsByType(annotationKClass).firstOrNull() != null

@Suppress("UNCHECKED_CAST")
private fun  KSAnnotation.toAnnotation(annotationClass: Class): T {
  return Proxy.newProxyInstance(
    annotationClass.classLoader,
    arrayOf(annotationClass),
    createInvocationHandler(annotationClass)
  ) as T
}

@Suppress("TooGenericExceptionCaught")
private fun KSAnnotation.createInvocationHandler(clazz: Class<*>): InvocationHandler {
  val cache = ConcurrentHashMap, Any>, Any>(arguments.size)
  return InvocationHandler { proxy, method, _ ->
    if (method.name == "toString" && arguments.none { it.name?.asString() == "toString" }) {
      clazz.canonicalName +
        arguments.map { argument: KSValueArgument ->
          // handles default values for enums otherwise returns null
          val methodName = argument.name?.asString()
          val value = proxy.javaClass.methods.find { m -> m.name == methodName }?.invoke(proxy)
          "$methodName=$value"
        }.toList()
    } else {
      val argument = try {
        arguments.first { it.name?.asString() == method.name }
      } catch (e: NullPointerException) {
        throw IllegalArgumentException("This is a bug using the default KClass for an annotation", e)
      }
      when (val result = argument.value ?: method.defaultValue) {
        is Proxy -> result
        is List<*> -> {
          val value = { result.asArray(method) }
          cache.getOrPut(Pair(method.returnType, result), value)
        }
        else -> {
          when {
            method.returnType.isEnum -> {
              val value = { result.asEnum(method.returnType) }
              cache.getOrPut(Pair(method.returnType, result), value)
            }
            method.returnType.isAnnotation -> {
              val value = { (result as KSAnnotation).asAnnotation(method.returnType) }
              cache.getOrPut(Pair(method.returnType, result), value)
            }
            method.returnType.name == "java.lang.Class" -> {
              val value = { (result as KSType).asClass() }
              cache.getOrPut(Pair(method.returnType, result), value)
            }
            method.returnType.name == "byte" -> {
              val value = { result.asByte() }
              cache.getOrPut(Pair(method.returnType, result), value)
            }
            method.returnType.name == "short" -> {
              val value = { result.asShort() }
              cache.getOrPut(Pair(method.returnType, result), value)
            }
            else -> result // original value
          }
        }
      }
    }
  }
}

@Suppress("UNCHECKED_CAST")
private fun KSAnnotation.asAnnotation(
  annotationInterface: Class<*>,
): Any {
  return Proxy.newProxyInstance(
    this.javaClass.classLoader, arrayOf(annotationInterface),
    this.createInvocationHandler(annotationInterface)
  ) as Proxy
}

@Suppress("UNCHECKED_CAST")
private fun List<*>.asArray(method: Method) =
  when (method.returnType.componentType.name) {
    "boolean" -> (this as List).toBooleanArray()
    "byte" -> (this as List).toByteArray()
    "short" -> (this as List).toShortArray()
    "char" -> (this as List).toCharArray()
    "double" -> (this as List).toDoubleArray()
    "float" -> (this as List).toFloatArray()
    "int" -> (this as List).toIntArray()
    "long" -> (this as List).toLongArray()
    "java.lang.Class" -> (this as List).map {
      Class.forName(it.declaration.qualifiedName!!.asString())
    }.toTypedArray()
    "java.lang.String" -> (this as List).toTypedArray()
    else -> { // arrays of enums or annotations
      when {
        method.returnType.componentType.isEnum -> {
          this.toArray(method) { result -> result.asEnum(method.returnType.componentType) }
        }
        method.returnType.componentType.isAnnotation -> {
          this.toArray(method) { result ->
            (result as KSAnnotation).asAnnotation(method.returnType.componentType)
          }
        }
        else -> throw IllegalStateException("Unable to process type ${method.returnType.componentType.name}")
      }
    }
  }

@Suppress("UNCHECKED_CAST")
private fun List<*>.toArray(method: Method, valueProvider: (Any) -> Any): Array {
  val array: Array = java.lang.reflect.Array.newInstance(
    method.returnType.componentType,
    this.size
  ) as Array
  for (r in 0 until this.size) {
    array[r] = this[r]?.let { valueProvider.invoke(it) }
  }
  return array
}

@Suppress("UNCHECKED_CAST")
private fun  Any.asEnum(returnType: Class): T =
  returnType.getDeclaredMethod("valueOf", String::class.java)
    .invoke(
      null,
      // Change from upstream KSP - https://github.com/google/ksp/pull/685
      if (this is KSType) {
        this.declaration.simpleName.getShortName()
      } else {
        this.toString()
      }
    ) as T

private fun Any.asByte(): Byte = if (this is Int) this.toByte() else this as Byte

private fun Any.asShort(): Short = if (this is Int) this.toShort() else this as Short

private fun KSType.asClass() = Class.forName(this.declaration.qualifiedName!!.asString())




© 2015 - 2024 Weber Informatics LLC | Privacy Policy