io.github.freya022.botcommands.internal.utils.InternalUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of BotCommands Show documentation
Show all versions of BotCommands Show documentation
A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.
package io.github.freya022.botcommands.internal.utils
import io.github.freya022.botcommands.api.commands.CommandPath
import io.github.freya022.botcommands.api.commands.INamedCommand
import kotlinx.coroutines.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import net.dv8tion.jda.api.entities.Guild
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.time.Duration
internal fun String.toDiscordString(): String {
val sb: StringBuilder = StringBuilder()
for (c in this) {
if (c.isUpperCase()) {
sb.append('_').append(c.lowercaseChar())
} else {
sb.append(c)
}
}
return sb.toString()
}
internal fun INamedCommand.lazyPath(): Lazy = lazy {
val components = mutableListOf()
var info = this
do {
components.add(index = 0, info.name)
info = info.parentInstance ?: break
} while (true)
CommandPath.of(components)
}
internal fun Guild?.asScopeString() = if (this == null) "global scope" else "guild '${this.name}' (${this.id})"
@OptIn(ExperimentalContracts::class)
internal inline fun downcast(obj: Any): T {
contract {
returns() implies (obj is T)
}
if (obj as? T == null) {
throwInternal("${obj::class.simpleName} should implement ${T::class.simpleName}")
}
return obj
}
internal fun MutableMap.putIfAbsentOrThrowInternal(key: K, value: V) {
if (key in this)
throwInternal("Key '$key' is already present in the map")
this[key] = value
}
internal inline fun MutableMap.putIfAbsentOrThrow(key: K, value: V, messageSupplier: (value: V) -> String) {
val existingValue = this[key]
if(existingValue != null) throw IllegalStateException(messageSupplier(existingValue))
this[key] = value
}
internal inline fun CoroutineScope.launchCatching(
crossinline catchBlock: suspend CoroutineScope.(Throwable) -> Unit,
crossinline block: suspend CoroutineScope.() -> Unit
): Job = launch {
runCatching(catchBlock, block)
}
internal inline fun CoroutineScope.launchCatchingDelayed(
delay: Duration,
crossinline catchBlock: suspend CoroutineScope.(Throwable) -> Unit,
crossinline block: suspend CoroutineScope.() -> Unit
): Job = launch {
delay(delay)
runCatching(catchBlock, block)
}
private suspend inline fun CoroutineScope.runCatching(
crossinline catchBlock: suspend (CoroutineScope, Throwable) -> Unit,
crossinline block: suspend (CoroutineScope) -> Unit
) {
try {
block(this)
} catch (e: CancellationException) {
// Pass cancellation exceptions back,
// at worst JobSupport#cancelParent makes the exception ignored
throw e
} catch (e: Throwable) {
catchBlock(this, e)
}
}
internal fun Duration.toTimestampIfFinite(): Instant? =
takeIfFinite()?.let { Clock.System.now() + it }
internal fun Duration.takeIfFinite(): Duration? =
takeIf { it.isFinite() && it.isPositive() }
internal inline fun T?.ifNullThrowInternal(message: () -> String): T {
if (this == null)
throwInternal(message())
return this
}
internal class WriteOnce(private val wait: Boolean) : ReadWriteProperty {
private val lock = ReentrantLock()
private val condition = lock.newCondition()
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T = lock.withLock {
val value = value
if (value != null) return value
if (wait)
condition.await()
else
throwState("Property ${property.name} must be initialized before getting it.")
return getValue(thisRef, property)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = lock.withLock {
check(this.value == null) {
"Cannot set value twice"
}
this.value = value
condition.signalAll()
}
internal fun isInitialized(): Boolean = value != null
}