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

com.prt2121.kpop.internal.KMethod.kt Maven / Gradle / Ivy

The newest version!
package com.prt2121.kpop.internal

import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.body.Parameter
import com.github.javaparser.ast.expr.AnnotationExpr
import com.github.javaparser.ast.expr.MarkerAnnotationExpr
import com.github.javaparser.ast.expr.Name
import com.github.javaparser.ast.type.*
import sun.reflect.generics.reflectiveObjects.NotImplementedException

/**
 * Represents a method implementation that needs to be wired up in Kotlin
 */
class KMethod(declaration: MethodDeclaration) {
  private val name = declaration.name
  private val annotations: List = declaration.annotations
  private val comment = declaration.comment?.toString()?.let { cleanUpDoc(it, declaration.parameters.first() ) }
  private val extendedClass = declaration.parameters[0].type.toString() // todo
  private val parameters = declaration.parameters.subList(1, declaration.parameters.size)
  private val returnType = declaration.type
  private val typeParameters = typeParams(declaration.typeParameters)
  private val GenericTypeNullableAnnotation = MarkerAnnotationExpr(Name("GenericTypeNullable"))

  /** Generates method level type parameters */
  private fun typeParams(params: List?): String? {
    if (params == null || params.isEmpty()) {
      return null
    }

    return params.joinToString(prefix = "<", postfix = ">") { p ->
      if (p.typeBound.isNotEmpty())
        "${p.name} : ${resolveKotlinType(p.typeBound.first())}"
      else
        "${p.name}"
    }
  }

  /**
   * Generates parameters in a kotlin-style format
   *
   * @param specifyType boolean indicating whether or not to specify the type (i.e. we don't
   *        need the type when we're passing params into the underlying Java implementation)
   */
  private fun kParams(specifyType: Boolean): String {
    val builder = StringBuilder()
    parameters.forEach { p -> builder.append("${p.name}${if (specifyType) ": " + resolveKotlinType(p.type) else ""}") }
    return builder.toString()
  }

  /**
   * Generates the kotlin code for this method
   *
   * @param clazz name of the class this is tied to
   */
  internal fun generate(clazz: String): String {
    ///////////////
    // STRUCTURE //
    ///////////////
    // Javadoc
    // public inline fun DrawerLayout.drawerOpen(): Observable = RxDrawerLayout.drawerOpen(this)
    //  inline fun .(params):  = .name(this, params)

    val fParams = kParams(true)
    val jParams = kParams(false)

    val builder = StringBuilder()

    // doc
    builder.append("${comment ?: ""}\n")

    // signature boilerplate
    builder.append("inline fun ")

    // type params
    builder.append(if (typeParameters != null) typeParameters + " " else "")

    // return type
    val kotlinType = resolveKotlinType(returnType, annotations)
    builder.append("$extendedClass.$name($fParams): $kotlinType")

    builder.append(" = ")

    // target method call
    builder.append("$clazz.$name(${if (jParams.isNotEmpty()) "this, $jParams" else "this"})")

    // todo
    // Void --> Unit mapping
    if (kotlinType.contains("")) {
      builder.append(".map { Unit }")
    }

    return builder.toString()
  }

  /** Recursive function for resolving a Type into a Kotlin-friendly String representation */
  internal fun resolveKotlinType(inputType: Type, methodAnnotations: List? = null): String {
    when (inputType) {
      is ArrayType -> return resolveKotlinType(inputType.elementType, methodAnnotations)
      is ClassOrInterfaceType -> {
        val baseType = resolveKotlinTypeByName(inputType.nameAsString)
        if (inputType.typeArguments == null || !inputType.typeArguments.isPresent) {
          return if (baseType == inputType.nameAsString) inputType.toString() else baseType
        }
        val base = if (baseType == inputType.nameAsString) inputType.toString().substringBefore("<") else baseType
        return "$base<${inputType.typeArguments.get().map { type: Type -> resolveKotlinType(type, methodAnnotations) }.joinToString()}>"
      }
      is PrimitiveType, is VoidType -> return resolveKotlinTypeByName(inputType.toString())
      is WildcardType -> {
        var nullable = ""
        methodAnnotations
            ?.filter { it == GenericTypeNullableAnnotation }
            ?.forEach { nullable = "?" }
        if (inputType.superTypes != null) {
          return "in ${resolveKotlinType(inputType.superTypes.get())}$nullable"
        } else if (inputType.extendedTypes != null) {
          return "out ${resolveKotlinType(inputType.extendedTypes.get())}$nullable"
        } else {
          throw IllegalStateException("Wildcard with no super or extends")
        }
      }
      else -> throw NotImplementedException()
    }
  }

  companion object {
    internal val DOC_LINK_REGEX = "[0-9A-Za-z._]*"

    internal fun resolveKotlinTypeByName(input: String): String =
        when (input) {
          "Object" -> "Any"
          "Void" -> "Unit"
          "Integer" -> "Int"
          "int", "char", "boolean", "long", "float", "short", "byte" -> input.capitalize()
          "List" -> "MutableList"
          else -> input
        }

    /** Cleans up the generated doc and translates some html to equivalent markdown for Kotlin docs */
    internal fun cleanUpDoc(doc: String, selfParam: Parameter): String {
      return doc.replace("", "*")
          .replace("", "*")
          .replace("

", "") // JavaParser adds a couple spaces to the beginning of these for some reason .replace(" *", " *") // {@code view} -> `view` .replace("\\{@code ($DOC_LINK_REGEX)\\}".toRegex()) { result: MatchResult -> val codeName = result.destructured "`${codeName.component1()}`" } // {@link Foo} -> [Foo] .replace("\\{@link ($DOC_LINK_REGEX)\\}".toRegex()) { result: MatchResult -> val foo = result.destructured "[${foo.component1()}]" } // {@link Foo#bar} -> [Foo.bar] .replace("\\{@link ($DOC_LINK_REGEX)#($DOC_LINK_REGEX)\\}".toRegex()) { result: MatchResult -> val (foo, bar) = result.destructured "[$foo.$bar]" } // {@linkplain Foo baz} -> [baz][Foo] .replace("\\{@linkplain ($DOC_LINK_REGEX) ($DOC_LINK_REGEX)\\}".toRegex()) { result: MatchResult -> val (foo, baz) = result.destructured "[$baz][$foo]" } //{@linkplain Foo#bar baz} -> [baz][Foo.bar] .replace("\\{@linkplain ($DOC_LINK_REGEX)#($DOC_LINK_REGEX) ($DOC_LINK_REGEX)\\}".toRegex()) { result: MatchResult -> val (foo, bar, baz) = result.destructured "[$baz][$foo.$bar]" } // Remove any trailing whitespace .replace("(?m)\\s+$".toRegex(), "") // Remove the first param doc .replaceFirstParamDoc(selfParam) .trim() } private fun String.replaceFirstParamDoc(param: Parameter): String { val lines = lines() val start = lines.indexOfFirst { it.contains("@param ${param.nameAsString}") } if (start == -1) return this val numOfLines = lines.subList(start + 1, lines.size).indexOfFirst { it.contains("@") || it.contains("*/") } return lines.filterIndexed { i, _ -> i !in start..(start + numOfLines) }.joinToString("\n") } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy