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

io.appmetrica.gradle.aarcheck.api.ApiSerializer.kt Maven / Gradle / Ivy

The newest version!
package io.appmetrica.gradle.aarcheck.api

import io.appmetrica.gradle.aarcheck.utils.MappingFile
import java.io.File

private val NULLABLE_ANNOTATIONS = listOf(
    "androidx.annotation.Nullable",
    "org.jetbrains.annotations.Nullable"
)
private val NONNULL_ANNOTATIONS = listOf(
    "androidx.annotation.NonNull",
    "org.jetbrains.annotations.NotNull"
)

private val NOT_PRINT_PACKAGES = listOf(
    "java.lang"
)

class ApiSerializer(
    mappingFile: MappingFile? = null
) {
    private class BodyGroup(
        val isEmpty: Boolean,
        val appendTo: Appendable.() -> Unit
    )

    private val nameProvider = NameProvider(mappingFile)

    fun writeTo(classes: List, file: File) {
        file.parentFile.mkdirs()

        file.bufferedWriter().use { out ->
            writeTo(classes, out)
        }
    }

    fun writeTo(classes: List, appender: Appendable) {
        val apiClasses = classes.sortedBy { it.fullName }
        for (clazz in apiClasses) {
            when (clazz.type) {
                ClassType.CLASS -> appender.appendClass(clazz)
                ClassType.ENUM -> appender.appendEnum(clazz)
                ClassType.INTERFACE -> appender.appendInterface(clazz)
                ClassType.ANNOTATION -> appender.appendAnnotation(clazz)
            }
            appender.newLine()
        }
    }

    private fun Appendable.appendEnum(clazz: Class) = apply {
        append(clazz.modifier.toString()).append(" ")
        append(clazz.type.toString()).append(" ")
        append(clazz.fullName)
        if (clazz.interfaces.isNotEmpty()) {
            append(" implementation ").appendList(clazz.interfaces, ", ") { appendType(it) }
        }

        val isEnumConstant = { field: Field ->
            field.isFinal && field.isStatic && field.type.fullName == clazz.fullName
        }
        val enumValues = clazz.fields.filter(isEnumConstant)
        val fields = clazz.fields.filterNot(isEnumConstant)

        appendBody(
            BodyGroup(isEmpty = enumValues.isEmpty()) {
                appendTab()
                appendList(enumValues.sortedBy { it.name }, ", ") { append(it.name) }
                newLine()
            },
            fieldBodyGroup(fields),
            methodBodyGroup(clazz.methods)
        )
        newLine()
    }

    private fun Appendable.appendAnnotation(clazz: Class) = apply {
        append(clazz.modifier.toString()).append(" ")
        append(clazz.type.toString()).append(" ")
        append(clazz.fullName)
        appendBody(
            fieldBodyGroup(clazz.fields),
            methodBodyGroup(clazz.methods)
        )
        newLine()
    }

    private fun Appendable.appendInterface(clazz: Class) = apply {
        append(clazz.modifier.toString()).append(" ")
        append(clazz.type.toString()).append(" ")
        append(clazz.fullName)
        appendGenericParameters(clazz.genericParameters)
        if (clazz.interfaces.isNotEmpty()) {
            append(" extends ").appendList(clazz.interfaces, ", ") { appendType(it) }
        }
        appendBody(
            fieldBodyGroup(clazz.fields),
            methodBodyGroup(clazz.methods)
        )
        newLine()
    }

    private fun Appendable.appendClass(clazz: Class) = apply {
        append(clazz.modifier.toString()).append(" ")
        if (clazz.isAbstract) append("abstract ")
        if (clazz.isStatic) append("static ")
        if (clazz.isFinal) append("final ")
        append(clazz.type.toString()).append(" ")
        append(clazz.fullName)
        appendGenericParameters(clazz.genericParameters)
        if (clazz.superClass != null) {
            append(" extends ").appendType(clazz.superClass)
        }
        if (clazz.interfaces.isNotEmpty()) {
            append(" implementation ").appendList(clazz.interfaces, ", ") { appendType(it) }
        }
        appendBody(
            fieldBodyGroup(clazz.fields),
            methodBodyGroup(clazz.methods)
        )
        newLine()
    }

    private fun fieldBodyGroup(fields: List) = BodyGroup(isEmpty = fields.isEmpty()) {
        fields.sortedBy { nameProvider.getClearFieldName(it) }.forEach { appendField(it) }
    }

    private fun methodBodyGroup(methods: List) = BodyGroup(isEmpty = methods.isEmpty()) {
        methods.sortedBy { "${nameProvider.getClearMethodName(it)}(${it.paramString})" }.forEach { appendMethod(it) }
    }

    private fun Appendable.appendBody(vararg groups: BodyGroup) = apply {
        if (groups.all { it.isEmpty }) return@apply
        append(" {").newLine()
        appendList(groups.filterNot { it.isEmpty }, "\n") { it.appendTo(this) }
        append("}")
    }

    private fun Appendable.appendField(field: Field) = apply {
        appendTab()
        append(field.modifier.toString()).append(" ")
        if (field.isStatic) append("static ")
        if (field.isFinal) append("final ")
        appendNullable(field.annotations)
        appendType(field.type).append(" ")
        append(nameProvider.getFieldName(field))
        newLine()
    }

    private fun Appendable.appendMethod(method: Method) = apply {
        appendTab()
        append(method.modifier.toString()).append(" ")
        if (method.isAbstract) append("abstract ")
        if (method.isStatic) append("static ")
        if (method.isFinal) append("final ")
        if (method.genericParameters.isNotEmpty()) {
            appendGenericParameters(method.genericParameters).append(" ")
        }
        if (!method.isConstructor) {
            appendNullable(method.annotations)
            appendType(method.returnType).append(" ")
        }
        append(nameProvider.getMethodName(method))
        append("(")
        appendList(method.params, ", ") { appendMethodParam(it) }
        append(")")
        newLine()
    }

    private fun Appendable.appendMethodParam(param: MethodParam) = apply {
        appendNullable(param.annotations)
        appendType(param.type)
        if (param.isVarArgs) append("...")
    }

    private fun Appendable.appendType(type: Type) = apply {
        if (type.packageName in NOT_PRINT_PACKAGES) {
            append(type.name)
        } else {
            append(nameProvider.getClassName(type))
        }
        appendTypeArguments(type.arguments)
        repeat(type.arrayDim) {
            append("[]")
        }
    }

    private fun Appendable.appendGenericParameters(parameters: List) = apply {
        if (parameters.isEmpty()) return@apply
        append("<")
        appendList(parameters, ", ") { appendTypeParameter(it) }
        append(">")
    }

    private fun Appendable.appendTypeParameter(parameter: TypeParameter) = apply {
        append(parameter.name)
        val extends = listOfNotNull(
            parameter.extend.takeIf { it.fullName != "java.lang.Object" }
        ) + parameter.interfaces
        if (extends.isNotEmpty()) {
            append(" extends ")
            appendList(extends, " & ") { appendType(it) }
        }
    }

    private fun Appendable.appendTypeArguments(arguments: List) = apply {
        if (arguments.isEmpty()) return@apply
        append("<")
        appendList(arguments, ", ") { appendTypeArgument(it) }
        append(">")
    }

    private fun Appendable.appendTypeArgument(argument: TypeArgument): Appendable {
        when (argument.wildcard) {
            '*' -> append("?")
            '+' -> append("? extends ")
            '-' -> append("? super ")
        }
        if (argument.type != null) {
            appendType(argument.type)
        }
        return this
    }

    private fun Appendable.appendNullable(annotations: List) = apply {
        if (annotations.any { it.type.fullName in NULLABLE_ANNOTATIONS }) {
            append("[Nullable] ")
        } else if (annotations.any { it.type.fullName in NONNULL_ANNOTATIONS }) {
            append("[NonNull] ")
        }
    }
}

private fun  Appendable.appendList(list: List, delimiter: String, appender: Appendable.(T) -> Unit) = apply {
    list.forEachIndexed { index, it ->
        appender(it)
        if (index != list.lastIndex) {
            append(delimiter)
        }
    }
}

private fun Appendable.appendTab(n: Int = 1) = apply {
    append("    ".repeat(n))
}

private fun Appendable.newLine() = apply {
    append("\n")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy