Skip to content

util.kt

src/jvmMain/kotlin/afield/app/diagnostics/util.kt · 313 lines

package afield.app.diagnostics

import afield.app.server.app
import afield.data.table.div
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with

import afield.app.protocol.computeIfAbsent
import afield.audio.synth.*
import afield.data.table.get
import afield.document.graph.Box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.lifecycle.map
import afield.ui.output.templates.filter
import afield.app.settings.PersistOnExit.needingPersistance
import afield.document.persistence.SavingContext.Companion.format
import afield.app.diagnostics.logging.lprintln
import afield.data.collections.invoke
import afield.math.linalg.invoke
import java.io.File
import java.lang.reflect.*
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.concurrent.thread
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

var timingInfo = ConcurrentHashMap<String, LinkedList<Long>>()

var disableTiming = true

var max_window = 100

private val tDP by lazy {
    DecimalFormat("#,##0.##", DecimalFormatSymbols(Locale.US).apply {
        groupingSeparator = ','
        decimalSeparator = '.'
    })
}

fun Number.tDP(): String = tDP.format(this)

inline fun <T> logTime(name: String, max_window: Int = afield.app.diagnostics.max_window, block: () -> T): T {

inline fun <T> logTime(name: String, max_window: Int = afield.app.diagnostics.max_window, block: () -> T): T {
    if (disableTiming) {
        return block.invoke()
    }
    var a = System.nanoTime()
    var r = block()
    var b = System.nanoTime()
    timingInfo.computeIfAbsent(name) { LinkedList<Long>() }.windowedAppendAndPrint(name, max_window, b - a)
    return r
}

suspend fun <T> logTime_(name: String, max_window: Int = afield.app.diagnostics.max_window, block: suspend () -> T): T {

suspend fun <T> logTime_(name: String, max_window: Int = afield.app.diagnostics.max_window, block: suspend () -> T): T {
    if (disableTiming) {
        return block.invoke()
    }
    var a = System.nanoTime()
    var r = block()
    var b = System.nanoTime()
    timingInfo.computeIfAbsent(name) { LinkedList<Long>() }.windowedAppendAndPrint(name, max_window, b - a)
    return r
}

fun LinkedList<Long>.windowedAppendAndPrint(name: String, max_window: Int, data: Long)

fun LinkedList<Long>.windowedAppendAndPrint(name: String, max_window: Int, data: Long) {
    add(data)
    if (this.size > max_window * 2) {
        max_window { this.removeFirst() }
        var data = ArrayList<Long>(this)
        data.sort()
        var total = data.sum()
        lprintln(
            "info,perf",
            ">>time '$name' = [${data.first().tDP()} | ${data[data.size / 2].tDP()}, ${(total / data.size.toDouble()).tDP()} | ${data.last().tDP()}]"
        )
    }
}

data class Named<T>(val name: String, val value: T)

infix fun <T> T.named(name: String) = Named(name, this)

fun <T> T.namedHere(name: String = "${this!!.javaClass.name}", prefix : String = "${Box.context.get()}")

fun <T> T.namedHere(name: String = "${this!!.javaClass.name}", prefix : String = "${Box.context.get()}") = Named(prefix+"_"+name, this)

fun Any?.reflectionDump

Dump an object's class hierarchy (superclasses + interfaces) and print: - declared fields + their values - declared methods + their signatures

Notes: - "Declared" means exactly what's declared on that type (not inherited). - Values are read reflectively; may fail for some fields due to security/module access. - Reading a field value can throw, and calling toString() can have side-effects; both are guarded.

body — 63 lines, from line 104
fun Any?.reflectionDump(
    includeStatic: Boolean = false,
    includeSynthetic: Boolean = false,
    showInterfaces: Boolean = true
) {
    if (this == null) {
        println("<<null>>")
        return
    }

    val rootClass = this.javaClass
    println("=== Reflection dump for instance: ${safeToString(this)}")
    println("=== Runtime class: ${rootClass.name}")
    println()

    val visitedTypes = LinkedHashSet<Class<*>>()
    for (type in rootClass.walkTypes(showInterfaces)) {
        if (!visitedTypes.add(type)) continue

        println("----- TYPE: ${type.name}")
        type.superclass?.let { println("  superclass: ${it.name}") }
        if (showInterfaces) {
            val ifaces = type.interfaces.map { it.name }
            println("  interfaces: ${ifaces.ifEmpty { listOf("<none>") }.joinToString()}")
        }

        println("  [FIELDS]")
        val fields = type.declaredFields
            .asSequence()
            .filter { includeSynthetic || !it.isSynthetic }
            .sortedBy { it.name }
            .toList()

        if (fields.isEmpty()) {
            println("    <none>")
        } else {
            for (f in fields) {
                val isStatic = Modifier.isStatic(f.modifiers)
                if (!includeStatic && isStatic) continue
                dumpField(this, f)
            }
        }

        println("  [METHODS]")
        val methods = type.declaredMethods
            .asSequence()
            .filter { includeSynthetic || !it.isSynthetic }
            .sortedWith(compareBy<Method>({ it.name }, { it.parameterCount }))
            .toList()

        if (methods.isEmpty()) {
            println("    <none>")
        } else {
            for (m in methods) {
                val isStatic = Modifier.isStatic(m.modifiers)
                if (!includeStatic && isStatic) continue
                println("    ${m.toGenericString()}")
            }
        }

        println()
    }
}

fun showMethodNames(a : Any)

fun showMethodNames(a : Any) {
    MethodParameterSpy.printClassMethods(a.javaClass)
}

class MethodParameterSpy

class MethodParameterSpy {

fun <E : RuntimeException?> genericThrow()

for the morbidly curious

    fun <E : RuntimeException?> genericThrow() {
    }

companion object

    companion object {
        private const val fmt = "%24s: %s%n"

fun printClassConstructors(c: Class<*>)

        fun printClassConstructors(c: Class<*>) {
            val allConstructors: Array<Constructor<*>> = c.getConstructors()
            System.out.format(fmt, "Number of constructors", allConstructors.size)
            for (currentConstructor in allConstructors) {
                printConstructor(currentConstructor)
            }
            val allDeclConst: Array<Constructor<*>> = c.getDeclaredConstructors()
            System.out.format(
                fmt, "Number of declared constructors",
                allDeclConst.size
            )
            for (currentDeclConst in allDeclConst) {
                printConstructor(currentDeclConst)
            }
        }

fun printClassMethods(c: Class<*>)

        fun printClassMethods(c: Class<*>) {
            val allMethods = c.getDeclaredMethods()
            System.out.format(fmt, "Number of methods", allMethods.size)
            for (m in allMethods) {
                printMethod(m)
            }
        }

fun printConstructor(c: Constructor<*>)

        fun printConstructor(c: Constructor<*>) {
            System.out.format("%s%n", c.toGenericString())
            val params: Array<Parameter?> = c.getParameters()
            System.out.format(fmt, "Number of parameters", params.size)
            for (i in params.indices) {
                printParameter(params[i]!!)
            }
        }

fun printMethod(m: Method)

        fun printMethod(m: Method) {
            System.out.format("%s%n", m.toGenericString())
            System.out.format(fmt, "Return type", m.getReturnType())
            System.out.format(fmt, "Generic return type", m.getGenericReturnType())

            val params: Array<Parameter?> = m.getParameters()
            for (i in params.indices) {
                printParameter(params[i]!!)
            }
        }

fun printParameter(p: Parameter)

        fun printParameter(p: Parameter) {
            System.out.format(fmt, "Parameter class", p.getType())
            System.out.format(fmt, "Parameter name", p.getName())
            System.out.format(fmt, "Modifiers", p.getModifiers())
            System.out.format(fmt, "Is implicit?", p.isImplicit())
            System.out.format(fmt, "Is name present?", p.isNamePresent())
            System.out.format(fmt, "Is synthetic?", p.isSynthetic())
        }

fun main(args: Array<String>)

        @JvmStatic
        fun main(args: Array<String>) {
            try {
                printClassConstructors(Class.forName(args[0]))
                printClassMethods(Class.forName(args[0]))
            } catch (x: ClassNotFoundException) {
                x.printStackTrace()
            }
        }
    }
}

Walk class hierarchy: - current class, its superclasses - (optionally) interfaces at each level, including their parent interfaces

De-duplicated by caller.

source — 61 lines, from line 252
private fun Class<*>.walkTypes(includeInterfaces: Boolean): Sequence<Class<*>> = sequence {
    var c: Class<*>? = this@walkTypes
    while (c != null) {
        yield(c)

        if (includeInterfaces) {
            // BFS over interface graph for this class level
            val queue = ArrayDeque<Class<*>>(c.interfaces.toList())
            val seen = HashSet<Class<*>>()
            while (queue.isNotEmpty()) {
                val i = queue.removeFirst()
                if (seen.add(i)) {
                    yield(i)
                    i.interfaces.forEach(queue::addLast)
                }
            }
        }

        c = c.superclass
    }
}

private fun dumpField(instance: Any, f: Field) {
    val mods = Modifier.toString(f.modifiers).ifBlank { "package-private" }
    val typeName = f.genericType.typeName
    val name = f.name

    val valueStr = runCatching {
        f.tryMakeAccessible()
        val v = f.get(instance)
        safeToString(v)
    }.getOrElse { e ->
        "<unreadable: ${e::class.java.simpleName}: ${e.message}>"
    }

    println("    $mods $typeName $name = $valueStr")
}

private fun Field.tryMakeAccessible() {
    // Works on many JVMs; on newer Java with modules, this can still fail (caught by caller).
    runCatching { isAccessible = true }
    runCatching { trySetAccessible() }
}

private fun safeToString(v: Any?): String = when (v) {
    null -> "null"
    is String -> "\"$v\""
    is Char -> "'$v'"
    is Boolean, is Number -> v.toString()
    is Array<*> -> v.contentDeepToString()
    is ByteArray -> v.contentToString()
    is ShortArray -> v.contentToString()
    is IntArray -> v.contentToString()
    is LongArray -> v.contentToString()
    is FloatArray -> v.contentToString()
    is DoubleArray -> v.contentToString()
    is CharArray -> v.contentToString()
    is BooleanArray -> v.contentToString()
    else -> runCatching { v.toString() }
        .getOrElse { "<toString failed: ${it::class.java.simpleName}: ${it.message}>" }
}