Commit a37d81c6 authored by Adam Gausmann's avatar Adam Gausmann

Add a periodic hook feature to the plugin API, allow access to registered plugins

parent 708a698d
......@@ -14,6 +14,11 @@ public interface Plugin {
*/
void init() throws Exception;
/**
* Called periodically by the plugin manager, once for every iteration of the bot's main loop.
*/
void run() throws Exception;
/**
* Called when the bot is ready to remove the plugin. A good time to free resources!
*/
......
package ninja.nonemu.samurai.plugin;
import java.util.Map;
/**
* Responsible for loading and managing plugins for the bot.
*/
......@@ -11,7 +13,7 @@ public interface PluginManager {
* @param plugin The plugin to register.
* @param name The name of the plugin.
*/
void registerPlugin(Plugin plugin, String name);
void registerPlugin(Plugin plugin, String name) throws Exception;
/**
* Registers a plugin extended from the plugin base using its given name.
......@@ -19,7 +21,28 @@ public interface PluginManager {
* <br>Equivalent to {@code registerPlugin(plugin, plugin.getId())}.
* @param plugin The plugin to register.
*/
default void registerPlugin(PluginBase plugin) {
default void registerPlugin(PluginBase plugin) throws Exception {
registerPlugin(plugin, plugin.getName());
}
/**
* Calls the named plugin's {@link Plugin#cleanup()} method and removes it from the list.
* @param name The name of the plugin; case-insensitive
*/
void unregisterPlugin(String name) throws Exception;
/**
* Retrieves a registered plugin, given its name.
* @param name The name of the plugin; case-insensitive
* @return The plugin with the given name, or {@code null} if no plugin with that name is registered.
*/
Plugin getPlugin(String name);
/**
* Generates a map of all plugins that are registered at the time of this method's execution.
* <br>The key is a case-insensitive string that denotes the plugin's name, and each corresponding value is that
* plugin's instance.
* @return A map containing all registered Plugin instances.
*/
Map<String, Plugin> getPlugins();
}
package ninja.nonemu.samurai.plugin;
import ninja.nonemu.samurai.BotImpl;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
......@@ -8,8 +11,6 @@ import java.net.URLClassLoader;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarFile;
import ninja.nonemu.samurai.BotImpl;
import org.apache.logging.log4j.Logger;
public class PluginManagerImpl implements PluginManager {
......@@ -34,6 +35,7 @@ public class PluginManagerImpl implements PluginManager {
if (files != null) {
for (File file : files) {
PluginBase plugin = null;
try {
JarFile jarFile = new JarFile(file);
String className = jarFile.getManifest().getMainAttributes().getValue("Plugin-Class");
......@@ -47,8 +49,7 @@ public class PluginManagerImpl implements PluginManager {
}
PluginBase.setDefaultBot(bot);
PluginBase plugin = (PluginBase) pluginClass.getConstructor().newInstance();
registerPlugin(plugin);
plugin = (PluginBase) pluginClass.getConstructor().newInstance();
} catch (IOException e) {
logger.error("I/O error while loading plugin. Check to see that your manifest file exists.", e);
......@@ -63,10 +64,18 @@ public class PluginManagerImpl implements PluginManager {
} catch (IllegalAccessException e) {
logger.error("Cannot load plugin. Make sure the bot can access the plugin class.", e);
}
if (plugin != null) {
try {
registerPlugin(plugin);
} catch (Exception e) {
logger.error("Plugin threw an exception while initializing", e);
}
}
}
}
} else if (pluginDir.exists()) {
logger.error("Plugin directory is not a directory. Please move that file and restart the bot.");
logger.error("Plugin directory `./plugins' is not a directory. Please move that file and restart the bot.");
System.exit(1);
} else {
pluginDir.mkdir();
......@@ -74,30 +83,75 @@ public class PluginManagerImpl implements PluginManager {
}
public void run() {
plugins.values().forEach((plugin) -> {
try {
plugin.run();
} catch (Exception e) {
logger.error("Plugin threw an exception while running", e);
}
});
}
public void cleanup() {
logger.trace("Cleaning up plugin manager");
plugins.keySet().iterator().forEachRemaining((name) -> {
try {
unregisterPlugin(name);
} catch (Exception e) {
logger.error("Plugin threw an exception while cleaning up", e);
}
});
}
@Override
public void registerPlugin(Plugin plugin, String name) {
public void registerPlugin(Plugin plugin, String name) throws Exception {
logger.info("Registering plugin {}", name);
if (plugins.containsKey(name)) {
throw new IllegalStateException("A plugin is already registered with that name");
logger.warn("A plugin is already registered as {}; aborting", name);
return;
}
if (plugins.containsValue(plugin)) {
logger.warn("That instance of Plugin ({}) is already registered; aborting", plugin);
return;
}
logger.info("Registering plugin {}", plugin);
plugins.put(name, plugin);
try {
plugin.init();
} catch (Exception e) {
logger.error("Unable to initialize plugin", e);
plugins.remove(name);
throw e;
}
}
@Override
public void unregisterPlugin(String name) throws Exception {
logger.info("Unregistering plugin {}", name);
if (!plugins.containsKey(name)) {
logger.warn("No Plugin is registered as {}; aborting", name);
}
Plugin plugin = plugins.get(name);
try {
plugin.cleanup();
} finally {
plugins.remove(name);
}
}
@Override
public Plugin getPlugin(String name) {
return plugins.get(name);
}
@Override
public Map<String, Plugin> getPlugins() {
Map<String, Plugin> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
result.putAll(plugins);
return result;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment