diff options
Diffstat (limited to 'src/main/java')
54 files changed, 3059 insertions, 0 deletions
diff --git a/src/main/java/com/redstoner/annotations/AutoRegisterListener.java b/src/main/java/com/redstoner/annotations/AutoRegisterListener.java new file mode 100644 index 0000000..fabdb5f --- /dev/null +++ b/src/main/java/com/redstoner/annotations/AutoRegisterListener.java @@ -0,0 +1,15 @@ +package com.redstoner.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** The auto register annotation, to be put onto Classes that implement listener when you are too lazy to register the events yourself. + * + * @author Pepich */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Version(major = 1, minor = 0, revision = 1, compatible = 1) +public @interface AutoRegisterListener +{} diff --git a/src/main/java/com/redstoner/annotations/Commands.java b/src/main/java/com/redstoner/annotations/Commands.java new file mode 100644 index 0000000..537bff0 --- /dev/null +++ b/src/main/java/com/redstoner/annotations/Commands.java @@ -0,0 +1,15 @@ +package com.redstoner.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.redstoner.misc.CommandHolderType; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Commands +{ + CommandHolderType value(); +} diff --git a/src/main/java/com/redstoner/annotations/Version.java b/src/main/java/com/redstoner/annotations/Version.java new file mode 100644 index 0000000..52d5145 --- /dev/null +++ b/src/main/java/com/redstoner/annotations/Version.java @@ -0,0 +1,32 @@ +package com.redstoner.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** The Version annotation, to be applied to all Classes that are part of the project. + * + * @author Pepich */ +@Target(ElementType.TYPE) +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Version +{ + /** The major indicator of the version. Will be used for compatibility detection. + * + * @return the major version as an int */ + int major(); + + int minor(); + + int revision(); + + /** The compatibility part of the version number. Will be used for compatibility detection.</br> + * Set to -1 if it is supposed to be always compatible.</br> + * Defaults to 1. + * + * @return the smallest compatible version as an int. */ + int compatible() default 1; +} diff --git a/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.cmd b/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.cmd new file mode 100644 index 0000000..4e06bd0 --- /dev/null +++ b/src/main/java/com/redstoner/coremods/moduleLoader/ModuleLoader.cmd @@ -0,0 +1,24 @@ +command modules { + [empty] { + help Lists all modules. Color indicates status: §aENABLED §cDISABLED; + perm moduleloader.modules.list; + run list; + } + list { + help Lists all modules. Color indicates status: §aENABLED §cDISABLED; + perm moduleloader.modules.list; + run list; + } + load [string:name...] { + help (Re)-Loads a module. WARNING: Handle with care! This has direct affect on code being executed. This command will temporarily halt the main thread until the class loading operation was completed.; + perm moduleloader.modules.admin; + run load name; + type console; + } + unload [string:name...] { + help Unloads a module. WARNING: Handle with care! This has direct affect on code being executed. This command will temporarily halt the main thread until the class loading operation was completed.; + perm moduleloader.modules.admin; + run unload name; + type console; + } +}
\ No newline at end of file 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")); + } +} diff --git a/src/main/java/com/redstoner/exceptions/MissingVersionException.java b/src/main/java/com/redstoner/exceptions/MissingVersionException.java new file mode 100644 index 0000000..62032b6 --- /dev/null +++ b/src/main/java/com/redstoner/exceptions/MissingVersionException.java @@ -0,0 +1,22 @@ +package com.redstoner.exceptions; + +import com.redstoner.annotations.Version; + +/** To be thrown when a module is not annotated with its version. If this gets thrown, then oh boy, you're in trouble now. + * + * @author Pepich */ +@Version(major = 1, minor = 0, revision = 0, compatible = -1) +public class MissingVersionException extends Exception +{ + private static final long serialVersionUID = 4940161335512222539L; + + public MissingVersionException() + { + super(); + } + + public MissingVersionException(String message) + { + super(message); + } +} diff --git a/src/main/java/com/redstoner/exceptions/NonSaveableConfigException.java b/src/main/java/com/redstoner/exceptions/NonSaveableConfigException.java new file mode 100644 index 0000000..df33bff --- /dev/null +++ b/src/main/java/com/redstoner/exceptions/NonSaveableConfigException.java @@ -0,0 +1,9 @@ +package com.redstoner.exceptions; + +public class NonSaveableConfigException extends Exception { + private static final long serialVersionUID = -7271481973389455510L; + + public NonSaveableConfigException() { + super("This config does not support saving!"); + } +} diff --git a/src/main/java/com/redstoner/logging/Log4JFilter.java b/src/main/java/com/redstoner/logging/Log4JFilter.java new file mode 100644 index 0000000..1ebed09 --- /dev/null +++ b/src/main/java/com/redstoner/logging/Log4JFilter.java @@ -0,0 +1,54 @@ +package com.redstoner.logging; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.message.Message; + +public class Log4JFilter extends AbstractFilter { + + private static final long serialVersionUID = -5594073755007974254L; + + private static Result validateMessage(Message message) { + if (message == null) { + return Result.NEUTRAL; + } + return validateMessage(message.getFormattedMessage()); + } + + private static Result validateMessage(String message) { + return PrivateLogManager.isHidden(message) + ? Result.DENY + : Result.NEUTRAL; + } + + @Override + public Result filter(LogEvent event) { + Message candidate = null; + if (event != null) { + candidate = event.getMessage(); + } + return validateMessage(candidate); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { + return validateMessage(msg); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { + return validateMessage(msg); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { + String candidate = null; + if (msg != null) { + candidate = msg.toString(); + } + return validateMessage(candidate); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/logging/PrivateLogManager.java b/src/main/java/com/redstoner/logging/PrivateLogManager.java new file mode 100644 index 0000000..e8451e0 --- /dev/null +++ b/src/main/java/com/redstoner/logging/PrivateLogManager.java @@ -0,0 +1,89 @@ +package com.redstoner.logging; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; + +import com.redstoner.misc.Utils; +import com.redstoner.modules.Module; +import com.redstoner.modules.ModuleLogger; + +public class PrivateLogManager { + + private static Map<String, Module> registrar = new HashMap<>(); + private static Map<String, String> commands = new HashMap<>(); + + private static final String ISSUED_COMMAND_TEXT = "issued server command: /"; + private static final int ISSUED_COMMAND_TEXT_LENGTH = ISSUED_COMMAND_TEXT.length(); + + private static ModuleLogger logger; + + public static void initialize() { + org.apache.logging.log4j.core.Logger logger; + logger = (org.apache.logging.log4j.core.Logger) LogManager.getRootLogger(); + logger.addFilter(new Log4JFilter()); + PrivateLogManager.logger = new ModuleLogger("PrivateLogManager"); + } + + public static void register(Module module, String command, String replacement) { + command = command.toLowerCase(); + registrar.put(command, module); + commands.put(command, replacement); + logger.info(module.getClass().getSimpleName() + " registered &e/" + command + + (replacement.equals("")? "&7. Command will not be logged!" + : "&7, using replacement, &e" + replacement + "&7.")); + } + + public static void unregister(Module module) { + String unregestered = ""; + Iterator<Map.Entry<String, Module>> i = registrar.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry<String, Module> entry = i.next(); + if (entry.getValue() == module) { + i.remove(); + commands.remove(entry.getKey()); + unregestered += "&e" + entry.getKey() + "&7, "; + } + } + if (!unregestered.equals("")) + logger.info("Unregistered " + unregestered.substring(0, unregestered.length() - 2) + "&7 for module, " + module.getClass().getSimpleName() + "."); + } + + public static void unregister(Module module, String... toRemove) { + String unregestered = ""; + for (int i = 0; i < toRemove.length; i++) { + String command = toRemove[i].toLowerCase(); + registrar.remove(command); + if (commands.remove(command) != null) + unregestered += "&e" + command + "&7, "; + } + if (!unregestered.equals("")) + logger.info(module.getClass().getSimpleName() + " unregistered " + unregestered.substring(0, unregestered.length() - 2) + "&7."); + } + + public static boolean isHidden(String message) { + if (message == null) + return false; + + int index = message.indexOf(ISSUED_COMMAND_TEXT); + if (index == -1) + return false; + + String command = message.substring(index + ISSUED_COMMAND_TEXT_LENGTH); + + int spaceIndex = command.indexOf(" "); + command = spaceIndex == -1? command.toLowerCase() : command.substring(0, spaceIndex).toLowerCase(); + + String replacement = commands.get(command); + if (replacement == null) + return false; + if (replacement.equals("")) + return true; + + String player = message.substring(0, message.indexOf(" ")); + Utils.run(() -> System.out.println(replacement.replace("$s", player))); + return true; + } +} diff --git a/src/main/java/com/redstoner/misc/BroadcastFilter.java b/src/main/java/com/redstoner/misc/BroadcastFilter.java new file mode 100644 index 0000000..1f0ce04 --- /dev/null +++ b/src/main/java/com/redstoner/misc/BroadcastFilter.java @@ -0,0 +1,14 @@ +package com.redstoner.misc; + +import org.bukkit.command.CommandSender; + +import com.redstoner.annotations.Version; + +/** Classes implementing this interface can be used to define a filter for the Utils.broadcast method for sending a message to more than one, but less than all users. + * + * @author Pepich */ +@Version(major = 1, minor = 0, revision = 0, compatible = 1) +public interface BroadcastFilter +{ + public boolean sendTo(CommandSender recipient); +} diff --git a/src/main/java/com/redstoner/misc/CommandHolderType.java b/src/main/java/com/redstoner/misc/CommandHolderType.java new file mode 100644 index 0000000..7c4383e --- /dev/null +++ b/src/main/java/com/redstoner/misc/CommandHolderType.java @@ -0,0 +1,13 @@ +package com.redstoner.misc; + +import com.redstoner.annotations.Version; + +/** @author Pepich */ +@Version(major = 4, minor = 0, revision = 0, compatible = -1) +public enum CommandHolderType +{ + Stream, + File, + String, + None +} diff --git a/src/main/java/com/redstoner/misc/JsonManager.java b/src/main/java/com/redstoner/misc/JsonManager.java new file mode 100644 index 0000000..13b51b6 --- /dev/null +++ b/src/main/java/com/redstoner/misc/JsonManager.java @@ -0,0 +1,149 @@ +package com.redstoner.misc; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.redstoner.annotations.Version; + +/** This class provides simple JSON handling, like storing and loading from and to files. + * + * @author Pepich */ +@Version(major = 1, minor = 0, revision = 2, compatible = -1) +public class JsonManager +{ + private JsonManager() + {} + + /** Loads a JSONObject from a file. + * + * @param source the file to load from. + * @return the JSONObject or null if the source does not contain a valid JSONObject. */ + public static JSONObject getObject(File source) + { + if (!source.exists()) + return null; + JSONParser parser = new JSONParser(); + try + { + FileReader reader = new FileReader(source); + Object rawObject = parser.parse(reader); + reader.close(); + JSONObject jsonObject = (JSONObject) rawObject; + return jsonObject; + } + catch (IOException | ParseException e) + {} + return null; + } + + /** Saves a JSONObject to a file. Will create the necessary FileStructure like folders and the file itself.</br> + * Note that this operation will be run on a different thread and you do not need to take care of that yourself. + * + * @param object the JSONObject to save. + * @param destination the file to write to. */ + public static void save(JSONObject object, File destination) + { + Thread t = new Thread(new Runnable() + { + @Override + public void run() + { + saveSync(object, destination); + } + }); + t.start(); + } + + /** Saves a JSONObject to a file. Will create the necessary FileStructure like folders and the file itself.</br> + * Note that this operation will be run on the same thread that you are calling it from! + * + * @param object the JSONObject to save. + * @param destination the file to write to. */ + public static void saveSync(JSONObject object, File destination) + { + if (destination.exists()) + destination.delete(); + else if (!destination.getParentFile().exists()) + destination.getParentFile().mkdirs(); + try + { + destination.createNewFile(); + FileWriter writer = new FileWriter(destination); + String json_string = object.toJSONString(); + writer.write(json_string); + writer.flush(); + writer.close(); + } + catch (IOException e) + {} + } + + /** Loads a JSONArray from a file. + * + * @param source the file to load from. + * @return the JSONArray or null if the source does not contain a valid JSONArray. */ + public static JSONArray getArray(File source) + { + if (!source.exists()) + return null; + JSONParser parser = new JSONParser(); + try + { + Object rawObject = parser.parse(new FileReader(source)); + JSONArray jsonArray = (JSONArray) rawObject; + return jsonArray; + } + catch (IOException | ParseException e) + {} + return null; + } + + /** Saves a JSONArray to a file. Will create the necessary FileStructure like folders and the file itself.</br> + * Note that this operation will be run on a different thread and you do not need to take care of that yourself. + * + * @param object the JSONArray to save. + * @param destination the file to write to. */ + public static void save(JSONArray array, File destination) + { + Thread t = new Thread(new Runnable() + { + @Override + public void run() + { + saveSync(array, destination); + } + }); + t.start(); + } + + /** Saves a JSONArray to a file. Will create the necessary FileStructure like folders and the file itself.</br> + * Note that this operation will be run on the same thread that you are calling it from! + * + * @param object the JSONArray to save. + * @param destination the file to write to. */ + public static void saveSync(JSONArray array, File destination) + { + if (destination.exists()) + destination.delete(); + else if (!destination.getParentFile().exists()) + destination.getParentFile().mkdirs(); + try + { + destination.createNewFile(); + FileWriter writer = new FileWriter(destination); + String json_string = array.toJSONString(); + writer.write(json_string); + writer.flush(); + writer.close(); + } + catch (IOException e) + {} + } +} diff --git a/src/main/java/com/redstoner/misc/Main.java b/src/main/java/com/redstoner/misc/Main.java new file mode 100644 index 0000000..40894ee --- /dev/null +++ b/src/main/java/com/redstoner/misc/Main.java @@ -0,0 +1,40 @@ +package com.redstoner.misc; + +import org.bukkit.plugin.java.JavaPlugin; + +import com.redstoner.annotations.Version; +import com.redstoner.coremods.moduleLoader.ModuleLoader; +import com.redstoner.logging.PrivateLogManager; +import com.redstoner.misc.mysql.MysqlHandler; + +import net.nemez.chatapi.ChatAPI; + +/** Main class. Duh. + * + * @author Pepich */ +@Version(major = 5, minor = 1, revision = 0, compatible = -1) +public class Main extends JavaPlugin +{ + public static JavaPlugin plugin; + + @Override + public void onEnable() + { + plugin = this; + + PrivateLogManager.initialize(); + + ChatAPI.initialize(this); + // Configger.init(); + MysqlHandler.init(); + ModuleLoader.init(); + // Load modules from config + ModuleLoader.loadFromConfig(); + } + + @Override + public void onDisable() + { + ModuleLoader.disableModules(); + } +} diff --git a/src/main/java/com/redstoner/misc/ModuleInfo.java b/src/main/java/com/redstoner/misc/ModuleInfo.java new file mode 100644 index 0000000..e96e813 --- /dev/null +++ b/src/main/java/com/redstoner/misc/ModuleInfo.java @@ -0,0 +1,81 @@ +package com.redstoner.misc; + +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import com.redstoner.coremods.moduleLoader.ModuleLoader; +import com.redstoner.exceptions.MissingVersionException; +import com.redstoner.modules.Module; + +public class ModuleInfo { + + private String simpleName; + private String displayName; + private String category; + private String description; + private String version; + + private String warning; + + public ModuleInfo(InputStream descriptor, Module module) { + try { + InputStreamReader reader = new InputStreamReader(descriptor); + FileConfiguration config = YamlConfiguration.loadConfiguration(reader); + + displayName = config.getString("displayName"); + category = config.getString("category"); + description = config.getString("description"); + } + catch (Exception e) { + warning = "Descriptor file could not be loaded, using the class's name."; + } + + simpleName = module.getClass().getSimpleName(); + + if (displayName == null) + displayName = simpleName; + + if (category == null) + category = "Other"; + + try { + version = VersionHelper.getVersion(module.getClass()); + } catch (MissingVersionException e) {} + } + + public String getSimpleName() { + return simpleName; + } + + public String getDisplayName() { + return displayName; + } + + public String getCategory() { + return category; + } + + public String getDescription() { + return description; + } + + public String getWarning() { + return warning; + } + + public String getVersion() { + return version; + } + + public String getModuleInfoHover() { + return "&8&o" + getSimpleName() + "\n" + + "&r&e" + (getVersion() == null? "&cVersion Missing" : getVersion()) + + "&r&9" + (ModuleLoader.hasCategories()? "\n" + getCategory() : "") + + "&r&7" + (getDescription() == null? "" : "\n\n" + getDescription()); + } + + +} diff --git a/src/main/java/com/redstoner/misc/Utils.java b/src/main/java/com/redstoner/misc/Utils.java new file mode 100644 index 0000000..e3fd68c --- /dev/null +++ b/src/main/java/com/redstoner/misc/Utils.java @@ -0,0 +1,203 @@ +package com.redstoner.misc; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import com.redstoner.annotations.Version; +import com.redstoner.coremods.moduleLoader.ModuleLoader; + +import net.nemez.chatapi.ChatAPI; +import net.nemez.chatapi.click.Message; + +/** The utils class containing utility functions. Those include but are not limited to sending formatted messages, broadcasts and more. + * + * @author Pepich */ +@Version(major = 4, minor = 0, revision = 2, compatible = 1) +public final class Utils +{ + /** The @SimpleDateFormat used for getting the current date. */ + public static SimpleDateFormat dateFormat = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]"); + + /** The Pattern for a UUID*/ + private static final Pattern UUID_pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + private static final Pattern Class_pattern = Pattern.compile(".*\\."); + private static final Pattern NoDolarSign_pattern = Pattern.compile("\\$\\d*"); + + /** Hidden constructor. Do not instantiate UTILS classes! :) */ + private Utils() + {} + + /** This method broadcasts a message to all players and console that are allowed by the filter. Set the filter to NULL to broadcast to everyone.</br> + * If you want to, you can set a message that will be logged to console. Set to null to not log anything.</br> + * You can still allow console in the filter to log the original message. + * + * @param prefix The prefix for the message. Set to NULL to let it auto generate. + * @param message the message to be sent around + * @param filter the BroadcastFilter to be applied.</br> + * Write a class implementing the interface and pass it to this method, the "sendTo()" method will be called for each recipient. + * @param logmessage the log message to appear in console. Set to null to not log this (you can still log the original message by returning true in the filter). + * @return the amount of people that received the message. */ + public static int broadcast(String prefix, String message, BroadcastFilter filter) + { + if (prefix == null) + prefix = "§8[§2" + getCaller() + "§8]: "; + if (filter == null) + { + for (Player p : Bukkit.getOnlinePlayers()) + p.sendMessage(prefix + message); + Bukkit.getConsoleSender().sendMessage(prefix + message); + return Bukkit.getOnlinePlayers().size() + 1; + } + else + { + int count = 0; + for (Player p : Bukkit.getOnlinePlayers()) + if (filter.sendTo(p)) + { + p.sendMessage(prefix + message); + count++; + } + if (filter.sendTo(Bukkit.getConsoleSender())) + { + Bukkit.getConsoleSender().sendMessage(prefix + message); + count++; + } + return count; + } + } + + /** This method broadcasts a message to all players and console that are allowed by the filter. Set the filter to NULL to broadcast to everyone.</br> + * If you want to, you can set a message that will be logged to console. Set to null to not log anything.</br> + * You can still allow console in the filter to log the original message. + * + * @param prefix The prefix for the message. Set to NULL to let it auto generate. + * @param message the message to be sent around + * @param filter the BroadcastFilter to be applied.</br> + * Write a class implementing the interface and pass it to this method, the "sendTo()" method will be called for each recipient. + * @param logmessage the log message to appear in console. Set to null to not log this (you can still log the original message by returning true in the filter). + */ + public static int broadcast(String prefix, Message message, BroadcastFilter filter) + { + if (prefix == null) + prefix = "§8[§2" + getCaller() + "§8]: "; + if (filter == null) + { + for (Player p : Bukkit.getOnlinePlayers()) + ChatAPI.createMessage(p).appendText(prefix).appendMessage(message).send(); + Bukkit.getConsoleSender().sendMessage(prefix + message.getRawMessage()); + return Bukkit.getOnlinePlayers().size() + 1; + } + else + { + int count = 0; + for (Player p : Bukkit.getOnlinePlayers()) + if (filter.sendTo(p)) + { + ChatAPI.createMessage(p).appendText(prefix).appendMessage(message).send(); + count++; + } + if (filter.sendTo(Bukkit.getConsoleSender())) + { + Bukkit.getConsoleSender().sendMessage(prefix + message.getRawMessage()); + count++; + } + return count; + } + } + + /** This method will find the next parent caller and return their class name, omitting package names. + * + * @return the Name of the calling class. */ + private static final String getCaller() + { + StackTraceElement[] stackTrace = (new Exception()).getStackTrace(); + String classname = "Utils"; + for (int i = 0; classname.equals("Utils"); i++) + { + classname = Class_pattern.matcher(stackTrace[i].getClassName()).replaceAll(""); + } + return classname; + } + + /** This method will find the next parent caller and return their class name, omitting package names. + * + * @param directCaller used to prevent this method from returning the caller itself. Null if supposed to be ignored. + * @return the name of the calling class. */ + public static final String getCaller(String... directCaller) + { + if (directCaller == null || directCaller.length == 0) + return getCaller(); + StackTraceElement[] stackTrace = (new Exception()).getStackTrace(); + String classname = "Utils"; + List<String> callers = Arrays.asList(directCaller); + for (int i = 0; callers.contains(classname) || classname.equals("Utils"); i++) + { + classname = Class_pattern.matcher(stackTrace[i].getClassName()).replaceAll(""); + } + classname = NoDolarSign_pattern.matcher(classname).replaceAll(""); + return classname; + } + + /** Provides a uniform way of getting the date for all modules. + * + * @return The current date in the format "[dd-mm-yyyy hh:mm:ss]" */ + public static String getDate() + { + Date date = new Date(System.currentTimeMillis()); + return dateFormat.format(date); + } + + /** Provides a uniform way of getting the (display)name of a @CommandSender. + * + * @param sender The @CommandSender to get the name of. + * @return The DisplayName of the @CommandSender or if not a @Player, the name in blue. */ + public static String getName(CommandSender sender) + { + if (sender instanceof Player) + return ((Player) sender).getDisplayName(); + else + return "§9" + sender.getName(); + } + + /** Provides a uniform way of getting the UUID of a @CommandSender. + * + * @param sender The @CommandSender to get the UUID of. + * @return The UUID of the @CommandSender or if not a player, "CONSOLE" in blue. */ + public static String getID(CommandSender sender) + { + String id; + if (sender instanceof Player) + id = ((Player) sender).getUniqueId().toString(); + else + id = "CONSOLE"; + return id; + } + + /** Checks if the string is a UUID. + * + * @param toCheck String to check. + * @return if the string is a UUID. + */ + public static boolean isUUID(String toCheck) + { + return UUID_pattern.matcher(toCheck).matches(); + } + + public static void run(Runnable r) { + run(r, 0); + } + + public static void run(Runnable r, int delay) { + Bukkit.getScheduler().scheduleSyncDelayedTask(ModuleLoader.getPlugin(), r, delay); + } + + + +} diff --git a/src/main/java/com/redstoner/misc/VersionHelper.java b/src/main/java/com/redstoner/misc/VersionHelper.java new file mode 100644 index 0000000..e4a9403 --- /dev/null +++ b/src/main/java/com/redstoner/misc/VersionHelper.java @@ -0,0 +1,151 @@ +package com.redstoner.misc; + +import java.lang.annotation.Annotation; + +import com.redstoner.annotations.Version; +import com.redstoner.exceptions.MissingVersionException; + +/** This class can be used to compare modules against the loader version or against each other to prevent dependency issues. + * + * @author Pepich */ +@Version(major = 2, minor = 1, revision = 3, compatible = 0) +public final class VersionHelper +{ + private VersionHelper() + {} + + /** Checks two classes versions for compatibility. + * + * @param base The API to compare to. + * @param module The module to compare. + * @return true, when the module is up to date with the API, or the API supports outdated modules. + * @throws MissingVersionException When one of the parameters is not annotated with a @Version annotation. */ + public static boolean isCompatible(Class<?> api, Class<?> module) throws MissingVersionException + { + if (!api.isAnnotationPresent(Version.class)) + throw new MissingVersionException("The API is not annotated with a version."); + if (!module.isAnnotationPresent(Version.class)) + throw new MissingVersionException("The module is not annotated with a version."); + Version apiVersion = api.getAnnotation(Version.class); + Version moduleVersion = module.getAnnotation(Version.class); + return isCompatible(apiVersion, moduleVersion); + } + + /** Checks two classes versions for compatibility. + * + * @param base The API to compare to. + * @param module The module to compare. + * @return true, when the module is up to date with the API, or the API supports outdated modules. + * @throws MissingVersionException When one of the parameters is not annotated with a @Version annotation. */ + public static boolean isCompatible(Version apiVersion, Class<?> module) throws MissingVersionException + { + if (!module.isAnnotationPresent(Version.class)) + throw new MissingVersionException("The module is not annotated with a version."); + Version moduleVersion = module.getAnnotation(Version.class); + return isCompatible(apiVersion, moduleVersion); + } + + /** Checks two classes versions for compatibility. + * + * @param base The API to compare to. + * @param module The module to compare. + * @return true, when the module is up to date with the API, or the API supports outdated modules. + * @throws MissingVersionException When one of the parameters is not annotated with a @Version annotation. */ + public static boolean isCompatible(Class<?> api, Version moduleVersion) throws MissingVersionException + { + if (!api.isAnnotationPresent(Version.class)) + throw new MissingVersionException("The API is not annotated with a version."); + Version apiVersion = api.getAnnotation(Version.class); + return isCompatible(apiVersion, moduleVersion); + } + + /** Checks two versions for compatibility. + * + * @param base The API version to compare to. + * @param module The module version to compare. + * @return true, when the module is up to date with the API, or the API supports outdated modules. */ + public static boolean isCompatible(Version apiVersion, Version moduleVersion) + { + if (apiVersion.major() >= moduleVersion.compatible()) + return true; + if (apiVersion.compatible() == -1) + return false; + if (apiVersion.compatible() <= moduleVersion.major()) + return true; + return false; + } + + /** Returns the version of a given class as a String. + * + * @param clazz The class to grab the version number from. + * @return The version number of the class in format major.minor.revision.compatible. + * @throws MissingVersionException If the class is not annotated with @Version. */ + public static String getVersion(Class<?> clazz) throws MissingVersionException + { + if (!clazz.isAnnotationPresent(Version.class)) + throw new MissingVersionException("The given class is not associated with a version."); + Version ver = clazz.getAnnotation(Version.class); + return getString(ver); + } + + /** Returns the String representation of a version. + * + * @param ver The version to be represented. + * @return The String representation. */ + public static String getString(Version ver) + { + return ver.major() + "." + ver.minor() + "." + ver.revision() + "." + ver.compatible(); + } + + public static Version getVersion(String ver) + { + String[] raw = ver.split("\\."); + if (raw.length != 4) + return null; + return VersionHelper.create(Integer.parseInt(raw[0]), Integer.parseInt(raw[1]), Integer.parseInt(raw[2]), + Integer.parseInt(raw[3])); + } + + /** This method creates a new Version to use for compatibility checks. + * + * @param major The major version + * @param minor The minor version + * @param revision The revision + * @param compatible The compatibility tag + * @return */ + public static Version create(int major, int minor, int revision, int compatible) + { + return new Version() + { + @Override + public Class<? extends Annotation> annotationType() + { + return Version.class; + } + + @Override + public int revision() + { + return revision; + } + + @Override + public int minor() + { + return minor; + } + + @Override + public int major() + { + return major; + } + + @Override + public int compatible() + { + return compatible; + } + }; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/Config.java b/src/main/java/com/redstoner/misc/mysql/Config.java new file mode 100644 index 0000000..519b20a --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/Config.java @@ -0,0 +1,280 @@ +package com.redstoner.misc.mysql; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.redstoner.exceptions.NonSaveableConfigException; +import com.redstoner.misc.Main; + +public class Config +{ + private File file; + private JSONObject config; + private JSONParser parser; + + public Config() + { + file = null; + parser = new JSONParser(); + config = new JSONObject(); + } + + public Config(JSONObject config) + { + this.file = null; + this.parser = new JSONParser(); + this.config = config; + } + + private Config(File file) throws IOException, ParseException + { + this.file = file; + parser = new JSONParser(); + if (file.exists()) + { + config = loadConfig(file); + } + else + { + config = new JSONObject(); + } + } + + public static final Config getConfig(String fileName) throws IOException, ParseException + { + return new Config(new File(Main.plugin.getDataFolder(), fileName)); + } + + public static final Config getConfig(File file) throws IOException, ParseException + { + return new Config(file); + } + + private JSONObject loadConfig(File file) throws IOException, ParseException + { + FileReader reader = new FileReader(file); + JSONObject object = (JSONObject) parser.parse(reader); + reader.close(); + return object; + } + + @Override + public String toString() + { + return config.toJSONString(); + } + + public JSONObject asObject() + { + return config; + } + + public void save() throws IOException, NonSaveableConfigException + { + if (file == null) + { + throw new NonSaveableConfigException(); + } + PrintWriter writer = new PrintWriter(file); + writer.write(config.toJSONString()); + writer.close(); + } + + public void refresh() throws IOException, ParseException, NonSaveableConfigException + { + if (file == null) + { + throw new NonSaveableConfigException(); + } + loadConfig(file); + } + + public void setFile(String fileName) + { + file = new File(Main.plugin.getDataFolder(), fileName); + } + + public void setFile(File file) + { + this.file = file; + } + + @SuppressWarnings("unchecked") + public void put(String key, String value) + { + config.put(key, value); + } + + @SuppressWarnings("unchecked") + public void put(String key, List<String> value) + { + JSONArray array = new JSONArray(); + for (String entry : value) + { + array.add(entry); + } + config.put(key, array); + } + + @SuppressWarnings("unchecked") + public void putArray(String key, JSONArray value) + { + config.put(key, value); + } + + @SuppressWarnings("unchecked") + public void put(String key, Map<String, String> value) + { + JSONObject object = new JSONObject(); + for (String valKey : value.keySet()) + { + String valVal = value.get(valKey); + object.put(valKey, valVal); + } + config.put(key, object); + } + + @SuppressWarnings("unchecked") + public void put(String key, JSONObject value) + { + config.put(key, value); + } + + @SuppressWarnings("unchecked") + public void putAll(Map<String, String> entry) + { + for (String key : entry.keySet()) + { + String value = entry.get(key); + config.put(key, value); + } + } + + public boolean containsKey(String key) + { + return config.containsKey(key); + } + + public String get(String key) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof String) + { + return (String) value; + } + } + return null; + } + + public String getOrDefault(String key, String defaultValue) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof String) + { + return (String) value; + } + return null; + } + else + { + return defaultValue; + } + } + + @SuppressWarnings("unchecked") + public List<String> getList(String key) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof JSONArray) + { + JSONArray array = (JSONArray) value; + List<String> output = new ArrayList<String>(); + for (String entry : (String[]) array.toArray(new String[0])) + { + output.add(entry); + } + return output; + } + } + return null; + } + + public JSONArray getArray(String key) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof JSONArray) + { + JSONArray array = (JSONArray) value; + return array; + } + } + return null; + } + + public Map<String, String> getMap(String key) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof JSONObject) + { + JSONObject object = (JSONObject) value; + @SuppressWarnings("unchecked") + Set<Map.Entry<String, String>> entrySet = object.entrySet(); + Map<String, String> output = new HashMap<String, String>(); + for (Map.Entry<String, String> entry : entrySet) + { + output.put(entry.getKey(), entry.getValue()); + } + return output; + } + } + return null; + } + + public JSONObject getObject(String key) + { + if (containsKey(key)) + { + Object value = config.get(key); + if (value instanceof JSONObject) + { + JSONObject object = (JSONObject) value; + return object; + } + } + return null; + } + + public void remove(String key) + { + config.remove(key); + } + + @SuppressWarnings("unchecked") + public Set<Entry<String, String>> getAll() + { + return config.entrySet(); + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/JSONManager.java b/src/main/java/com/redstoner/misc/mysql/JSONManager.java new file mode 100644 index 0000000..ae248d5 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/JSONManager.java @@ -0,0 +1,107 @@ +package com.redstoner.misc.mysql; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.redstoner.misc.Main; + +public class JSONManager +{ + public static Map<Serializable, Serializable> getConfiguration(String fileName) + { + File file = new File(Main.plugin.getDataFolder(), fileName); + if (!file.exists()) + { + try + { + PrintWriter writer = new PrintWriter(file.getAbsolutePath(), "UTF-8"); + writer.println("{}"); + writer.close(); + } + catch (FileNotFoundException | UnsupportedEncodingException e) + { + e.printStackTrace(); + } + } + try + { + return loadMap(file); + } + catch (IOException | ParseException e) + { + e.printStackTrace(); + return null; + } + } + + public static void saveConfiguration(Map<Serializable, Serializable> config, String fileName) + { + try + { + saveMap(new File(Main.plugin.getDataFolder(), fileName), config); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + public static void saveList(File file, List<Serializable> entries) throws IOException + { + JSONArray array = new JSONArray(); + array.addAll(entries); + FileWriter writer = new FileWriter(file); + writer.write(array.toJSONString()); + writer.close(); + } + + public static List<Serializable> loadList(File file) throws IOException, ParseException + { + FileReader read = new FileReader(file); + List<Serializable> entries = new ArrayList<>(); + JSONArray array = (JSONArray) new JSONParser().parse(read); + for (Object o : array) + { + entries.add((Serializable) o); + } + return entries; + } + + @SuppressWarnings("unchecked") + public static void saveMap(File file, Map<Serializable, Serializable> entries) throws IOException + { + JSONObject map = new JSONObject(); + map.putAll(entries); + FileWriter writer = new FileWriter(file); + writer.write(map.toJSONString()); + writer.close(); + } + + public static Map<Serializable, Serializable> loadMap(File file) throws IOException, ParseException + { + FileReader reader = new FileReader(file); + JSONObject map = (JSONObject) new JSONParser().parse(reader); + Map<Serializable, Serializable> entries = new HashMap<>(); + for (Object o : map.keySet()) + { + entries.put((Serializable) o, (Serializable) map.get(o)); + } + return entries; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/MysqlHandler.java b/src/main/java/com/redstoner/misc/mysql/MysqlHandler.java new file mode 100644 index 0000000..909d276 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/MysqlHandler.java @@ -0,0 +1,115 @@ +package com.redstoner.misc.mysql; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.json.simple.parser.ParseException; + +import com.redstoner.misc.Main; +import com.redstoner.misc.mysql.elements.MysqlDatabase; + +public class MysqlHandler +{ + public static MysqlHandler INSTANCE; + private String url, username, password; + + public MysqlHandler(String hostname, int port, String username, String password) + { + this.url = "jdbc:mysql://" + hostname + ":" + port + "/"; + this.username = username; + this.password = password; + } + + public static void init() + { + Map<Serializable, Serializable> mysqlCredentials = new HashMap<>(); + File mysqlCredentialsFile = new File(Main.plugin.getDataFolder(), "mysqlCredentials.json"); + if (mysqlCredentialsFile.exists()) + { + try + { + mysqlCredentials = JSONManager.loadMap(mysqlCredentialsFile); + } + catch (IOException | ParseException e) + { + e.printStackTrace(); + } + } + else + { + Bukkit.getConsoleSender().sendMessage( + ChatColor.RED + "MySQL config does not exist, creating an example one, things might (will) break!"); + mysqlCredentials.put("hostname", "localhost"); + mysqlCredentials.put("port", "3306"); + mysqlCredentials.put("username", "your username here"); + mysqlCredentials.put("password", "your password here"); + try + { + JSONManager.saveMap(mysqlCredentialsFile, mysqlCredentials); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + String hostname = (String) mysqlCredentials.get("hostname"); + int port = Integer.valueOf((String) mysqlCredentials.get("port")); + String username = (String) mysqlCredentials.get("username"); + String password = (String) mysqlCredentials.get("password"); + INSTANCE = new MysqlHandler(hostname, port, username, password); + } + + private Connection getConnection(String databaseName) throws IllegalStateException + { + Connection connection = null; + try + { + connection = DriverManager.getConnection(url + databaseName, username, password); + } + catch (SQLException e) + { + throw new IllegalStateException("Cannot connect to the database!", e); + } + return connection; + } + + public MysqlDatabase getDatabase(String databaseName) + { + return new MysqlDatabase(getConnection(databaseName)); + } + + public List<MysqlDatabase> getDatabases() + { + try + { + List<MysqlDatabase> databases = new ArrayList<>(); + Connection connection = DriverManager.getConnection(url.substring(0, url.length()), username, password); + DatabaseMetaData metadata = connection.getMetaData(); + ResultSet queryResults = metadata.getCatalogs(); + while (queryResults.next()) + { + String databaseName = queryResults.getString("TABLE_CAT"); + databases.add(new MysqlDatabase(getConnection(databaseName))); + } + connection.close(); + return databases; + } + catch (SQLException e) + { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/MysqlQueryHandler.java b/src/main/java/com/redstoner/misc/mysql/MysqlQueryHandler.java new file mode 100644 index 0000000..f89a08a --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/MysqlQueryHandler.java @@ -0,0 +1,33 @@ +package com.redstoner.misc.mysql; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class MysqlQueryHandler { + public static ResultSet queryResult(Connection connection, String query) { + try { + Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery(query); + + return results; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + public static boolean queryNoResult(Connection connection, String query) { + try { + CallableStatement statement = connection.prepareCall(query); + statement.execute(); + + return true; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/ConstraintOperator.java b/src/main/java/com/redstoner/misc/mysql/elements/ConstraintOperator.java new file mode 100644 index 0000000..45cb33c --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/ConstraintOperator.java @@ -0,0 +1,24 @@ +package com.redstoner.misc.mysql.elements; + +public enum ConstraintOperator { + LESS_THAN, GREATER_THAN, EQUAL, NOT_EQUAL, LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL; + + public String toString() { + switch (this) { + case LESS_THAN: + return "<"; + case GREATER_THAN: + return ">"; + case EQUAL: + return "="; + case NOT_EQUAL: + return "!="; + case LESS_THAN_OR_EQUAL: + return "<="; + case GREATER_THAN_OR_EQUAL: + return ">="; + default: + return "="; + } + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/MysqlConstraint.java b/src/main/java/com/redstoner/misc/mysql/elements/MysqlConstraint.java new file mode 100644 index 0000000..d651344 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/MysqlConstraint.java @@ -0,0 +1,24 @@ +package com.redstoner.misc.mysql.elements; + +public class MysqlConstraint { + private String fieldName, value; + private ConstraintOperator operator; + + public MysqlConstraint(String fieldName, ConstraintOperator operator, String value) { + this.fieldName = fieldName; + this.operator = operator; + this.value = value; + } + + public String getFieldName() { + return fieldName; + } + + public String getValue() { + return value; + } + + public ConstraintOperator getOperator() { + return operator; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/MysqlDatabase.java b/src/main/java/com/redstoner/misc/mysql/elements/MysqlDatabase.java new file mode 100644 index 0000000..91c0fe4 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/MysqlDatabase.java @@ -0,0 +1,90 @@ +package com.redstoner.misc.mysql.elements; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.redstoner.misc.mysql.MysqlQueryHandler; + +public class MysqlDatabase { + private Connection connection; + + public MysqlDatabase(Connection connection) { + this.connection = connection; + } + + public String getName() { + try { + return connection.getCatalog(); + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + public MysqlTable getTable(String name) { + return new MysqlTable(this, name); + } + + public boolean createTable(String name, MysqlField... description) { + return MysqlQueryHandler.queryNoResult(connection, "CREATE TABLE `" + name + "` " + getDescription(description) + ";"); + } + + public boolean createTableIfNotExists(String name, MysqlField... description) { + return MysqlQueryHandler.queryNoResult(connection, "CREATE TABLE IF NOT EXISTS `" + name + "` " + getDescription(description) + ";"); + } + + public boolean dropTable(String name) { + return MysqlQueryHandler.queryNoResult(connection, "DROP TABLE `" + name + "`;"); + } + + public boolean drop() { + return MysqlQueryHandler.queryNoResult(connection, "DROP DATABASE `" + getName() + "`;"); + } + + public List<MysqlTable> getTables() { + try { + List<MysqlTable> tables = new ArrayList<>(); + DatabaseMetaData metadata = connection.getMetaData(); + ResultSet queryResults = metadata.getTables(null, null, "%", null); + + while (queryResults.next()) { + tables.add(new MysqlTable(this, queryResults.getString(3))); + } + + return tables; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + protected Connection getConnection() { + return connection; + } + + private String getDescription(MysqlField... description) { + String desc = "("; + + for (int i = 0; i < description.length; i++) { + String nil = ""; + + if (description[i].canBeNull()) { + nil = " NOT NULL"; + } + + desc += "`" + description[i].getName() + "` " + description[i].getType().getName() + nil; + + if (i < description.length - 1) { + desc += ","; + } + } + + desc += ")"; + + return desc; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/MysqlField.java b/src/main/java/com/redstoner/misc/mysql/elements/MysqlField.java new file mode 100644 index 0000000..61cba2e --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/MysqlField.java @@ -0,0 +1,33 @@ +package com.redstoner.misc.mysql.elements; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class MysqlField { + private String name; + private MysqlType type; + private boolean canBeNull; + + public MysqlField(String name, MysqlType type, boolean canBeNull) { + this.name = name; + this.type = type; + this.canBeNull = canBeNull; + } + + public MysqlField(String name, String type, boolean canBeNull) { + this.name = name; + this.type = MysqlType.getTypeFromString(type); + this.canBeNull = canBeNull; + } + + public String getName() { + return name; + } + + public MysqlType getType() { + return type; + } + + public boolean canBeNull() { + return canBeNull; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/MysqlResult.java b/src/main/java/com/redstoner/misc/mysql/elements/MysqlResult.java new file mode 100644 index 0000000..6db0769 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/MysqlResult.java @@ -0,0 +1,16 @@ +package com.redstoner.misc.mysql.elements; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MysqlResult { + private ResultSet results; + + public MysqlResult(ResultSet results) { + this.results = results; + } + + public Object getObject(int columnIndex, Class<?> type) throws SQLException { + return results.getObject(columnIndex, type); + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/elements/MysqlTable.java b/src/main/java/com/redstoner/misc/mysql/elements/MysqlTable.java new file mode 100644 index 0000000..6656fcd --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/elements/MysqlTable.java @@ -0,0 +1,133 @@ +package com.redstoner.misc.mysql.elements; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.redstoner.misc.mysql.MysqlQueryHandler; + +public class MysqlTable +{ + private MysqlDatabase database; + private String name; + + public MysqlTable(MysqlDatabase database, String name) + { + this.database = database; + this.name = name; + } + + public String getName() + { + return this.name; + } + + public MysqlField[] describe() + { + try + { + List<MysqlField> description = new ArrayList<>(); + DatabaseMetaData metadata = database.getConnection().getMetaData(); + ResultSet queryResults = metadata.getColumns(null, null, name, null); + while (queryResults.next()) + { + description.add(new MysqlField(queryResults.getString(4), + queryResults.getString(6).split(" ")[0] + "(" + queryResults.getString(7) + ")", + queryResults.getBoolean(11))); + } + return description.toArray(new MysqlField[0]); + } + catch (SQLException e) + { + e.printStackTrace(); + return null; + } + } + + public boolean insert(String... values) + { + MysqlField[] description = describe(); + if (values.length > 0 && values.length == description.length) + { + String val = "(\"" + String.join("\",\"", values) + "\")"; + return MysqlQueryHandler.queryNoResult(database.getConnection(), + "INSERT INTO `" + name + "` VALUES " + val + ";"); + } + else + { + return false; + } + } + + public Object[] get(String fieldName, MysqlConstraint... constraints) + { + ResultSet results = MysqlQueryHandler.queryResult(database.getConnection(), + "SELECT " + fieldName + " FROM `" + name + "`" + getConstraints(constraints) + ";"); + List<Object> resObj = new ArrayList<>(); + try + { + while (results.next()) + { + resObj.add(results.getObject(1)); + } + } + catch (SQLException e) + { + e.printStackTrace(); + return new Object[0]; + } + return resObj.toArray(new Object[0]); + } + + public Object[] get(String statement) + { + ResultSet results = MysqlQueryHandler.queryResult(database.getConnection(), statement); + List<Object> resObj = new ArrayList<>(); + try + { + while (results.next()) + { + resObj.add(results.getObject(1)); + } + } + catch (SQLException e) + { + e.printStackTrace(); + return new Object[0]; + } + return resObj.toArray(new Object[0]); + } + + public boolean delete(MysqlConstraint... constraints) + { + return MysqlQueryHandler.queryNoResult(database.getConnection(), + "DELETE FROM `" + name + "`" + getConstraints(constraints) + ";"); + } + + public boolean drop() + { + return MysqlQueryHandler.queryNoResult(database.getConnection(), "DROP TABLE `" + name + "`;"); + } + + private String getConstraints(MysqlConstraint... constraints) + { + String cons = ""; + if (constraints.length > 0) + { + cons += " WHERE "; + for (int i = 0; i < constraints.length; i++) + { + MysqlConstraint constraint = constraints[i]; + cons += constraint.getFieldName() + constraint.getOperator().toString() + "\"" + constraint.getValue() + + "\""; + if (i < constraints.length - 1) + { + cons += " AND "; + } + } + } + return cons; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/MysqlType.java b/src/main/java/com/redstoner/misc/mysql/types/MysqlType.java new file mode 100644 index 0000000..86413f9 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/MysqlType.java @@ -0,0 +1,96 @@ +package com.redstoner.misc.mysql.types; + +import com.redstoner.misc.mysql.types.date.Date; +import com.redstoner.misc.mysql.types.date.DateTime; +import com.redstoner.misc.mysql.types.date.Time; +import com.redstoner.misc.mysql.types.date.TimeStamp; +import com.redstoner.misc.mysql.types.date.Year; +import com.redstoner.misc.mysql.types.number.BigInt; +import com.redstoner.misc.mysql.types.number.Decimal; +import com.redstoner.misc.mysql.types.number.Double; +import com.redstoner.misc.mysql.types.number.Float; +import com.redstoner.misc.mysql.types.number.Int; +import com.redstoner.misc.mysql.types.number.MediumInt; +import com.redstoner.misc.mysql.types.number.SmallInt; +import com.redstoner.misc.mysql.types.number.TinyInt; +import com.redstoner.misc.mysql.types.text.Blob; +import com.redstoner.misc.mysql.types.text.Char; +import com.redstoner.misc.mysql.types.text.Enum; +import com.redstoner.misc.mysql.types.text.LongBlob; +import com.redstoner.misc.mysql.types.text.LongText; +import com.redstoner.misc.mysql.types.text.MediumBlob; +import com.redstoner.misc.mysql.types.text.MediumText; +import com.redstoner.misc.mysql.types.text.Set; +import com.redstoner.misc.mysql.types.text.Text; +import com.redstoner.misc.mysql.types.text.TinyText; +import com.redstoner.misc.mysql.types.text.VarChar; + +public abstract class MysqlType +{ + public abstract String getName(); + + public static MysqlType getTypeFromString(String type) + { + String[] splitType = type.split("\\("); + String toSwitch = splitType[0].toUpperCase(); + String value = ""; + if (type.contains("(") && type.endsWith(")")) + { + value = splitType[1].substring(0, splitType[1].length() - 1); + } + switch (toSwitch) + { + case "CHAR": + return new Char(Integer.valueOf(value)); + case "ENUM": + return new Enum(value.replaceAll("'", "").split(",")); + case "VARCHAR": + return new VarChar(Integer.valueOf(value)); + case "SET": + return new Set(value.replaceAll("'", "").split(",")); + case "BLOB": + return new Blob(); + case "TEXT": + return new Text(); + case "MEDIUMBLOB": + return new MediumBlob(); + case "LONGBLOB": + return new LongBlob(); + case "TINYTEXT": + return new TinyText(); + case "MEDIUMTEXT": + return new MediumText(); + case "LONGTEXT": + return new LongText(); + case "INT": + return new Int(Integer.valueOf(value)); + case "TINYINT": + return new TinyInt(Integer.valueOf(value)); + case "SMALLINT": + return new SmallInt(Integer.valueOf(value)); + case "MEDIUMINT": + return new MediumInt(Integer.valueOf(value)); + case "BIGINT": + return new BigInt(Integer.valueOf(value)); + case "BIT": + return new TinyInt(1); + case "FLOAT": + return new Float(); + case "DOUBLE": + return new Double(); + case "DECIMAL": + return new Decimal(); + case "DATE": + return new Date(); + case "DATETIME": + return new DateTime(); + case "TIME": + return new Time(); + case "TIMESTAMP": + return new TimeStamp(); + case "YEAR": + return new Year(); + } + return null; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/date/Date.java b/src/main/java/com/redstoner/misc/mysql/types/date/Date.java new file mode 100644 index 0000000..9943333 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/date/Date.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.date; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Date extends MysqlType { + @Override + public String getName() { + return "DATE"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/date/DateTime.java b/src/main/java/com/redstoner/misc/mysql/types/date/DateTime.java new file mode 100644 index 0000000..b4d9623 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/date/DateTime.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.date; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class DateTime extends MysqlType { + @Override + public String getName() { + return "DATETIME"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/date/Time.java b/src/main/java/com/redstoner/misc/mysql/types/date/Time.java new file mode 100644 index 0000000..4ead72c --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/date/Time.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.date; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Time extends MysqlType { + @Override + public String getName() { + return "TIME"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/date/TimeStamp.java b/src/main/java/com/redstoner/misc/mysql/types/date/TimeStamp.java new file mode 100644 index 0000000..56205af --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/date/TimeStamp.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.date; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class TimeStamp extends MysqlType { + @Override + public String getName() { + return "TIMESTAMP"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/date/Year.java b/src/main/java/com/redstoner/misc/mysql/types/date/Year.java new file mode 100644 index 0000000..4c2ce1a --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/date/Year.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.date; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Year extends MysqlType { + @Override + public String getName() { + return "YEAR"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/BigInt.java b/src/main/java/com/redstoner/misc/mysql/types/number/BigInt.java new file mode 100644 index 0000000..71086fd --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/BigInt.java @@ -0,0 +1,12 @@ +package com.redstoner.misc.mysql.types.number; + +public class BigInt extends Int { + public BigInt(int maxSize) { + super(maxSize); + } + + @Override + public String getName() { + return "BIG" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/Decimal.java b/src/main/java/com/redstoner/misc/mysql/types/number/Decimal.java new file mode 100644 index 0000000..4e4dbb6 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/Decimal.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.number; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Decimal extends MysqlType { + @Override + public String getName() { + return "DECIMAL"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/Double.java b/src/main/java/com/redstoner/misc/mysql/types/number/Double.java new file mode 100644 index 0000000..b4b1dda --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/Double.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.number; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Double extends MysqlType { + @Override + public String getName() { + return "DOUBLE"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/Float.java b/src/main/java/com/redstoner/misc/mysql/types/number/Float.java new file mode 100644 index 0000000..ea3047e --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/Float.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.number; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Float extends MysqlType { + @Override + public String getName() { + return "FLOAT"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/Int.java b/src/main/java/com/redstoner/misc/mysql/types/number/Int.java new file mode 100644 index 0000000..4256f7b --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/Int.java @@ -0,0 +1,16 @@ +package com.redstoner.misc.mysql.types.number; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Int extends MysqlType { + private int maxSize; + + public Int(int maxSize) { + this.maxSize = maxSize; + } + + @Override + public String getName() { + return "INT(" + maxSize + ")"; + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/MediumInt.java b/src/main/java/com/redstoner/misc/mysql/types/number/MediumInt.java new file mode 100644 index 0000000..fbcb0f4 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/MediumInt.java @@ -0,0 +1,12 @@ +package com.redstoner.misc.mysql.types.number; + +public class MediumInt extends Int { + public MediumInt(int maxSize) { + super(maxSize); + } + + @Override + public String getName() { + return "MEDIUM" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/SmallInt.java b/src/main/java/com/redstoner/misc/mysql/types/number/SmallInt.java new file mode 100644 index 0000000..01bf97d --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/SmallInt.java @@ -0,0 +1,12 @@ +package com.redstoner.misc.mysql.types.number; + +public class SmallInt extends Int { + public SmallInt(int maxSize) { + super(maxSize); + } + + @Override + public String getName() { + return "SMALL" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/number/TinyInt.java b/src/main/java/com/redstoner/misc/mysql/types/number/TinyInt.java new file mode 100644 index 0000000..63ad700 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/number/TinyInt.java @@ -0,0 +1,12 @@ +package com.redstoner.misc.mysql.types.number; + +public class TinyInt extends Int { + public TinyInt(int maxSize) { + super(maxSize); + } + + @Override + public String getName() { + return "TINY" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/Blob.java b/src/main/java/com/redstoner/misc/mysql/types/text/Blob.java new file mode 100644 index 0000000..d56ee45 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/Blob.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Blob extends MysqlType { + @Override + public String getName() { + return "BLOB"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/Char.java b/src/main/java/com/redstoner/misc/mysql/types/text/Char.java new file mode 100644 index 0000000..ece068c --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/Char.java @@ -0,0 +1,16 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Char extends MysqlType { + private int size; + + public Char(int size) { + this.size = size; + } + + @Override + public String getName() { + return "CHAR(" + size + ")"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/Enum.java b/src/main/java/com/redstoner/misc/mysql/types/text/Enum.java new file mode 100644 index 0000000..e68476d --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/Enum.java @@ -0,0 +1,27 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Enum extends MysqlType { + private String[] possibleValues; + + public Enum(String... possibleValues) { + this.possibleValues = possibleValues; + } + + @Override + public String getName() { + String name = "ENUM("; + + for (int i = 0; i < possibleValues.length; i++) { + name += "'" + possibleValues[i] + "'"; + + if (i != possibleValues.length - 1) { + name += ","; + } + } + + return name + ")"; + } + +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/LongBlob.java b/src/main/java/com/redstoner/misc/mysql/types/text/LongBlob.java new file mode 100644 index 0000000..802caed --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/LongBlob.java @@ -0,0 +1,8 @@ +package com.redstoner.misc.mysql.types.text; + +public class LongBlob extends Blob { + @Override + public String getName() { + return "LONG" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/LongText.java b/src/main/java/com/redstoner/misc/mysql/types/text/LongText.java new file mode 100644 index 0000000..5149ed2 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/LongText.java @@ -0,0 +1,8 @@ +package com.redstoner.misc.mysql.types.text; + +public class LongText extends Text { + @Override + public String getName() { + return "LONG" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/MediumBlob.java b/src/main/java/com/redstoner/misc/mysql/types/text/MediumBlob.java new file mode 100644 index 0000000..6ae25a7 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/MediumBlob.java @@ -0,0 +1,8 @@ +package com.redstoner.misc.mysql.types.text; + +public class MediumBlob extends Blob { + @Override + public String getName() { + return "MEDIUM" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/MediumText.java b/src/main/java/com/redstoner/misc/mysql/types/text/MediumText.java new file mode 100644 index 0000000..a0f6525 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/MediumText.java @@ -0,0 +1,8 @@ +package com.redstoner.misc.mysql.types.text; + +public class MediumText extends Text { + @Override + public String getName() { + return "MEDIUM" + super.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/Set.java b/src/main/java/com/redstoner/misc/mysql/types/text/Set.java new file mode 100644 index 0000000..4e12ce6 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/Set.java @@ -0,0 +1,27 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Set extends MysqlType { + private String[] possibleValues; + + public Set(String... possibleValues) { + this.possibleValues = possibleValues; + } + + @Override + public String getName() { + String name = "SET("; + + for (int i = 0; i < possibleValues.length; i++) { + name += "'" + possibleValues[i] + "'"; + + if (i != possibleValues.length - 1) { + name += ","; + } + } + + return name + ")"; + } + +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/Text.java b/src/main/java/com/redstoner/misc/mysql/types/text/Text.java new file mode 100644 index 0000000..7d1a38c --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/Text.java @@ -0,0 +1,10 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class Text extends MysqlType { + @Override + public String getName() { + return "TEXT"; + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/TinyText.java b/src/main/java/com/redstoner/misc/mysql/types/text/TinyText.java new file mode 100644 index 0000000..e78160c --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/TinyText.java @@ -0,0 +1,8 @@ +package com.redstoner.misc.mysql.types.text; + +public class TinyText extends Text { + @Override + public String getName() { + return "TINY" + super.getName(); + } +} diff --git a/src/main/java/com/redstoner/misc/mysql/types/text/VarChar.java b/src/main/java/com/redstoner/misc/mysql/types/text/VarChar.java new file mode 100644 index 0000000..cb28ad1 --- /dev/null +++ b/src/main/java/com/redstoner/misc/mysql/types/text/VarChar.java @@ -0,0 +1,16 @@ +package com.redstoner.misc.mysql.types.text; + +import com.redstoner.misc.mysql.types.MysqlType; + +public class VarChar extends MysqlType { + private int maxSize; + + public VarChar(int maxSize) { + this.maxSize = maxSize; + } + + @Override + public String getName() { + return "VARCHAR(" + maxSize + ")"; + } +}
\ No newline at end of file diff --git a/src/main/java/com/redstoner/modules/CoreModule.java b/src/main/java/com/redstoner/modules/CoreModule.java new file mode 100644 index 0000000..9f71557 --- /dev/null +++ b/src/main/java/com/redstoner/modules/CoreModule.java @@ -0,0 +1,24 @@ +package com.redstoner.modules; + +import com.redstoner.annotations.Version; + +/** This class shall be used for "CoreModules", which are acting on a lower level than modules and are also exempted from being disabled or reloaded on the go.</br> + * Please note that CoreModules will not be known to the ModuleLoader itself!</br> + * Examples are the ModuleLoader and the Debugger. + * + * @author Pepich */ +@Version(major = 2, minor = 0, revision = 0, compatible = -1) +public interface CoreModule extends Module +{ + /** Core modules don't need to be enabled. */ + @Override + public default boolean onEnable() + { + return true; + } + + /** Core modules don't need to be disabled. */ + @Override + public default void onDisable() + {} +} diff --git a/src/main/java/com/redstoner/modules/Module.java b/src/main/java/com/redstoner/modules/Module.java new file mode 100644 index 0000000..1c89e15 --- /dev/null +++ b/src/main/java/com/redstoner/modules/Module.java @@ -0,0 +1,54 @@ +package com.redstoner.modules; + +import com.redstoner.annotations.Version; +import com.redstoner.coremods.moduleLoader.ModuleLoader; + +/** Interface for the Module class. Modules must always have an empty constructor to be invoked by the ModuleLoader. + * + * @author Pepich */ +@Version(major = 4, minor = 0, revision = 0, compatible = 0) +public interface Module +{ + /** Will be called when the module gets enabled. */ + public default boolean onEnable() + { + return true; + } + + /** This methods gets called after all modules were enabled, please use this method to register commands and similar. <br/> + * It will only get called if and only if the module was successfully enabled. */ + public default void postEnable() + {} + + /** Will be called when the module gets disabled. */ + public default void onDisable() + {} + + /** Gets called on registration of the module, when this option is selected for command registration + * + * @return The String used for the CommandManager to register the commands. */ + public default String getCommandString() + { + return null; + } + + public default ModuleLogger getLogger() + { + return ModuleLoader.getModuleLogger(this); + } + + /** This method gets run the very first time a module gets loaded. You can use this to set up file structures or background data. */ + public default void firstLoad() + {} + + /** This method gets run every time a module gets loaded and its version has changed. + * + * @param old The version of the previous module. */ + public default void migrate(Version old) + {} + + default void setPrefix(final String name) + { + getLogger().setName(name); + } +} diff --git a/src/main/java/com/redstoner/modules/ModuleLogger.java b/src/main/java/com/redstoner/modules/ModuleLogger.java new file mode 100644 index 0000000..11d71b6 --- /dev/null +++ b/src/main/java/com/redstoner/modules/ModuleLogger.java @@ -0,0 +1,77 @@ +package com.redstoner.modules; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; + +import com.redstoner.annotations.Version; + +import net.nemez.chatapi.ChatAPI; +import net.nemez.chatapi.click.Message; + +@Version(major = 4, minor = 0, revision = 0, compatible = -1) +public class ModuleLogger +{ + public static final String PREFIX_WARN = "§8[§eWARN§8]:§7 "; + public static final String PREFIX_ERROR = "§8[§cERROR§8]:§7 "; + public static final String PREFIX_INFO = "§8[§fINFO§8]:§7 "; + + private String name; + + public ModuleLogger(final String name) + { + this.name = name; + } + + public void info(final String message) + { + Bukkit.getConsoleSender().sendMessage(PREFIX_INFO + getPrefix() + ChatAPI.colorify(null, message)); + } + + public void warn(final String message) + { + Bukkit.getConsoleSender().sendMessage(PREFIX_WARN + getPrefix() + ChatAPI.colorify(null, message)); + } + + public void error(final String message) + { + Bukkit.getConsoleSender().sendMessage(PREFIX_ERROR + getPrefix() + ChatAPI.colorify(null, message)); + } + + public void message(final CommandSender recipient, final String... message) + { + message(recipient, false, message); + } + + public void message(final CommandSender recipient, final boolean error, final String... message) + { + Message m = new Message(recipient, null); + if (message.length == 1) + m.appendText(getPrefix(error) + message[0]); + else + { + m.appendText(getHeader()); + m.appendText("&7" + String.join("\n&7", message)); + } + m.send(); + } + + public String getPrefix() + { + return getPrefix(false); + } + + public String getPrefix(final boolean error) + { + return "§8[§" + (error ? 'c' : '2') + name + "§8]§7 "; + } + + public String getHeader() + { + return "§2--=[ " + name + " ]=--\n"; + } + + protected final void setName(final String name) + { + this.name = name; + } +} |