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

commonMain.ExceptionCollector.kt Maven / Gradle / Ivy

There is a newer version: 2.12.3
Show newest version
/*
 * Copyright 2019-2021 Mamoe Technologies and contributors.
 *
 *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
 *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
 *
 *  https://github.com/mamoe/mirai/blob/master/LICENSE
 */

package net.mamoe.mirai.utils

import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public open class ExceptionCollector {

    public constructor()
    public constructor(initial: Throwable?) {
        collect(initial)
    }

    public constructor(vararg initials: Throwable?) {
        for (initial in initials) {
            collect(initial)
        }
    }

    protected open fun beforeCollect(throwable: Throwable) {
    }

    @Volatile
    private var last: Throwable? = null
    private val hashCodes = mutableSetOf()

    /**
     * @return `true` if [e] is new.
     */
    @Synchronized
    public fun collect(e: Throwable?): Boolean {
        if (e == null) return false
        if (!hashCodes.add(hash(e))) return false // filter out duplications
        // we can also check suppressed exceptions of [e] but actual influence would be slight.
        beforeCollect(e)
        this.last?.let { addSuppressed(e, it) }
        this.last = e
        return true
    }

    protected open fun addSuppressed(receiver: Throwable, e: Throwable) {
        receiver.addSuppressed(e)
    }

    private fun hash(e: Throwable): Long {
        return e.stackTrace.fold(0L) { acc, stackTraceElement ->
            acc * 31 + hash(stackTraceElement).toLongUnsigned()
        }
    }

    private fun hash(element: StackTraceElement): Int {
        return element.lineNumber.hashCode() xor element.className.hashCode() xor element.methodName.hashCode()
    }

    public fun collectGet(e: Throwable?): Throwable {
        this.collect(e)
        return getLast()!!
    }

    /**
     * Alias to [collect] to be used inside [withExceptionCollector]
     * @return `true` if [e] is new.
     */
    public fun collectException(e: Throwable?): Boolean = collect(e)

    public fun getLast(): Throwable? = last

    @TerminalOperation // to give it a color for a clearer control flow
    public fun collectThrow(exception: Throwable): Nothing {
        collect(exception)
        throw getLast()!!
    }

    @TerminalOperation
    public fun throwLast(): Nothing {
        throw getLast() ?: error("Internal error: expected at least one exception collected.")
    }

    @DslMarker
    private annotation class TerminalOperation

    @TestOnly // very slow
    public fun asSequence(): Sequence {
        fun Throwable.itr(): Iterator {
            return (sequenceOf(this) + this.suppressed.asSequence().flatMap { it.itr().asSequence() }).iterator()
        }

        val last = getLast() ?: return emptySequence()
        return Sequence { last.itr() }
    }

    @Synchronized
    public fun dispose() { // help gc
        this.last = null
        this.hashCodes.clear()
    }
}

/**
 * Run with a coverage of `throw`. All thrown exceptions will be caught and rethrown with [ExceptionCollector.collectThrow]
 */
public inline fun  withExceptionCollector(action: ExceptionCollector.() -> R): R {
    contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
    return ExceptionCollector().run {
        withExceptionCollector(action).also { dispose() }
    }
}

/**
 * Run with a coverage of `throw`. All thrown exceptions will be caught and rethrown with [ExceptionCollector.collectThrow]
 */
public inline fun  ExceptionCollector.withExceptionCollector(action: ExceptionCollector.() -> R): R {
    contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
    this.run {
        try {
            return action()
        } catch (e: Throwable) {
            collectThrow(e)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy