summaryrefslogtreecommitdiff
path: root/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java')
-rw-r--r--src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java752
1 files changed, 752 insertions, 0 deletions
diff --git a/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java b/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java
new file mode 100644
index 0000000..3886dd1
--- /dev/null
+++ b/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.java
@@ -0,0 +1,752 @@
+package com.redstoner.coremods.moduleLoader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import com.nemez.cmdmgr.Command;
+import com.nemez.cmdmgr.Command.AsyncType;
+import com.nemez.cmdmgr.CommandManager;
+import com.redstoner.annotations.AutoRegisterListener;
+import com.redstoner.annotations.Commands;
+import com.redstoner.annotations.Version;
+import com.redstoner.logging.PrivateLogManager;
+import com.redstoner.misc.Main;
+import com.redstoner.misc.ModuleInfo;
+import com.redstoner.misc.VersionHelper;
+import com.redstoner.modules.CoreModule;
+import com.redstoner.modules.Module;
+import com.redstoner.modules.ModuleLogger;
+
+import net.nemez.chatapi.click.Message;
+
+/** The module loader, mother of all modules. Responsible for loading and taking care of all modules.
+ *
+ * @author Pepich */
+@Version(major = 5, minor = 2, revision = 0, compatible = 5)
+public final class ModuleLoader implements CoreModule
+{
+ private static ModuleLoader instance;
+ private static final HashMap<Module, Boolean> modules = new HashMap<>();
+ private static HashMap<Module, ModuleInfo> moduleInfos = new HashMap<>();
+ private static HashMap<String, List<Module>> categorizes = new HashMap<>();
+ private static URL[] urls;
+ private static URLClassLoader mainLoader;
+ private static HashMap<Module, URLClassLoader> loaders = new HashMap<>();
+ private static File configFile;
+ private static FileConfiguration config;
+ private static boolean debugMode = false;
+ private static HashMap<Module, ModuleLogger> loggers = new HashMap<>();
+
+ private ModuleLoader()
+ {
+ try
+ {
+ config = Main.plugin.getConfig();
+ configFile = new File(Main.plugin.getDataFolder(), "config.yml");
+ urls = new URL[] {(new File(Main.plugin.getDataFolder(), "classes")).toURI().toURL()};
+ mainLoader = new URLClassLoader(urls, this.getClass().getClassLoader());
+ }
+ catch (MalformedURLException e)
+ {
+ System.out.println("Sumtin is wong with ya filesüstem m8. Fix eeeet or I won't werk!");
+ Bukkit.getPluginManager().disablePlugin(Main.plugin);
+ }
+ }
+
+ public static void init()
+ {
+ if (instance == null)
+ instance = new ModuleLoader();
+ ModuleInfo info = new ModuleInfo(ModuleLoader.class.getResourceAsStream("module.info"), instance);
+ moduleInfos.put(instance, info);
+ loggers.put(instance, new ModuleLogger(info.getDisplayName()));
+ CommandManager.registerCommand(ModuleLoader.class.getResourceAsStream("ModuleLoader.cmd"), instance,
+ Main.plugin);
+ }
+
+ public static final void loadFromConfig()
+ {
+ try
+ {
+ if (!configFile.exists())
+ {
+ configFile.getParentFile().mkdirs();
+ configFile.createNewFile();
+ }
+ config.load(configFile);
+ }
+ catch (FileNotFoundException e)
+ {}
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (InvalidConfigurationException e)
+ {
+ configFile.delete();
+ try
+ {
+ configFile.createNewFile();
+ }
+ catch (IOException e1)
+ {
+ e1.printStackTrace();
+ }
+ instance.getLogger().error("Invalid config file! Creating new, blank file!");
+ }
+ List<String> coremods = config.getStringList("coremods");
+ if (coremods == null || coremods.isEmpty())
+ {
+ config.set("coremods", new String[] {"# Add the coremodules here!"});
+ Main.plugin.saveConfig();
+ try
+ {
+ config.save(configFile);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ List<String> autoload = config.getStringList("autoload");
+ if (autoload == null || autoload.isEmpty())
+ {
+ config.set("autoload", new String[] {"# Add the modules here!"});
+ Main.plugin.saveConfig();
+ try
+ {
+ config.save(configFile);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ if (!config.contains("debugMode"))
+ {
+ config.set("debugMode", false);
+ try
+ {
+ config.save(configFile);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ debugMode = config.getBoolean("debugMode");
+ for (String s : coremods)
+ if (!s.startsWith("#"))
+ if (!ModuleLoader.addDynamicModule(s))
+ {
+ instance.getLogger().error("Couldn't autocomplete path for module name: " + s
+ + "! If you're on a case sensitive filesystem, please take note that case correction does not work. Make sure that the classname has proper capitalisation.");
+
+ }
+ for (String s : autoload)
+ if (!s.startsWith("#"))
+ if (!ModuleLoader.addDynamicModule(s))
+ {
+ instance.getLogger().error("Couldn't autocomplete path for module name: " + s
+ + "! If you're on a case sensitive filesystem, please take note that case correction does not work. Make sure that the classname has proper capitalisation.");
+
+ }
+ updateConfig();
+ }
+
+ /** This method enables a specific module. If no module with that name is known to the loader yet it will be added to the list.</br>
+ * This method is deprecated, use enableDynamicModule instead. When using this method, dynamic reloading of the module will not be supported.
+ *
+ * @param clazz The class of the module to be enabled.
+ * @return true, when the module was successfully enabled. */
+ @Deprecated
+ public static final boolean enableModule(Class<? extends Module> clazz)
+ {
+ for (Module module : modules.keySet())
+ {
+ if (module.getClass().equals(clazz))
+ {
+ if (modules.get(module))
+ {
+ instance.getLogger().info("Module was already enabled! Ignoring module.!");
+ return true;
+ }
+ if (module.onEnable())
+ {
+ if (module.getClass().isAnnotationPresent(AutoRegisterListener.class)
+ && (module instanceof Listener))
+ {
+ Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin);
+ }
+ instance.getLogger().info("Enabled module " + module.getClass().getName());
+ instance.getLogger().info("Loaded module " + module.getClass().getName());
+ modules.put(module, true);
+ return true;
+ }
+ else
+ {
+ instance.getLogger().error("Failed to enable module " + module.getClass().getName());
+ return false;
+ }
+ }
+ }
+ try
+ {
+ Module m = clazz.newInstance();
+ modules.put(m, false);
+ if (m.onEnable())
+ {
+ if (m.getClass().isAnnotationPresent(AutoRegisterListener.class) && (m instanceof Listener))
+ {
+ Bukkit.getPluginManager().registerEvents((Listener) m, Main.plugin);
+ }
+ instance.getLogger().info("Loaded and enabled module " + m.getClass().getName());
+ instance.getLogger().info("Loaded module " + m.getClass().getName());
+ return true;
+ }
+ else
+ {
+ instance.getLogger().error("Failed to enable module " + m.getClass().getName());
+ return false;
+ }
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ instance.getLogger()
+ .error("Could not add " + clazz.getName() + " to the list, constructor not accessible.");
+ return false;
+ }
+ }
+
+ private static final void enableLoadedModule(Module module, Version oldVersion)
+ {
+ try
+ {
+ InputStream infoFile = null;
+
+ if (VersionHelper.isCompatible(VersionHelper.create(5, 0, 0, 5), module.getClass())) {
+ String basePath = "plugins/ModuleLoader/classes/" + module.getClass().getName().replace(".", "/");
+
+ try {
+ infoFile = new FileInputStream(
+ new File(basePath.substring(0, basePath.lastIndexOf('/')+1) + "module.info"));
+ }
+ catch(Exception e) {
+ infoFile = null;
+ }
+ }
+ ModuleInfo info = new ModuleInfo(infoFile, module);
+
+ moduleInfos.put(module, info);
+
+ String category = info.getCategory();
+ if (!categorizes.containsKey(category))
+ categorizes.put(category, new ArrayList<>(Arrays.asList(module)));
+ else {
+ List<Module> modsInCat = categorizes.get(category);
+ modsInCat.add(module);
+ categorizes.put(category, modsInCat);
+ }
+
+ loggers.put(module, new ModuleLogger(info.getDisplayName()));
+
+
+ if (module.onEnable())
+ {
+ modules.put(module, true);
+ if (VersionHelper.getString(oldVersion).equals("0.0.0.0"))
+ module.firstLoad();
+ else if (!VersionHelper.getVersion(module.getClass()).equals(VersionHelper.getString(oldVersion)))
+ module.migrate(oldVersion);
+ if (VersionHelper.isCompatible(VersionHelper.create(5, 0, 0, 3), module.getClass()))
+ module.postEnable();
+ if (VersionHelper.isCompatible(VersionHelper.create(5, 0, 0, 4), module.getClass()))
+ {
+ Commands ann = module.getClass().getAnnotation(Commands.class);
+ if (ann != null)
+ {
+ switch (ann.value())
+ {
+ case File:
+ File f = new File("plugins/ModuleLoader/classes/"
+ + module.getClass().getName().replace(".", "/") + ".cmd");
+ CommandManager.registerCommand(f, module, Main.plugin);
+ break;
+ case Stream:
+ InputStream stream = module.getClass()
+ .getResourceAsStream(module.getClass().getSimpleName() + ".cmd");
+ CommandManager.registerCommand(stream, module, Main.plugin);
+ case String:
+ CommandManager.registerCommand(module.getCommandString(), module, Main.plugin);
+ break;
+ case None:
+ break;
+ }
+ }
+ }
+ instance.getLogger().info("Loaded module " + module.getClass().getName());
+ if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener))
+ Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin);
+ }
+ else
+ instance.getLogger().error("Failed to load module " + module.getClass().getName());
+ }
+ catch (Exception e)
+ {
+ instance.getLogger().error("Failed to load module " + module.getClass().getName());
+ e.printStackTrace();
+ }
+ }
+
+ /** This method lists all modules to the specified CommandSender. The modules will be color coded correspondingly to their enabled status.
+ *
+ * @param sender The person to send the info to, usually the issuer of the command or the console sender.
+ * @return true. */
+ @Command(hook = "list", async = AsyncType.ALWAYS)
+ public boolean listModulesCommand(CommandSender sender)
+ {
+ boolean hasCategorys = hasCategories();
+ Message m = new Message(sender, null);
+ ModuleInfo ml_info = moduleInfos.get(instance);
+
+ m.appendText("§2--=[ ")
+ .appendTextHover("§2" + ml_info.getDisplayName(), ml_info.getModuleInfoHover())
+ .appendText("§2 ]=--\nModules:\n");
+
+ for (String cat: categorizes.keySet()) {
+ if (hasCategorys)
+ m.appendText("\n&7" + cat + ":\n");
+
+ int curModule = 1;
+ List<Module> mods = categorizes.get(cat);
+ for (Module mod : mods) {
+
+ ModuleInfo info = moduleInfos.get(mod);
+ m.appendTextHover((modules.get(mod) ? "§a" : "§c") + info.getDisplayName(), info.getModuleInfoHover());
+
+ if (curModule != mods.size())
+ m.appendText("&7, ");
+ curModule++;
+ }
+ m.appendText("\n");
+
+ }
+ m.send();
+ return true;
+ }
+
+ public static void disableModules()
+ {
+ for (Module module : modules.keySet())
+ {
+ disableModule(module);
+ }
+ }
+
+ public static void disableModule(Module module)
+ {
+ if (modules.get(module))
+ {
+ module.onDisable();
+ if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener))
+ {
+ HandlerList.unregisterAll((Listener) module);
+ }
+ CommandManager.unregisterAllWithFallback(module.getClass().getSimpleName());
+ PrivateLogManager.unregister(module);
+ try
+ {
+ URLClassLoader loader = loaders.get(module);
+ if (loader != null)
+ loader.close();
+ }
+ catch (IOException e)
+ {}
+ finally
+ {
+ loaders.remove(module);
+ }
+ }
+ }
+
+ @Command(hook = "load")
+ public boolean loadModule(CommandSender sender, String name)
+ {
+ if (!addDynamicModule(name))
+ {
+ instance.getLogger().message(sender, true, "Couldn't autocomplete path for module name: " + name
+ + "! If you're on a case sensitive filesystem, please take note that case correction does not work. Make sure that the classname has proper capitalisation.");
+
+ }
+ updateConfig();
+ return true;
+ }
+
+ @Command(hook = "unload")
+ public boolean unloadModule(CommandSender sender, String name)
+ {
+ if (!removeDynamicModule(name))
+ instance.getLogger().error("Couldn't find module! Couldn't disable nonexisting module!");
+ return true;
+ }
+
+ public static final boolean addDynamicModule(String raw_name)
+ {
+ String[] raw = raw_name.split(" ");
+ String name = raw[0];
+ Version oldVersion;
+ if (raw.length > 1)
+ oldVersion = VersionHelper.getVersion(raw[1]);
+ else
+ oldVersion = VersionHelper.create(0, 0, 0, 0);
+ for (Module m : modules.keySet())
+ {
+ if (m.getClass().getName().equals(name))
+ {
+ instance.getLogger().info(
+ "Found existing module, attempting override. WARNING! This operation will halt the main thread until it is completed.");
+ instance.getLogger()
+ .info("Attempting to load new class definition before disabling and removing the old module");
+ boolean differs = false;
+ instance.getLogger().info("Old class definition: Class@" + m.getClass().hashCode());
+ ClassLoader delegateParent = mainLoader.getParent();
+ Class<?> newClass = null;
+ URLClassLoader cl = new URLClassLoader(urls, delegateParent);
+ try
+ {
+ newClass = cl.loadClass(m.getClass().getName());
+ instance.getLogger().info("Found new class definition: Class@" + newClass.hashCode());
+ differs = m.getClass() != newClass;
+ }
+ catch (ClassNotFoundException e)
+ {
+ instance.getLogger().error("Could not find a class definition, aborting now!");
+ e.printStackTrace();
+ try
+ {
+ cl.close();
+ }
+ catch (IOException e1)
+ {
+ e1.printStackTrace();
+ }
+ return false;
+ }
+ if (!differs)
+ {
+ if (!debugMode)
+ {
+ instance.getLogger().warn(
+ "New class definition equals old definition, are you sure you did everything right?");
+ instance.getLogger().info("Aborting now...");
+ try
+ {
+ cl.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ }
+ else
+ instance.getLogger().warn(
+ "New class definition equals old definition, but debugMode is enabled. Loading anyways.");
+ }
+ else
+ instance.getLogger().info("Found new class definition, attempting to instantiate:");
+ Module module = null;
+ try
+ {
+ module = (Module) newClass.newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ instance.getLogger().error("Could not instantiate the module, aborting!");
+ e.printStackTrace();
+ try
+ {
+ cl.close();
+ }
+ catch (IOException e1)
+ {
+ e1.printStackTrace();
+ }
+ return false;
+ }
+ instance.getLogger().info("Instantiated new class definition, checking versions");
+ oldVersion = m.getClass().getAnnotation(Version.class);
+ instance.getLogger().info("Current version: " + VersionHelper.getString(oldVersion));
+ Version newVersion = module.getClass().getAnnotation(Version.class);
+ instance.getLogger().info("Version of remote class: " + VersionHelper.getString(newVersion));
+ if (oldVersion.equals(newVersion))
+ {
+ if (!debugMode)
+ {
+ instance.getLogger().error("Detected equal module versions, " + (debugMode
+ ? " aborting now... Set debugMode to true in your config if you want to continue!"
+ : " continueing anyways."));
+ if (!debugMode)
+ {
+ try
+ {
+ cl.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ }
+ }
+ else
+ instance.getLogger()
+ .warn("New version equals old version, but debugMode is enabled. Loading anyways.");
+ }
+ else
+ instance.getLogger().info("Versions differ, disabling old module");
+ disableModule(m);
+ instance.getLogger().info("Disabled module, overriding the implementation");
+ modules.remove(m);
+ categorizes.get(moduleInfos.get(m).getCategory()).remove(m);
+ moduleInfos.remove(m);
+
+ try
+ {
+ if (loaders.containsKey(m))
+ loaders.remove(m).close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ modules.put(module, false);
+ loaders.put(module, cl);
+ instance.getLogger().info("Successfully updated class definition. Enabling new implementation:");
+ enableLoadedModule(module, oldVersion);
+ return true;
+ }
+ }
+ ClassLoader delegateParent = mainLoader.getParent();
+ URLClassLoader cl = new URLClassLoader(urls, delegateParent);
+ try
+ {
+ Class<?> clazz = cl.loadClass(name);
+ Module module = (Module) clazz.newInstance();
+ modules.put(module, false);
+ loaders.put(module, cl);
+ enableLoadedModule(module, oldVersion);
+ return true;
+ }
+ catch (NoClassDefFoundError | ClassNotFoundException | InstantiationException | IllegalAccessException e)
+ {
+ try
+ {
+ cl.close();
+ }
+ catch (IOException e1)
+ {}
+ if (e instanceof NoClassDefFoundError)
+ {
+ NoClassDefFoundError exception = (NoClassDefFoundError) e;
+ String[] exMessage = exception.getMessage().split(" ");
+ String moduleName = exMessage[exMessage.length - 1]
+ .substring(0, exMessage[exMessage.length - 1].length()
+ - (exMessage[exMessage.length - 1].endsWith(")") ? 1 : 0))
+ .replace("/", ".");
+ if (!moduleName.equalsIgnoreCase(name))
+ {
+ instance.getLogger()
+ .error("Class &e" + moduleName + "&r couldn't be found! Suspecting a missing dependency!");
+ return false;
+ }
+ else
+ instance.getLogger().warn(
+ "Couldn't find class definition, attempting to get proper classname from thrown Exception.");
+ if (addDynamicModule(moduleName))
+ return true;
+ }
+ if (name.endsWith(".class"))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, but path ends with .class -> Attempting again with removed file suffix.");
+ if (addDynamicModule(name.replaceAll(".class$", "")))
+ return true;
+ }
+ if (!name.contains("."))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, suspecting incomplete path. Attempting autocompletion of path by adding a package name and trying again.");
+ if (addDynamicModule(name.toLowerCase() + "." + name))
+ return true;
+ }
+ if (!name.startsWith("com.redstoner.modules.") && name.contains("."))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, suspecting incomplete path. Attempting autocompletion of package name and trying again.");
+ if (addDynamicModule("com.redstoner.modules." + name))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static final boolean removeDynamicModule(String name)
+ {
+ for (Module m : modules.keySet())
+ {
+ if (m.getClass().getName().equals(name))
+ {
+ instance.getLogger().info(
+ "Found existing module, attempting unload. WARNING! This operation will halt the main thread until it is completed.");
+ instance.getLogger().info("Attempting to disable module properly:");
+ disableModule(m);
+ modules.remove(m);
+ categorizes.get(moduleInfos.get(m).getCategory()).remove(m);
+ moduleInfos.remove(m);
+ instance.getLogger().info("Disabled module.");
+ return true;
+ }
+ }
+ if (!name.startsWith("com.redstoner.modules."))
+ {
+ if (name.endsWith(".class"))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, but path ends with .class -> Attempting again with removed file suffix.");
+ if (removeDynamicModule(name.replaceAll(".class$", "")))
+ return true;
+ }
+ if (!name.contains("."))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, suspecting incomplete path. Attempting autocompletion of path by adding a package name and trying again.");
+ if (removeDynamicModule(name.toLowerCase() + "." + name))
+ return true;
+ }
+ if (!name.startsWith("com.redstoner.modules."))
+ {
+ instance.getLogger().warn(
+ "Couldn't find class definition, suspecting incomplete path. Attempting autocompletion of package name and trying again.");
+ if (removeDynamicModule("com.redstoner.modules." + name))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Finds a module by name for other modules to reference it.
+ *
+ * @param name the name of the module. Use the full path if you are not sure about the module's SimpleClassName being unique.
+ * @return the instance of the module or @null it none could be found */
+ public static Module getModule(String name)
+ {
+ for (Module m : modules.keySet())
+ if (m.getClass().getSimpleName().equalsIgnoreCase(name) || m.getClass().getName().equalsIgnoreCase(name))
+ return m;
+ return null;
+ }
+
+ /** Finds a module by name for other modules to reference it.
+ *
+ * @param name the name of the module. Use the full path if you are not sure about the module's SimpleClassName being unique.
+ * @return the instance of the module or @null it none could be found */
+ public static boolean exists(String name)
+ {
+ for (Module m : modules.keySet())
+ if (m.getClass().getSimpleName().equals(name) || m.getClass().getName().equals(name))
+ return true;
+ return false;
+ }
+
+ public static ModuleLogger getModuleLogger(Module module)
+ {
+ return loggers.get(module);
+ }
+
+ public static void updateConfig()
+ {
+ List<String> coremods = config.getStringList("coremods");
+ ArrayList<String> new_coremods = new ArrayList<>();
+ List<String> autoload = config.getStringList("autoload");
+ ArrayList<String> new_autoload = new ArrayList<>();
+
+ for (String s : coremods)
+ {
+ if (s.startsWith("#"))
+ {
+ new_coremods.add(s);
+ }
+ else
+ {
+ s = s.split(" ")[0];
+ try
+ {
+ new_coremods.add(getModule(s).getClass().getName() + " "
+ + VersionHelper.getVersion(getModule(s).getClass()));
+ }
+ catch (Exception e)
+ {
+ new_coremods.add(s + " " + VersionHelper.getString(VersionHelper.create(0, 0, 0, 0)));
+ }
+ }
+ }
+ for (String s : autoload)
+ {
+ if (s.startsWith("#"))
+ {
+ new_autoload.add(s);
+ }
+ else
+ {
+ s = s.split(" ")[0];
+ try
+ {
+ new_autoload.add(getModule(s).getClass().getName() + " "
+ + VersionHelper.getVersion(getModule(s).getClass()));
+ }
+ catch (Exception e)
+ {
+ new_autoload.add(s + " " + VersionHelper.getString(VersionHelper.create(0, 0, 0, 0)));
+ }
+ }
+ }
+
+ config.set("coremods", new_coremods);
+ config.set("autoload", new_autoload);
+ try
+ {
+ config.save(configFile);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static JavaPlugin getPlugin() {
+ return Main.plugin;
+ }
+
+ public static boolean hasCategories() {
+ return !(categorizes.size() == 1 && categorizes.containsKey("Other"));
+ }
+}