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

net.bjoernpetersen.musicbot.api.plugin.management.DefaultDependencyManager.kt Maven / Gradle / Ivy

There is a newer version: 0.25.0
Show newest version
package net.bjoernpetersen.musicbot.api.plugin.management

import com.google.common.collect.MultimapBuilder
import kotlin.reflect.KClass
import mu.KotlinLogging
import net.bjoernpetersen.musicbot.api.config.ChoiceBox
import net.bjoernpetersen.musicbot.api.config.Config
import net.bjoernpetersen.musicbot.api.config.ConfigSerializer
import net.bjoernpetersen.musicbot.api.config.DeserializationException
import net.bjoernpetersen.musicbot.api.config.MainConfigScope
import net.bjoernpetersen.musicbot.api.config.NonnullConfigChecker
import net.bjoernpetersen.musicbot.api.config.UiNode
import net.bjoernpetersen.musicbot.api.plugin.DeclarationException
import net.bjoernpetersen.musicbot.api.plugin.PluginId
import net.bjoernpetersen.musicbot.api.plugin.PluginLoader
import net.bjoernpetersen.musicbot.api.plugin.bases
import net.bjoernpetersen.musicbot.api.plugin.id
import net.bjoernpetersen.musicbot.spi.plugin.GenericPlugin
import net.bjoernpetersen.musicbot.spi.plugin.PlaybackFactory
import net.bjoernpetersen.musicbot.spi.plugin.Plugin
import net.bjoernpetersen.musicbot.spi.plugin.Provider
import net.bjoernpetersen.musicbot.spi.plugin.Suggester
import net.bjoernpetersen.musicbot.spi.plugin.management.DependencyConfigurationException
import net.bjoernpetersen.musicbot.spi.plugin.management.DependencyManager

/**
 * Default implementation of the [DependencyManager] interface.
 *
 * @param plain a plain config (usually from the [main config scope][MainConfigScope])
 * @param genericPlugins a list of all available generic plugins
 * @param playbackFactories a list of all available playback factories
 * @param providers a list of all available providers
 * @param suggesters a list of all available suggesters
 */
class DefaultDependencyManager(
    plain: Config,
    override val genericPlugins: List,
    override val playbackFactories: List,
    override val providers: List,
    override val suggesters: List
) : DependencyManager {

    private val logger = KotlinLogging.logger { }

    private val basesByPlugin: Map>> = allPlugins
        .associateWith { it.bases.toSet() }
    private val allBases: Set> = basesByPlugin.values.flatten().toSet()

    @Suppress("UnstableApiUsage")
    private val pluginsByBase = MultimapBuilder.SetMultimapBuilder
        .hashKeys()
        .hashSetValues()
        .build, Plugin>().apply {
            for (entry in basesByPlugin) {
                entry.value.forEach {
                    put(it, entry.key)
                }
            }
        }

    private val pluginSerializer = object : ConfigSerializer {
        override fun serialize(obj: Plugin): String = obj::class.qualifiedName!!

        override fun deserialize(string: String): Plugin = allPlugins
            .firstOrNull { it::class.qualifiedName == string } ?: throw DeserializationException()
    }

    private val defaultByBase: Map, Config.SerializedEntry> =
        allBases.associateWith { plain.defaultEntry(it) }

    /**
     * Convenience constructor which loads all plugins using the specified PluginLoader.
     *
     * @param plain the [MainConfigScope] plain config
     * @param loader a plugin loader
     */
    constructor(plain: Config, loader: PluginLoader) : this(plain, loadPlugins(loader))

    private constructor(plain: Config, plugins: Plugins) : this(
        plain,
        plugins.generic,
        plugins.playbackFactories,
        plugins.providers,
        plugins.suggesters
    )

    override fun getDefaults(plugin: Plugin): Sequence> {
        return basesByPlugin[plugin]?.asSequence()?.filter { defaultByBase[it]?.get() == plugin }
            ?: throw IllegalStateException()
    }

    override fun  getDefault(base: KClass): B? {
        @Suppress("UNCHECKED_CAST")
        return defaultByBase[base]?.get() as B?
    }

    override fun isDefault(plugin: Plugin, base: KClass<*>): Boolean {
        return defaultByBase[base]?.get() == plugin
    }

    override fun setDefault(plugin: Plugin?, base: KClass<*>) {
        defaultByBase[base]?.set(plugin) ?: logger.warn { "Tried to set default on unknown base" }
    }

    override fun findAvailable(base: KClass<*>): List {
        return pluginsByBase[base].toList()
    }

    @Throws(DependencyConfigurationException::class)
    override fun finish(
        providerOrder: List,
        suggesterOrder: List
    ): PluginFinder {
        val genericPlugins: List = findEnabledGeneric()
        val playbackFactories: List = findEnabledPlaybackFactory()
        val providers: List = findEnabledProvider().sortedByOrder(providerOrder)
        val suggesters: List = findEnabledSuggester().sortedByOrder(suggesterOrder)

        val defaultByBase = findEnabledDependencies()
            .associateWithTo(HashMap()) { base ->
                val plugin = try {
                    getDefault(base)
                } catch (e: DeserializationException) {
                    null
                } ?: throw DependencyConfigurationException("No default: ${base.qualifiedName}")
                plugin
            }

        sequenceOf(genericPlugins, playbackFactories, providers, suggesters)
            .flatMap { it.asSequence() }
            .forEach {
                try {
                    defaultByBase[it.id.type] = it
                } catch (e: DeclarationException) {
                    // ignore
                }
            }

        return PluginFinder(defaultByBase, genericPlugins, playbackFactories, providers, suggesters)
    }

    private fun Config.defaultEntry(base: KClass): Config.SerializedEntry {
        fun defaultEntryUi(): UiNode {
            return ChoiceBox(
                { it.name },
                { pluginsByBase[base].toList() })
        }

        val key = "${base.qualifiedName!!}.default"

        return SerializedEntry(
            key,
            "",
            pluginSerializer,
            NonnullConfigChecker,
            defaultEntryUi()
        )
    }

    private companion object {
        /**
         * Loads plugins of all types using the specified loader.
         *
         * @param loader a plugin loader
         * @return all loaded plugins
         */
        fun loadPlugins(loader: PluginLoader): Plugins {
            return Plugins(
                generic = loader.load(GenericPlugin::class).toList(),
                playbackFactories = loader.load(PlaybackFactory::class).toList(),
                providers = loader.load(Provider::class).toList(),
                suggesters = loader.load(Suggester::class).toList()
            )
        }
    }
}

/**
 * A data class containing all plugins which have been loaded.
 *
 * @param generic all available generic plugins
 * @param playbackFactories all available playback factories
 * @param providers all available providers
 * @param suggesters all available suggesters
 */
data class Plugins(
    val generic: List,
    val playbackFactories: List,
    val providers: List,
    val suggesters: List
)

private fun  List.sortedByOrder(order: List): List {
    return sortedBy { order.indexOf(it.id) }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy