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

jvmMain.com.bkahlert.kommons.debug.StackTrace.kt Maven / Gradle / Ivy

There is a newer version: 2.8.0
Show newest version
package com.bkahlert.kommons.debug

import com.bkahlert.kommons.EMPTY
import java.lang.reflect.Method
import java.util.Locale
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KFunction

/** Representation of a stack trace. */
public class StackTrace(elements: List) : List by elements {
    override fun toString(): String {
        return joinToString("\n    at ")
    }

    public companion object
}

/** Gets the current [StackTrace]. */
@Suppress("NOTHING_TO_INLINE") // inline to avoid impact on stack trace
public inline fun StackTrace.Companion.get(): StackTrace =
    Thread.currentThread().stackTrace
        .dropWhile { it.className == Thread::class.qualifiedName }
        .let { StackTrace(it) }

/**
 * Finds the [StackTraceElement] that immediately follows the one
 * the specified [predicate] stops returning `true` for the first time.
 *
 * In other words:
 * - The [predicate] is used to drop irrelevant calls for as long as it returns `false`.
 * - As soon as it responds `true` calls that are expected to exist are dropped.
 * - Finally, the next element which represents the caller of the last matched call is returned.
 */
public fun StackTrace.findOrNull(predicate: (StackTraceElement) -> Boolean): StackTraceElement? =
    (if (firstOrNull()?.let(predicate) == false) dropWhile { !predicate(it) } else this).dropWhile { predicate(it) }.firstOrNull()

/**
 * Finds the [StackTraceElement] that immediately follows the one
 * the specified [predicate] stops returning `true` for the first time.
 *
 * In other words:
 * - The [predicate] is used to drop irrelevant calls for as long as it returns `false`.
 * - As soon as it responds `true` calls that are expected to exist are dropped.
 * - Finally, the next element which represents the caller of the last matched call is returned.
 */
public fun StackTrace.find(predicate: (StackTraceElement) -> Boolean): StackTraceElement =
    findOrNull(predicate) ?: throw NoSuchElementException()

private val functionMangleRegex: Regex = "\\$.*$".toRegex()
private val generatedFunctionRegex = "\\\$.*".toRegex()

/** Returns the specified [function] with mangling information removed. */
public fun StackTrace.Companion.demangleFunction(function: String): String =
    function.replace(functionMangleRegex, String.EMPTY)

/** The name of the invoked function with mangling information removed. */
public val StackTraceElement.demangledFunction: String?
    get() = StackTrace.demangleFunction(methodName)


/** The [Class] containing the execution point represented by this element. */
public val StackTraceElement.`class`: Class<*> get() = Class.forName(className)

/** The [KClass] containing the execution point represented by this element. */
public val StackTraceElement.kClass: KClass<*> get() = `class`.kotlin


/** The method containing the execution point represented by this element. */
public val StackTraceElement.method: Method get() = `class`.declaredMethods.single { it.name == methodName }

/**
 * Finds the [StackTraceElement] that represents the caller
 * invoking the [StackTraceElement] matching a call to the specified [functions].
 */
public fun StackTrace.findByLastKnownCallsOrNull(vararg functions: Pair): StackTraceElement? {
    var skipInvoke = false
    val demangledFunctions = functions.map { (className, function) -> className to StackTrace.demangleFunction(function) }
    return findOrNull {
        if (demangledFunctions.any { (className, demangledFunction) -> it.className == className && it.demangledFunction == demangledFunction }) {
            skipInvoke = true
            true
        } else {
            if (skipInvoke) it.methodName == "invoke" else false
        }
    }
}

/**
 * Finds the [StackTraceElement] that represents the caller
 * invoking the [StackTraceElement] matching a call to the specified [className] and [function].
 */
public fun StackTrace.findByLastKnownCallsOrNull(className: KClassifier, function: String): StackTraceElement? =
    findByLastKnownCallsOrNull(className.toString() to function)

private fun String.javaBeanMethodToKotlinProperty() = takeUnless {
    length >= 4 && substring(0, 3).let { it == "get" || it == "set" } && this[3].isUpperCase()
} ?: substring(3).replaceFirstChar { it.lowercase(Locale.getDefault()) }

private fun String.simplifyFunction() =
    replace(generatedFunctionRegex, String.EMPTY).javaBeanMethodToKotlinProperty()

/**
 * Finds the [StackTraceElement] that represents the caller
 * invoking the [StackTraceElement] matching a call to the specified [functions].
 */
public fun StackTrace.findByLastKnownCallsOrNull(vararg functions: String): StackTraceElement? {
    var skipInvoke = false
    val simplifiedFunctions = functions.map { StackTrace.demangleFunction(it).simplifyFunction() }

    return findOrNull {
        if (simplifiedFunctions.contains(it.demangledFunction?.simplifyFunction())) {
            skipInvoke = true
            true
        } else {
            if (skipInvoke) it.methodName == "invoke" else false
        }
    }
}

/**
 * Finds the [StackTraceElement] that represents the caller
 * invoking the [StackTraceElement] matching a call to the specified [functions].
 */
public fun StackTrace.findByLastKnownCallsOrNull(vararg functions: KFunction<*>): StackTraceElement? =
    findByLastKnownCallsOrNull(*functions.map { it.name }.toTypedArray())




© 2015 - 2024 Weber Informatics LLC | Privacy Policy