Skip to content

Commit

Permalink
Merge pull request #8 from Devoxin/feature/load-from-external
Browse files Browse the repository at this point in the history
External Jar Scanning
  • Loading branch information
devoxin authored Nov 24, 2019
2 parents a396090 + dfb53c9 commit 3e73aa8
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/me/devoxin/flight/FlightInfo.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package me.devoxin.flight

object FlightInfo {
val VERSION = "1.1.1"
val VERSION = "1.2.0"
}
43 changes: 20 additions & 23 deletions src/main/kotlin/me/devoxin/flight/api/CommandClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.coroutines.launch
import me.devoxin.flight.arguments.ArgParser
import me.devoxin.flight.exceptions.BadArgument
import me.devoxin.flight.exceptions.AwaitTimeoutException
import me.devoxin.flight.internal.CommandRegistry
import me.devoxin.flight.internal.DefaultHelpCommand
import me.devoxin.flight.internal.WaitingEvent
import me.devoxin.flight.models.Cog
Expand All @@ -20,7 +21,11 @@ import net.dv8tion.jda.api.events.GenericEvent
import net.dv8tion.jda.api.events.ReadyEvent
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import org.reflections.Reflections
import org.slf4j.LoggerFactory
import java.io.File
import java.net.URL
import java.net.URLClassLoader
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
Expand All @@ -35,8 +40,8 @@ class CommandClient(

private val waiterScheduler = Executors.newSingleThreadScheduledExecutor()
private val pendingEvents = hashMapOf<Class<*>, HashSet<WaitingEvent<*>>>()
val commands = hashMapOf<String, CommandWrapper>()
var ownerIds: MutableSet<Long> = customOwnerIds ?: mutableSetOf()
val commands = CommandRegistry()
val ownerIds = customOwnerIds ?: mutableSetOf()

init {
ArgParser.parsers.putAll(parsers)
Expand All @@ -53,36 +58,28 @@ class CommandClient(
* @param packageName
* The package name to look for commands in.
*/
fun registerCommands(packageName: String) {
val indexer = Indexer(packageName)
val cogs = indexer.getCogs()

for (cogClass in cogs) {
val cog = cogClass.getDeclaredConstructor().newInstance()
registerCommands(cog, indexer)
}

logger.info("Successfully loaded ${commands.size} commands")
}
fun registerCommands(packageName: String) = commands.registerCommands(packageName)

/**
* Registers all commands in the given class
* Registers all commands in the given class.
*
* @param cog
* The cog to load commands from.
* @param indexer
* The indexer to use. This can be omitted, but it's better to reuse an indexer if possible.
*/
fun registerCommands(cog: Cog, indexer: Indexer? = null) {
val i = indexer ?: Indexer(cog::class.java.`package`.name)

val commands = i.getCommands(cog)
fun registerCommands(cog: Cog, indexer: Indexer? = null) = commands.registerCommands(cog, indexer)

for (command in commands) {
val cmd = i.loadCommand(command, cog)
this.commands[cmd.name] = cmd
}
}
/**
* Registers all commands in a jar file.
*
* @param jarPath
* A string-representation of the path to the jar file.
*
* @param packageName
* The package name to scan for cogs/commands in.
*/
fun registerCommands(jarPath: String, packageName: String) = commands.registerCommands(jarPath, packageName)


// +------------------+
Expand Down
12 changes: 5 additions & 7 deletions src/main/kotlin/me/devoxin/flight/api/CommandClientBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ class CommandClientBuilder {
private var parsers = hashMapOf<Class<*>, Parser<*>>()
private var prefixes: List<String> = emptyList()
private var allowMentionPrefix: Boolean = true
private var useDefaultHelpCommand: Boolean = true
private var showParameterTypes: Boolean = false
private var helpCommandConfig: DefaultHelpCommandConfig = DefaultHelpCommandConfig()
private var ignoreBots: Boolean = true
private var prefixProvider: PrefixProvider? = null
private var eventListeners: MutableList<CommandClientAdapter> = mutableListOf()
Expand Down Expand Up @@ -67,9 +66,8 @@ class CommandClientBuilder {
*
* @return The builder instance. Useful for chaining.
*/
fun useDefaultHelpCommand(useDefaultHelpCommand: Boolean, showParameterTypes: Boolean = false): CommandClientBuilder {
this.useDefaultHelpCommand = useDefaultHelpCommand
this.showParameterTypes = showParameterTypes
fun configureDefaultHelpCommand(config: DefaultHelpCommandConfig.() -> Unit): CommandClientBuilder {
config(helpCommandConfig)
return this
}

Expand Down Expand Up @@ -158,8 +156,8 @@ class CommandClientBuilder {
val prefixProvider = this.prefixProvider ?: DefaultPrefixProvider(prefixes, allowMentionPrefix)
val commandClient = CommandClient(parsers, prefixProvider, ignoreBots, eventListeners.toList(), ownerIds)

if (useDefaultHelpCommand) {
commandClient.registerCommands(DefaultHelpCommand(showParameterTypes))
if (helpCommandConfig.enabled) {
commandClient.registerCommands(DefaultHelpCommand(helpCommandConfig.showParameterTypes))
}

return commandClient
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.devoxin.flight.api

data class DefaultHelpCommandConfig(
var enabled: Boolean = true,
var showParameterTypes: Boolean = false
)
10 changes: 8 additions & 2 deletions src/main/kotlin/me/devoxin/flight/arguments/ArgParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ class ArgParser(
private val delimiter: Char
) {

private var args = commandArgs.toMutableList()
private var args = commandArgs.toList()

private fun getArgs(amount: Int): List<String> {
if (args.isEmpty()) {
return emptyList()
}

val taken = args.take(amount)
args.drop(amount)
args = args.drop(amount) // I don't like re-assignment, so @todo figure out why .removeAt didn't work.

/*
for (i in 0 until amount) {
args.removeAt(0)
}
*/

return taken
}
Expand Down
79 changes: 79 additions & 0 deletions src/main/kotlin/me/devoxin/flight/internal/CommandRegistry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package me.devoxin.flight.internal

import me.devoxin.flight.api.CommandWrapper
import me.devoxin.flight.models.Cog
import me.devoxin.flight.utils.Indexer

class CommandRegistry : HashMap<String, CommandWrapper>() {

fun findCommandByName(name: String): CommandWrapper? {
return this[name]
}

fun findCommandByAlias(alias: String): CommandWrapper? {
return this.values.firstOrNull { it.properties.aliases.contains(alias) }
}

fun removeByCog(cogName: String, ignoreCase: Boolean = true) {
this.values.removeIf {
it.cog.name().equals(cogName, ignoreCase)
}
}

fun registerCommands(cog: Cog, indexer: Indexer? = null) {
val i = indexer ?: Indexer(cog::class.java.`package`.name)
val commands = i.getCommands(cog)

for (command in commands) {
val cmd = i.loadCommand(command, cog)
this[cmd.name] = cmd
}
}

fun registerCommands(packageName: String) {
val indexer = Indexer(packageName)
val cogs = indexer.getCogs()

for (cogClass in cogs) {
val cog = cogClass.getDeclaredConstructor().newInstance()
registerCommands(cog, indexer)
}
}

fun registerCommands(jarPath: String, packageName: String) {
Indexer(packageName, jarPath).use {
val cogClasses = it.getCogs()

for (cls in cogClasses) {
val cog = cls.getDeclaredConstructor().newInstance()
registerCommands(cog, it)
}
}
}

/**
* Attempts to load the jar at the given path. If successful,
* Flight will attempt to discover all cogs in the jar, under the given package name.
*
* Before registering the cogs, any existing cogs whose names match those found in the jar will
* automatically be unregistered and removed.
*
* @param jarPath
* A string-representation of the path to the jar file.
*
* @param packageName
* The package name to scan for cogs/commands in.
*/
fun reload(jarPath: String, packageName: String) {Indexer(packageName, jarPath).use {
val cogClasses = it.getCogs()

for (cls in cogClasses) {
val cog = cls.getDeclaredConstructor().newInstance()
removeByCog(cog.name())
registerCommands(cog, it)
}
}

}

}
37 changes: 34 additions & 3 deletions src/main/kotlin/me/devoxin/flight/utils/Indexer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,40 @@ import org.reflections.Reflections
import org.reflections.scanners.MethodParameterNamesScanner
import org.reflections.scanners.SubTypesScanner
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.io.File
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.net.URL
import java.net.URLClassLoader
import kotlin.coroutines.Continuation

class Indexer(private val pkg: String) {
class Indexer : Closeable {

private val reflections = Reflections(pkg, MethodParameterNamesScanner(), SubTypesScanner())
private val packageName: String
private val reflections: Reflections
private val classLoader: URLClassLoader?

constructor(packageName: String) {
this.packageName = packageName
this.classLoader = null
reflections = Reflections(packageName, MethodParameterNamesScanner(), SubTypesScanner())
}

constructor(packageName: String, jarPath: String) {
this.packageName = packageName

val commandJar = File(jarPath)
check(commandJar.exists()) { "jarPath points to a non-existent file." }
check(commandJar.extension == "jar") { "jarPath leads to a file which is not a jar." }

val path = URL("jar:file:${commandJar.absolutePath}!/")
this.classLoader = URLClassLoader.newInstance(arrayOf(path))
reflections = Reflections(packageName, this.classLoader, MethodParameterNamesScanner(), SubTypesScanner())
}

fun getCogs(): List<Class<out Cog>> {
logger.debug("Scanning $pkg for cogs...")
logger.debug("Scanning $packageName for cogs...")
val cogs = reflections.getSubTypesOf(Cog::class.java)
logger.debug("Found ${cogs.size} cogs")

Expand Down Expand Up @@ -81,6 +105,13 @@ class Indexer(private val pkg: String) {
return reflections.getMethodParamNames(meth)
}

override fun close() {
this.classLoader?.close()
// classLoader must be closed otherwise external jar files will remain open
// which kinda defeats the purpose of reloadable commands.
// This is only a problem if loading from jar files anyway.
}

companion object {
private val logger = LoggerFactory.getLogger(Indexer::class.java)
}
Expand Down

0 comments on commit 3e73aa8

Please sign in to comment.