From 0cacc495ad8ab3ccc95041b195381b990d4eb58a Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 8 May 2017 13:28:19 +0300 Subject: [PATCH] [3.0.0] Release - Updated javadocs partially complete - Fixed possible memory leaks from the queues - Fixed a bug where new player didn't save to the cache. - Stable enough for release. --- .gitignore | 6 +- .../main/java/com/djrapitops/plan/Log.java | 15 +- .../java/com/djrapitops/plan/Permissions.java | 1 + .../main/java/com/djrapitops/plan/Phrase.java | 1 + .../main/java/com/djrapitops/plan/Plan.java | 8 +- .../java/com/djrapitops/plan/Settings.java | 9 +- .../java/com/djrapitops/plan/api/API.java | 11 +- .../java/com/djrapitops/plan/api/Gender.java | 1 + .../djrapitops/plan/command/CommandType.java | 1 + .../djrapitops/plan/command/PlanCommand.java | 1 + .../djrapitops/plan/command/SubCommand.java | 1 + .../plan/command/commands/AnalyzeCommand.java | 1 + .../plan/command/commands/HelpCommand.java | 1 + .../plan/command/commands/InfoCommand.java | 1 + .../plan/command/commands/InspectCommand.java | 1 + .../plan/command/commands/ManageCommand.java | 12 +- .../command/commands/QuickAnalyzeCommand.java | 11 +- .../command/commands/QuickInspectCommand.java | 6 +- .../plan/command/commands/ReloadCommand.java | 3 +- .../plan/command/commands/SearchCommand.java | 8 +- .../commands/manage/ManageBackupCommand.java | 4 +- .../commands/manage/ManageClearCommand.java | 10 +- .../commands/manage/ManageHelpCommand.java | 14 +- .../commands/manage/ManageHotswapCommand.java | 3 + .../commands/manage/ManageImportCommand.java | 6 +- .../commands/manage/ManageMoveCommand.java | 23 +- .../commands/manage/ManageRemoveCommand.java | 15 +- .../commands/manage/ManageRestoreCommand.java | 13 +- .../commands/manage/ManageStatusCommand.java | 18 +- .../djrapitops/plan/data/AnalysisData.java | 337 +++++++++++---- .../plan/data/DemographicsData.java | 42 +- .../com/djrapitops/plan/data/KillData.java | 23 +- .../djrapitops/plan/data/RawAnalysisData.java | 5 +- .../com/djrapitops/plan/data/SessionData.java | 35 +- .../com/djrapitops/plan/data/UserData.java | 383 +++++++++++++----- .../plan/data/cache/DataCacheHandler.java | 30 +- .../plan/data/cache/SessionCache.java | 4 +- .../data/cache/queue/DataCacheClearQueue.java | 22 +- .../data/cache/queue/DataCacheGetQueue.java | 21 +- .../cache/queue/DataCacheProcessQueue.java | 51 ++- .../data/cache/queue/DataCacheSaveQueue.java | 39 +- .../plan/data/handling/GamemodeHandling.java | 4 +- .../plan/data/handling/LoginHandling.java | 5 - .../data/listeners/PlanPlayerListener.java | 10 +- .../plan/database/databases/SQLDB.java | 10 +- .../plan/database/tables/GMTimesTable.java | 7 +- .../plan/database/tables/IPsTable.java | 4 +- .../plan/database/tables/NicknamesTable.java | 4 +- .../java/com/djrapitops/plan/ui/Html.java | 299 -------------- .../djrapitops/plan/utilities/Analysis.java | 2 +- .../plan/utilities/NewPlayerCreator.java | 2 +- .../plan/utilities/PlaceholderUtils.java | 5 +- .../djrapitops/plan/data/UserDataTest.java | 9 +- .../queue/DataCacheProcessQueueTest.java | 3 +- .../cache/queue/DataCacheSaveQueueTest.java | 9 - docs/Plan-javadoc.jar | Bin 83534 -> 0 bytes 56 files changed, 840 insertions(+), 730 deletions(-) delete mode 100644 docs/Plan-javadoc.jar diff --git a/.gitignore b/.gitignore index f68374daf..7276b0495 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ /dist/ /build/ /nbproject/ -/Plan Lite/build/ -/Plan Lite/dist/ -/Plan Lite/nbproject/private/ /Plan/nbproject/ /Plan/target/ /Plan/temporaryTestFolder/ /Debugger/nbproject/private/ -/PlanDebugger/nbproject/private/ \ No newline at end of file +/PlanDebugger/ +/Plan Lite/ \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/Log.java b/Plan/src/main/java/com/djrapitops/plan/Log.java index 23a7e839f..357df98ad 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Log.java +++ b/Plan/src/main/java/com/djrapitops/plan/Log.java @@ -9,13 +9,15 @@ import java.util.Date; import main.java.com.djrapitops.plan.utilities.FormatUtils; /** + * This class manages the messages going to the Bukkit's Logger. * * @author Rsl1122 + * @since 3.0.0 */ public class Log { /** - * Logs the message to the console. + * Logs the message to the console as INFO. * * @param message "Message" will show up as [INFO][Plan]: Message */ @@ -27,17 +29,22 @@ public class Log { } /** - * Logs an error message to the console. + * Logs an error message to the console as ERROR. * * @param message "Message" will show up as [ERROR][Plan]: Message */ - public static void errorMsg(String message) { + public static void error(String message) { Plan instance = Plan.getInstance(); if (instance != null) { instance.getLogger().severe(message); } } + /** + * Logs a debug message to the console as INFO if Settings.Debug is true. + * + * @param message "Message" will show up as [INFO][Plan]: [DEBUG] Message + */ public static void debug(String message) { if (Settings.DEBUG.isTrue()) { info("[DEBUG] " + message); @@ -51,7 +58,7 @@ public class Log { * @param e Throwable, eg NullPointerException */ public static void toLog(String source, Throwable e) { - errorMsg(Phrase.ERROR_LOGGED.parse(e.toString())); + error(Phrase.ERROR_LOGGED.parse(e.toString())); toLog(source + " Caught " + e); for (StackTraceElement x : e.getStackTrace()) { toLog(" " + x); diff --git a/Plan/src/main/java/com/djrapitops/plan/Permissions.java b/Plan/src/main/java/com/djrapitops/plan/Permissions.java index d200aff90..174075a69 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Permissions.java +++ b/Plan/src/main/java/com/djrapitops/plan/Permissions.java @@ -6,6 +6,7 @@ import org.bukkit.command.CommandSender; * Permissions class is used easily check every permission node. * * @author Rsl1122 + * @since 3.0.0 */ public enum Permissions { diff --git a/Plan/src/main/java/com/djrapitops/plan/Phrase.java b/Plan/src/main/java/com/djrapitops/plan/Phrase.java index 531a948bd..68b0330c7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Phrase.java +++ b/Plan/src/main/java/com/djrapitops/plan/Phrase.java @@ -14,6 +14,7 @@ import static org.bukkit.plugin.java.JavaPlugin.getPlugin; * or ChatColor. * * @author Rsl1122 + * @since 2.0.0 */ public enum Phrase { REPLACE0("REPLACE0"), diff --git a/Plan/src/main/java/com/djrapitops/plan/Plan.java b/Plan/src/main/java/com/djrapitops/plan/Plan.java index 3e0d77b27..62fa32fca 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Plan.java +++ b/Plan/src/main/java/com/djrapitops/plan/Plan.java @@ -22,11 +22,9 @@ package main.java.com.djrapitops.plan; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintWriter; import java.net.URL; import java.util.Collection; import java.util.Date; @@ -42,7 +40,6 @@ import main.java.com.djrapitops.plan.database.Database; import main.java.com.djrapitops.plan.database.databases.*; import main.java.com.djrapitops.plan.ui.Html; import main.java.com.djrapitops.plan.ui.webserver.WebSocketServer; -import main.java.com.djrapitops.plan.utilities.FormatUtils; import main.java.com.djrapitops.plan.utilities.MiscUtils; import org.bukkit.Bukkit; import org.bukkit.command.ConsoleCommandSender; @@ -56,6 +53,7 @@ import org.bukkit.scheduler.BukkitTask; * the Bukkit console & various get methods. * * @author Rsl1122 + * @since 1.0.0 */ public class Plan extends JavaPlugin { @@ -99,7 +97,7 @@ public class Plan extends JavaPlugin { if (initDatabase()) { Log.info(Phrase.DB_ESTABLISHED.parse(db.getConfigName())); } else { - Log.errorMsg(Phrase.DB_FAILURE_DISABLE.toString()); + Log.error(Phrase.DB_FAILURE_DISABLE.toString()); getServer().getPluginManager().disablePlugin(this); return; } @@ -179,7 +177,7 @@ public class Plan extends JavaPlugin { */ @Deprecated public void logError(String message) { - Log.errorMsg(message); + Log.error(message); } /** diff --git a/Plan/src/main/java/com/djrapitops/plan/Settings.java b/Plan/src/main/java/com/djrapitops/plan/Settings.java index 0e0cc7c39..bd54b0c3f 100644 --- a/Plan/src/main/java/com/djrapitops/plan/Settings.java +++ b/Plan/src/main/java/com/djrapitops/plan/Settings.java @@ -1,12 +1,11 @@ package main.java.com.djrapitops.plan; -import static org.bukkit.plugin.java.JavaPlugin.getPlugin; - /** * This enum contains all of the config settings used by the plugin for easier * access. * * @author Rsl1122 + * @since 2.3.2 */ public enum Settings { // Boolean @@ -70,7 +69,7 @@ public enum Settings { * @return Boolean value of the config setting, false if not boolean. */ public boolean isTrue() { - return getPlugin(Plan.class).getConfig().getBoolean(configPath); + return Plan.getInstance().getConfig().getBoolean(configPath); } /** @@ -80,7 +79,7 @@ public enum Settings { */ @Override public String toString() { - return getPlugin(Plan.class).getConfig().getString(configPath); + return Plan.getInstance().getConfig().getString(configPath); } /** @@ -89,7 +88,7 @@ public enum Settings { * @return Integer value of the config setting */ public int getNumber() { - return getPlugin(Plan.class).getConfig().getInt(configPath); + return Plan.getInstance().getConfig().getInt(configPath); } /** diff --git a/Plan/src/main/java/com/djrapitops/plan/api/API.java b/Plan/src/main/java/com/djrapitops/plan/api/API.java index d638e30ed..e90945481 100644 --- a/Plan/src/main/java/com/djrapitops/plan/api/API.java +++ b/Plan/src/main/java/com/djrapitops/plan/api/API.java @@ -11,8 +11,12 @@ import main.java.com.djrapitops.plan.utilities.FormatUtils; import main.java.com.djrapitops.plan.utilities.UUIDFetcher; /** + * This class contains the API methods. + *

+ * Revamp incoming in 3.1.0 * * @author Rsl1122 + * @since 2.0.0 */ public class API { @@ -102,8 +106,9 @@ public class API { * Returns the ip:port/player/playername html as a string so it can be * integrated into other webserver plugins. * - * Should use cacheUserDataToInspectCache(UUID uuid) before using this method. - * + * Should use cacheUserDataToInspectCache(UUID uuid) before using this + * method. + * * If UserData of the specified player is not in the Cache returns

404 * Data was not found in cache

* @@ -134,7 +139,7 @@ public class API { * other webserver plugins. * * Should use updateAnalysisCache() before using this method. - * + * * If AnalysisData is not in the AnalysisCache: returns

404 Data was not * found in cache

* diff --git a/Plan/src/main/java/com/djrapitops/plan/api/Gender.java b/Plan/src/main/java/com/djrapitops/plan/api/Gender.java index c08e7c619..c21b25a8c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/api/Gender.java +++ b/Plan/src/main/java/com/djrapitops/plan/api/Gender.java @@ -4,6 +4,7 @@ package main.java.com.djrapitops.plan.api; * This class contains Genders used by the plugin. * * @author Rsl1122 + * @since 2.0.0 */ public enum Gender { MALE, FEMALE, OTHER, UNKNOWN; diff --git a/Plan/src/main/java/com/djrapitops/plan/command/CommandType.java b/Plan/src/main/java/com/djrapitops/plan/command/CommandType.java index 71fe1f3ca..925a64178 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/CommandType.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/CommandType.java @@ -7,6 +7,7 @@ package main.java.com.djrapitops.plan.command; * CONSOLE_WITH_ARGUMENTS can be used always, except with arguments on console. * * @author Rsl1122 + * @since 1.0.0 */ public enum CommandType { CONSOLE, diff --git a/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java index bc2c999b9..5bf0c1734 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/PlanCommand.java @@ -15,6 +15,7 @@ import org.bukkit.entity.Player; * CommandExecutor for the /plan command, and all subcommands. * * @author Rsl1122 + * @since 1.0.0 */ public class PlanCommand implements CommandExecutor { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/SubCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/SubCommand.java index afb4c0277..ef1dbf88e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/SubCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/SubCommand.java @@ -9,6 +9,7 @@ import org.bukkit.command.CommandSender; * command. * * @author Rsl1122 + * @since 1.0.0 */ public abstract class SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java index b06234f6b..01e05757a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/AnalyzeCommand.java @@ -22,6 +22,7 @@ import org.bukkit.scheduler.BukkitTask; * This subcommand is used to run the analysis and access the /server link. * * @author Rsl1122 + * @since 2.0.0 */ public class AnalyzeCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/HelpCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/HelpCommand.java index 9f40b5025..787f7d941 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/HelpCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/HelpCommand.java @@ -15,6 +15,7 @@ import org.bukkit.entity.Player; * This subcommand is used to view the subcommands. * * @author Rsl1122 + * @since 1.0.0 */ public class HelpCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/InfoCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/InfoCommand.java index d6925c92d..3bbea48f5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/InfoCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/InfoCommand.java @@ -14,6 +14,7 @@ import org.bukkit.command.CommandSender; * This subcommand is used to view the version & the database type in use. * * @author Rsl1122 + * @since 2.0.0 */ public class InfoCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java index 323fee5b5..288b9444a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/InspectCommand.java @@ -24,6 +24,7 @@ import org.bukkit.scheduler.BukkitTask; * This command is used to cache UserData to InspectCache and display the link. * * @author Rsl1122 + * @since 1.0.0 */ public class InspectCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/ManageCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/ManageCommand.java index 078f98a70..266accc52 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/ManageCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/ManageCommand.java @@ -14,8 +14,12 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; /** + * This command is used to manage the database of the plugin. + *

+ * No arguments will run ManageHelpCommand. Contains subcommands. * * @author Rsl1122 + * @since 2.3.0 */ public class ManageCommand extends SubCommand { @@ -28,7 +32,7 @@ public class ManageCommand extends SubCommand { * @param plugin Current instance of Plan */ public ManageCommand(Plan plugin) { - super("manage, m", Permissions.MANAGE, Phrase.CMD_USG_MANAGE+"", CommandType.CONSOLE, ""); + super("manage, m", Permissions.MANAGE, Phrase.CMD_USG_MANAGE + "", CommandType.CONSOLE, ""); this.plugin = plugin; commands = new ArrayList<>(); commands.add(new ManageHelpCommand(plugin, this)); @@ -39,10 +43,12 @@ public class ManageCommand extends SubCommand { commands.add(new ManageStatusCommand(plugin)); commands.add(new ManageImportCommand(plugin)); commands.add(new ManageRemoveCommand(plugin)); - commands.add(new ManageClearCommand(plugin)); + commands.add(new ManageClearCommand(plugin)); } /** + * Used to get the list of manage subcommands. + * * @return Initialized SubCommands */ public List getCommands() { @@ -105,7 +111,7 @@ public class ManageCommand extends SubCommand { } if (console && args.length < 2 && command.getCommandType() == CommandType.CONSOLE_WITH_ARGUMENTS) { - sender.sendMessage("" + Phrase.COMMAND_REQUIRES_ARGUMENTS.parse(Phrase.USE_MANAGE+"")); + sender.sendMessage("" + Phrase.COMMAND_REQUIRES_ARGUMENTS.parse(Phrase.USE_MANAGE + "")); return true; } diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickAnalyzeCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickAnalyzeCommand.java index 253c720ca..6d3237f07 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickAnalyzeCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickAnalyzeCommand.java @@ -4,24 +4,21 @@ import java.util.Date; import main.java.com.djrapitops.plan.Permissions; import main.java.com.djrapitops.plan.Phrase; import main.java.com.djrapitops.plan.Plan; -import main.java.com.djrapitops.plan.Settings; import main.java.com.djrapitops.plan.command.CommandType; import main.java.com.djrapitops.plan.command.SubCommand; import main.java.com.djrapitops.plan.data.cache.AnalysisCacheHandler; import main.java.com.djrapitops.plan.ui.TextUI; -import main.java.com.djrapitops.plan.utilities.HtmlUtils; -import org.bukkit.Bukkit; import org.bukkit.command.Command; -import org.bukkit.command.CommandException; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; /** - * This subcommand is used to run the analysis and access the /server link. + * This subcommand is used to run the analysis and to view some of the data in + * game. * * @author Rsl1122 + * @since 3.0.0 */ public class QuickAnalyzeCommand extends SubCommand { @@ -71,7 +68,7 @@ public class QuickAnalyzeCommand extends SubCommand { public void run() { timesrun++; if (analysisCache.isCached()) { - sender.sendMessage(Phrase.CMD_ANALYZE_HEADER + ""); + sender.sendMessage(Phrase.CMD_ANALYZE_HEADER + ""); sender.sendMessage(TextUI.getAnalysisMessages()); sender.sendMessage(Phrase.CMD_FOOTER + ""); this.cancel(); diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickInspectCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickInspectCommand.java index 6a4b5cfda..ebcb6ebc0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickInspectCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/QuickInspectCommand.java @@ -9,21 +9,21 @@ import main.java.com.djrapitops.plan.command.CommandType; import main.java.com.djrapitops.plan.command.SubCommand; import main.java.com.djrapitops.plan.data.cache.InspectCacheHandler; import main.java.com.djrapitops.plan.ui.TextUI; -import main.java.com.djrapitops.plan.utilities.HtmlUtils; import main.java.com.djrapitops.plan.utilities.MiscUtils; import main.java.com.djrapitops.plan.utilities.UUIDFetcher; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; /** - * This command is used to cache UserData to InspectCache and display the link. + * This command is used to cache UserData to InspectCache and to view some of + * the data in game. * * @author Rsl1122 + * @since 3.0.0 */ public class QuickInspectCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/ReloadCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/ReloadCommand.java index 05286986f..0af309c1f 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/ReloadCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/ReloadCommand.java @@ -9,8 +9,10 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandSender; /** + * This subcommand is used to reload the plugin. * * @author Rsl1122 + * @since 2.0.0 */ public class ReloadCommand extends SubCommand { @@ -33,7 +35,6 @@ public class ReloadCommand extends SubCommand { plugin.onDisable(); plugin.onEnable(); sender.sendMessage(Phrase.RELOAD_COMPLETE + ""); - return true; } diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/SearchCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/SearchCommand.java index 59d4fbb32..0fe5dddbf 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/SearchCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/SearchCommand.java @@ -22,8 +22,10 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; /** + * This subcommand is used to search for a user, and to view all matches' data. * * @author Rsl1122 + * @since 2.0.0 */ public class SearchCommand extends SubCommand { @@ -44,7 +46,7 @@ public class SearchCommand extends SubCommand { /** * Subcommand search. * - * Searches database for matching playernames and caches matching PlayerData + * Searches database for matching playernames and caches matching UserData * to InspectCache. Shows all links to matching players data. * * @param sender @@ -110,10 +112,10 @@ public class SearchCommand extends SubCommand { Bukkit.getServer().dispatchCommand( Bukkit.getConsoleSender(), "tellraw " + player.getName() + " [\"\",{\"text\":\"Click Me\",\"underlined\":true," - + "\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + url + "\"}}]"); + + "\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + url + "\"}}]"); } }.runTask(plugin); - + } } } diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageBackupCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageBackupCommand.java index 5d5e71dbe..4c1fd8f0f 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageBackupCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageBackupCommand.java @@ -12,8 +12,10 @@ import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; /** + * This manage subcommand is used to backup a database to a .db file. * * @author Rsl1122 + * @since 2.3.0 */ public class ManageBackupCommand extends SubCommand { @@ -36,7 +38,7 @@ public class ManageBackupCommand extends SubCommand { * @param sender * @param cmd * @param commandLabel - * @param args Player's name or nothing - if empty sender's name is used. + * @param args mysql or sqlite, -a to confirm. * @return true in all cases. */ @Override diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageClearCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageClearCommand.java index d0c744550..066044f70 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageClearCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageClearCommand.java @@ -13,11 +13,13 @@ import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; /** + * This manage subcommand is used to clear a database of all data. * * @author Rsl1122 + * @since 2.3.0 */ public class ManageClearCommand extends SubCommand { - + private final Plan plugin; /** @@ -27,7 +29,7 @@ public class ManageClearCommand extends SubCommand { */ public ManageClearCommand(Plan plugin) { super("clear", Permissions.MANAGE, Phrase.CMD_USG_MANAGE_CLEAR + "", CommandType.CONSOLE_WITH_ARGUMENTS, " [-a]"); - + this.plugin = plugin; } @@ -59,7 +61,7 @@ public class ManageClearCommand extends SubCommand { sender.sendMessage(Phrase.COMMAND_ADD_CONFIRMATION_ARGUMENT.parse(Phrase.WARN_REMOVE.parse(args[0]))); return true; } - + Database clearDB = null; for (Database database : plugin.getDatabases()) { if (dbToClear.equalsIgnoreCase(database.getConfigName())) { @@ -72,7 +74,7 @@ public class ManageClearCommand extends SubCommand { plugin.logError(dbToClear + " was null!"); return true; } - + final Database clearThisDB = clearDB; (new BukkitRunnable() { @Override diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHelpCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHelpCommand.java index 624db1a03..ef7a3ef97 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHelpCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHelpCommand.java @@ -12,8 +12,10 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; /** + * This manage subcommand is used to view all other manage subcommands. * * @author Rsl1122 + * @since 2.3.0 */ public class ManageHelpCommand extends SubCommand { @@ -34,7 +36,7 @@ public class ManageHelpCommand extends SubCommand { } @Override - public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { + public boolean onCommand(CommandSender sender, Command c, String commandLabel, String[] args) { ChatColor oColor = Phrase.COLOR_MAIN.color(); ChatColor tColor = Phrase.COLOR_SEC.color(); @@ -43,21 +45,21 @@ public class ManageHelpCommand extends SubCommand { // Header sender.sendMessage(Phrase.CMD_MANAGE_HELP_HEADER + ""); // Help results - for (SubCommand command : this.command.getCommands()) { - if (command.getName().equalsIgnoreCase(getName())) { + for (SubCommand cmd : this.command.getCommands()) { + if (cmd.getName().equalsIgnoreCase(getName())) { continue; } - if (!command.getPermission().userHasThisPermission(sender)) { + if (!cmd.getPermission().userHasThisPermission(sender)) { continue; } - if (!(sender instanceof Player) && command.getCommandType() == CommandType.PLAYER) { + if (!(sender instanceof Player) && cmd.getCommandType() == CommandType.PLAYER) { continue; } sender.sendMessage(tColor + " " + Phrase.BALL.toString() + oColor - + " /plan manage " + command.getFirstName() + " " + command.getArguments() + tColor + " - " + command.getUsage()); + + " /plan manage " + cmd.getFirstName() + " " + cmd.getArguments() + tColor + " - " + cmd.getUsage()); } // Footer sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString()); diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHotswapCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHotswapCommand.java index 83b1a4a4c..f9a0845aa 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHotswapCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageHotswapCommand.java @@ -11,8 +11,11 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandSender; /** + * This manage subcommand is used to swap to a different database and reload the + * plugin if the connection to the new database can be established. * * @author Rsl1122 + * @since 2.3.0 */ public class ManageHotswapCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageImportCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageImportCommand.java index d4b01a7de..7d336abeb 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageImportCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageImportCommand.java @@ -22,8 +22,12 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; /** - * + * This manage subcommand is used to import data from 3rd party plugins. + * + * Supported plugins (v3.0.0) : OnTime + * * @author Rsl1122 + * @since 2.3.0 */ public class ManageImportCommand extends SubCommand { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageMoveCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageMoveCommand.java index ce38f63d3..b74ebef25 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageMoveCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageMoveCommand.java @@ -15,8 +15,12 @@ import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; /** - * + * This manage subcommand is used to move all data from one database to another. + * + * Destination database will be cleared. + * * @author Rsl1122 + * @since 2.3.0 */ public class ManageMoveCommand extends SubCommand { @@ -32,20 +36,7 @@ public class ManageMoveCommand extends SubCommand { this.plugin = plugin; } - - /** - * Subcommand inspect. - * - * Adds player's data from DataCache/DB to the InspectCache for amount of - * time specified in the config, and clears the data from Cache with a timer - * task. - * - * @param sender - * @param cmd - * @param commandLabel - * @param args Player's name or nothing - if empty sender's name is used. - * @return true in all cases. - */ + @Override public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { if (args.length < 2) { @@ -93,7 +84,7 @@ public class ManageMoveCommand extends SubCommand { plugin.logError(toDB + " was null!"); return true; } - + final Database moveFromDB = fromDatabase; final Database moveToDB = toDatabase; (new BukkitRunnable() { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRemoveCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRemoveCommand.java index 761ad1bf4..4bc89eb43 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRemoveCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRemoveCommand.java @@ -17,6 +17,8 @@ import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; /** + * This manage subcommand is used to remove a single player's data from the + * database. * * @author Rsl1122 */ @@ -35,19 +37,6 @@ public class ManageRemoveCommand extends SubCommand { this.plugin = plugin; } - /** - * Subcommand inspect. - * - * Adds player's data from DataCache/DB to the InspectCache for amount of - * time specified in the config, and clears the data from Cache with a timer - * task. - * - * @param sender - * @param cmd - * @param commandLabel - * @param args Player's name or nothing - if empty sender's name is used. - * @return true in all cases. - */ @Override public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { if (args.length == 0) { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRestoreCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRestoreCommand.java index 88d9b997a..fccbf1544 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRestoreCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageRestoreCommand.java @@ -18,12 +18,14 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; /** + * This manage subcommand is used to restore a backup.db file in the + * /plugins/Plan folder. * * @author Rsl1122 */ public class ManageRestoreCommand extends SubCommand { - private Plan plugin; + private final Plan plugin; /** * Class Constructor. @@ -36,15 +38,6 @@ public class ManageRestoreCommand extends SubCommand { this.plugin = plugin; } - /** - * Subcommand Manage backup. - * - * @param sender - * @param cmd - * @param commandLabel - * @param args Player's name or nothing - if empty sender's name is used. - * @return true in all cases. - */ @Override public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { try { diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageStatusCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageStatusCommand.java index c8c286b1e..b256b59b0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageStatusCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageStatusCommand.java @@ -10,12 +10,13 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandSender; /** + * This manage subcommand is used to check the status of the database. * * @author Rsl1122 */ public class ManageStatusCommand extends SubCommand { - private Plan plugin; + private final Plan plugin; /** * Class Constructor. @@ -28,29 +29,14 @@ public class ManageStatusCommand extends SubCommand { this.plugin = plugin; } - /** - * Subcommand inspect. - * - * Adds player's data from DataCache/DB to the InspectCache for amount of - * time specified in the config, and clears the data from Cache with a timer - * task. - * - * @param sender - * @param cmd - * @param commandLabel - * @param args Player's name or nothing - if empty sender's name is used. - * @return true in all cases. - */ @Override public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { ChatColor hColor = Phrase.COLOR_TER.color(); - // Header sender.sendMessage(Phrase.CMD_MANAGE_STATUS_HEADER + ""); sender.sendMessage(Phrase.CMD_MANAGE_STATUS_ACTIVE_DB.parse(plugin.getDB().getConfigName())); - // Footer sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString()); return true; } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java b/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java index 5acac496a..4de10f67b 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/AnalysisData.java @@ -3,10 +3,23 @@ package main.java.com.djrapitops.plan.data; import java.util.Arrays; import java.util.Objects; import main.java.com.djrapitops.plan.ui.Html; +import main.java.com.djrapitops.plan.ui.RecentPlayersButtonsCreator; +import main.java.com.djrapitops.plan.ui.tables.SortableCommandUseTableCreator; +import main.java.com.djrapitops.plan.ui.tables.SortablePlayersTableCreator; +import main.java.com.djrapitops.plan.utilities.Analysis; +import main.java.com.djrapitops.plan.utilities.AnalysisUtils; +import main.java.com.djrapitops.plan.utilities.PlaceholderUtils; /** + * This class is used to store result data from Analysis at runtime. + * + * Most of the variables need to be set with various set methods, as they are + * not initialized in a constructor. * * @author Rsl1122 + * @since 2.0.0 + * @see Analysis + * @see PlaceholderUtils */ public class AnalysisData { @@ -17,7 +30,6 @@ public class AnalysisData { private double averageAge; private String commandUseTableHtml; private long totalCommands; - private String top20ActivePlayers; private String recentPlayers; private String sortablePlayersTable; private String[] playersDataArray; @@ -56,12 +68,11 @@ public class AnalysisData { /** * Class constructor. * - * All data has to be set with setters to avoid NPE. + * All data has to be set with setters to avoid NPEs. */ public AnalysisData() { sortablePlayersTable = Html.ERROR_NOT_SET + ""; commandUseTableHtml = Html.ERROR_NOT_SET + ""; - top20ActivePlayers = Html.ERROR_NOT_SET + ""; recentPlayers = Html.ERROR_NOT_SET + ""; geomapCountries = Html.ERROR_NOT_SET + ""; geomapZ = Html.ERROR_NOT_SET + ""; @@ -70,7 +81,6 @@ public class AnalysisData { genderData = new int[]{0, 0, 0}; } - // Getters and setters v---------------------------------v @Override public boolean equals(Object obj) { if (this == obj) { @@ -155,9 +165,6 @@ public class AnalysisData { if (!Objects.equals(this.commandUseTableHtml, other.commandUseTableHtml)) { return false; } - if (!Objects.equals(this.top20ActivePlayers, other.top20ActivePlayers)) { - return false; - } if (!Objects.equals(this.recentPlayers, other.recentPlayers)) { return false; } @@ -183,106 +190,151 @@ public class AnalysisData { } /** + * Used to get the toString representation of a String[] containing all + * countries on the Plotly.js Chloropleth map. * - * @return + * @return ["Finland","Sweden","Etc.."] */ public String getGeomapCountries() { return geomapCountries; } /** + * Used to set the toString representation of a String[] containing all + * countries on the Plotly.js Chloropleth map. * - * @param geomapCountries + * Incorrect value will break the Chloropleth map on analysis.html page. + * + * @param geomapCountries ["Finland","Sweden","Etc.."] */ public void setGeomapCountries(String geomapCountries) { this.geomapCountries = geomapCountries; } /** + * Used to get the toString representation of a int[] containing all player + * amounts on the Plotly.js Chloropleth map. * - * @return + * Must contain same amount of numbers as countries in GeomapCountries. + * + * @return [0,0,0,3,0,Etc..] */ public String getGeomapZ() { return geomapZ; } /** + * Used to set the toString representation of a int[] containing all player + * amounts on the Plotly.js Chloropleth map. * - * @param geomapZ + * Must contain same amount of numbers as countries in GeomapCountries. + * Incorrect amount will break the Chloropleth map on analysis.html page. + * + * @param geomapZ [0,0,0,3,0,Etc..] */ public void setGeomapZ(String geomapZ) { this.geomapZ = geomapZ; } /** + * Used to get the toString representation of a String[] containing all + * country codes on the Plotly.js Chloropleth map. * - * @return + * Must contain same amount of numbers as countries in GeomapCountries. + * + * @return ["PNG","KHM","KAZ","PRY","SYR","SLB","MLI","Etc.."] */ public String getGeomapCodes() { return geomapCodes; } /** + * Used to set the toString representation of a String[] containing all + * country codes on the Plotly.js Chloropleth map. * - * @param geomapCodes + * Must contain same amount of numbers as countries in GeomapCountries. + * + * @param geomapCodes ["PNG","KHM","KAZ","PRY","SYR","SLB","MLI","Etc.."] */ public void setGeomapCodes(String geomapCodes) { this.geomapCodes = geomapCodes; } /** + * Used to get the html for players table. * - * @return + * @return Html string. + * @see SortablePlayersTableCreator */ public String getSortablePlayersTable() { return sortablePlayersTable; } /** + * Used to set the html for players table. * - * @param sortablePlayersTable + * @param sortablePlayersTable Html string. + * @see SortablePlayersTableCreator */ public void setSortablePlayersTable(String sortablePlayersTable) { this.sortablePlayersTable = sortablePlayersTable; } /** - * @return The Amount of players who have joined only once + * Used to get the amount of players who have joined only once + * + * @return Number from 0 to Integer.MAX */ public int getJoinleaver() { return joinleaver; } /** - * @param joinleaver The Amount of players who have joined only once + * Used to set the amount of players who have joined only once. + * + * No check for correct value. + * + * @param joinleaver Number from 0 to Integer.MAX */ public void setJoinleaver(int joinleaver) { this.joinleaver = joinleaver; } /** - * @return HTML String of the Top50CommandsList + * Used to get the html for the commands table. + * + * @return Html string. + * @see SortableCommandUseTableCreator */ public String getCommandUseListHtml() { return commandUseTableHtml; } /** - * @param top50CommandsListHtml HTML String of the Top50CommandsList + * Used to get the html for the commands table. + * + * @param commandsTableHtml Html string. + * @see SortableCommandUseTableCreator */ - public void setCommandUseTableHtml(String top50CommandsListHtml) { - this.commandUseTableHtml = top50CommandsListHtml; + public void setCommandUseTableHtml(String commandsTableHtml) { + this.commandUseTableHtml = commandsTableHtml; } /** - * @return Amount of banned players + * Used to get the amount of banned players. + * + * @return 0 to Integer.MAX */ public int getBanned() { return banned; } /** - * @param banned Amount of banned players + * Used to set the amount of banned players. + * + * No check for correct value. + * + * @param banned 0 to Integer.MAX */ public void setBanned(int banned) { this.banned = banned; @@ -294,6 +346,7 @@ public class AnalysisData { * Activity is determined by AnalysisUtils.isActive() * * @return Amount of active players + * @see AnalysisUtils */ public int getActive() { return active; @@ -305,166 +358,233 @@ public class AnalysisData { * Activity is determined by AnalysisUtils.isActive() * * @param active Amount of active players + * @see AnalysisUtils */ public void setActive(int active) { this.active = active; } /** + * Set the amount of inactive players. + * + * Activity is determined by AnalysisUtils.isActive() + * * @return Amount of inactive players + * @see AnalysisUtils */ public int getInactive() { return inactive; } /** + * Set the amount of inactive players. + * + * Activity is determined by AnalysisUtils.isActive() + * * @param inactive Amount of inactive players + * @see AnalysisUtils */ public void setInactive(int inactive) { this.inactive = inactive; } /** - * @return Total Amount of players used to calculate activity + * Get the total amount of players used to calculate activity. + * + * @return 0 to Integer.MAX */ public int getTotal() { return total; } /** - * @param total Total Amount of players used to calculate activity + * Set the total amount of players used to calculate activity. + * + * No check for correct value. + * + * @param total 0 to Integer.MAX */ public void setTotal(int total) { this.total = total; } /** - * @return Percentage of Gamemode usage time as a whole + * Get percentage of Gamemode usage time as a whole. + * + * @return 0.0 to 1.0 */ public double getGm0Perc() { return gm0Perc; } /** - * @param gm0Perc Percentage of Gamemode usage time as a whole + * Set percentage of Gamemode usage time as a whole. + * + * No check for correct value. + * + * @param gm0Perc 0.0 to 1.0 */ public void setGm0Perc(double gm0Perc) { this.gm0Perc = gm0Perc; } /** - * @return Percentage of Gamemode usage time as a whole + * Get percentage of Gamemode usage time as a whole. + * + * @return 0.0 to 1.0 */ public double getGm1Perc() { return gm1Perc; } /** - * @param gm1Perc Percentage of Gamemode usage time as a whole + * Set percentage of Gamemode usage time as a whole. + * + * No check for correct value. + * + * @param gm1Perc 0.0 to 1.0 */ public void setGm1Perc(double gm1Perc) { this.gm1Perc = gm1Perc; } /** - * @return Percentage of Gamemode usage time as a whole + * Get percentage of Gamemode usage time as a whole. + * + * @return 0.0 to 1.0 */ public double getGm2Perc() { return gm2Perc; } /** - * @param gm2Perc Percentage of Gamemode usage time as a whole + * Set percentage of Gamemode usage time as a whole. + * + * No check for correct value. + * + * @param gm2Perc 0.0 to 1.0 */ public void setGm2Perc(double gm2Perc) { this.gm2Perc = gm2Perc; } /** - * @return Percentage of Gamemode usage time as a whole + * Get percentage of Gamemode usage time as a whole. + * + * @return 0.0 to 1.0 */ public double getGm3Perc() { return gm3Perc; } /** - * @param gm3Perc Percentage of Gamemode usage time as a whole + * Set percentage of Gamemode usage time as a whole. + * + * No check for correct value. + * + * @param gm3Perc 0.0 to 1.0 */ public void setGm3Perc(double gm3Perc) { this.gm3Perc = gm3Perc; } /** - * @return Total number of players according to bukkit's data. + * Get percentage of Gamemode usage time as a whole. + * + * @return 0.0 to 1.0 */ public int getTotalPlayers() { return totalPlayers; } /** - * @param totalPlayers Total number of players according to bukkit's data. + * Get the Total number of players according to bukkit's data. + * + * @param totalPlayers 0 to Integer.MAX */ public void setTotalPlayers(int totalPlayers) { this.totalPlayers = totalPlayers; } /** - * @return How long has been played, long in ms. + * Get how long time has been played, long in ms. + * + * @return 0 to Long.MAX */ public long getTotalPlayTime() { return totalPlayTime; } /** - * @param totalPlayTime How long has been played, long in ms. + * Set how long time has been played, long in ms. + * + * No check for correct value. + * + * @param totalPlayTime 0 to Long.MAX */ public void setTotalPlayTime(long totalPlayTime) { this.totalPlayTime = totalPlayTime; } /** - * @return Last Analysis Refresh, long in ms. + * Retrieve the refresh Epoch millisecond this object's data was calculated. + * + * @return the refresh Epoch millisecond. */ public long getRefreshDate() { return refreshDate; } /** - * @return How long has been played on average, long in ms. + * Get How long players have played on average. + * + * @return long in ms. */ public long getAveragePlayTime() { return averagePlayTime; } /** - * @return Average age of the players whose age has been gathered. + * Get the average age of the players whose age has been gathered. + * + * -1 if none have been gathered. + * + * @return -1 or from 1.0 to 99.0 */ public double getAverageAge() { return averageAge; } /** - * @return How many times players have joined. + * Get How many times players have joined in total. + * + * @return 0 to Long.MAX */ public long getTotalLoginTimes() { return totalLoginTimes; } /** - * @return How many operators are on the server. + * Get How many operators are on the server. + * + * @return 0 to Integer.MAX */ public int getOps() { return ops; } /** - * @param refreshDate Last Analysis Refresh, long in ms. + * Set the refresh Epoch millisecond this object's data was calculated. + * + * @param refreshDate Epoch millisecond. */ public void setRefreshDate(long refreshDate) { this.refreshDate = refreshDate; } /** + * Set the average playtime of all players. + * * @param averagePlayTime long in ms. */ public void setAveragePlayTime(long averagePlayTime) { @@ -472,147 +592,167 @@ public class AnalysisData { } /** - * @param averageAge Average age of the players whose age has been gathered. + * Set the average age of the players whose age has been gathered. + * + * No check for correct value. + * + * @param averageAge 1.0 to 99.0 or -1 if none have been gathered. */ public void setAverageAge(double averageAge) { this.averageAge = averageAge; } /** - * @param totalLoginTimes How many times playes have logged in + * Set How many times playes have logged in. + * + * @param totalLoginTimes 0 to Long.MAX */ public void setTotalLoginTimes(long totalLoginTimes) { this.totalLoginTimes = totalLoginTimes; } /** - * @param ops Amount of operators. + * Set the amount of operators. + * + * @param ops 0 to Integer.MAX */ public void setOps(int ops) { this.ops = ops; } /** + * Get the html for Recent player buttons. * - * @return - */ - public String getTop20ActivePlayers() { - return top20ActivePlayers; - } - - /** - * - * @param top20ActivePlayers - */ - public void setTop20ActivePlayers(String top20ActivePlayers) { - this.top20ActivePlayers = top20ActivePlayers; - } - - /** - * - * @return + * @return html string. + * @see RecentPlayersButtonsCreator */ public String getRecentPlayers() { return recentPlayers; } /** + * Set the html for Recent player buttons. * - * @param recentPlayers + * @param recentPlayers html string. + * @see RecentPlayersButtonsCreator */ public void setRecentPlayers(String recentPlayers) { this.recentPlayers = recentPlayers; } /** + * Get the amount of registered players in last 30 days. * - * @return + * @return 0 to Integer.MAX */ public int getNewPlayersMonth() { return newPlayersMonth; } /** + * Set the amount of registered players in last 30 days. * - * @param newPlayersMonth + * No check for correct value. + * + * @param newPlayersMonth 0 to Integer.MAX */ public void setNewPlayersMonth(int newPlayersMonth) { this.newPlayersMonth = newPlayersMonth; } /** + * Get the amount of registered players in last 7 days. * - * @return + * @return 0 to Integer.MAX */ public int getNewPlayersWeek() { return newPlayersWeek; } /** + * Set the amount of registered players in last 7 days. * - * @param newPlayersWeek + * No check for correct value. + * + * @param newPlayersWeek 0 to Integer.MAX */ public void setNewPlayersWeek(int newPlayersWeek) { this.newPlayersWeek = newPlayersWeek; } /** + * Get the amount of registered players in last 24 hours. * - * @return + * @return 0 to Integer.MAX */ public int getNewPlayersDay() { return newPlayersDay; } /** + * Set the amount of registered players in last 24 hours. * - * @param newPlayersDay + * No check for correct value. + * + * @param newPlayersDay 0 to Integer.MAX */ public void setNewPlayersDay(int newPlayersDay) { this.newPlayersDay = newPlayersDay; } /** + * Get the amount of times players have killed each other. * - * @return + * @return 0 to Long.MAX */ public long getTotalPlayerKills() { return totalkills; } /** + * Get the amount of mob kills the players have. * - * @return + * @return 0 to Long.MAX */ public long getTotalMobKills() { return totalmobkills; } /** + * Get how many times the playes have died. * - * @return + * @return 0 to Long.MAX */ public long getTotalDeaths() { return totaldeaths; } /** + * Set the amount of times players have killed each other. * - * @param totalkills + * No check for correct value. + * + * @param totalkills 0 to Long.MAX */ public void setTotalkills(long totalkills) { this.totalkills = totalkills; } /** + * Set the amount of mob kills the players have. * - * @param totalmobkills + * No check for correct value. + * + * @param totalmobkills 0 to Long.MAX */ public void setTotalmobkills(long totalmobkills) { this.totalmobkills = totalmobkills; } /** + * Set how many times the playes have died. + * + * No check for correct value. * * @param totaldeaths */ @@ -621,56 +761,83 @@ public class AnalysisData { } /** + * Used to store all arrays created in + * Analysis#createPlayerActivityGraphs(). * - * @return + * 0, 2, 4 contain data. 1, 3, 5 contain labels. + * + * 0, 1 day; 2, 3 week; 4, 5 month + * + * @return String array containing multiple toString representations of + * number & label arrays. + * @see PlayersActivityGraphCreator + * @see Analysis */ public String[] getPlayersDataArray() { return playersDataArray; } /** + * Used to store all arrays created in + * Analysis#createPlayerActivityGraphs(). * - * @param playersDataArray + * 0, 2, 4 contain data. 1, 3, 5 contain labels. + * + * 0, 1 day; 2, 3 week; 4, 5 month + * + * @param playersDataArray String array containing multiple toString + * representations of number & label arrays. + * @see PlayersActivityGraphCreator + * @see Analysis */ public void setPlayersDataArray(String[] playersDataArray) { this.playersDataArray = playersDataArray; } /** + * Set the total number of unique commands. * - * @param totalCommands + * No check for correct value. + * + * @param totalCommands 0 to Long.MAX */ public void setTotalCommands(long totalCommands) { this.totalCommands = totalCommands; } /** + * Get the total number of unique commands. * - * @return + * @return 0 to Long.MAX */ public long getTotalCommands() { return totalCommands; } /** + * Get the average length of every session on the server. * - * @return + * @return long in ms. */ public long getSessionAverage() { return sessionAverage; } /** + * Set the average length of every session on the server. * - * @param sessionAverage + * @param sessionAverage 0 to Long.MAX */ public void setSessionAverage(long sessionAverage) { this.sessionAverage = sessionAverage; } /** + * Get the integer array containing 3 numbers. * - * @return + * 0 Male, 1 Female, 2 Unknown. + * + * @return for example [0, 4, 5] when 0 male, 4 female and 5 unknown. */ public int[] getGenderData() { return genderData; @@ -678,7 +845,11 @@ public class AnalysisData { /** * - * @param genderData + * Set the integer array containing 3 numbers. + * + * 0 Male, 1 Female, 2 Unknown. + * + * @param genderData for example [0, 4, 5] */ public void setGenderData(int[] genderData) { this.genderData = genderData; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/DemographicsData.java b/Plan/src/main/java/com/djrapitops/plan/data/DemographicsData.java index a54ba9baf..a7256b9d8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/DemographicsData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/DemographicsData.java @@ -2,10 +2,15 @@ package main.java.com.djrapitops.plan.data; import main.java.com.djrapitops.plan.Phrase; import main.java.com.djrapitops.plan.api.Gender; +import main.java.com.djrapitops.plan.data.handling.LoginHandling; /** + * This class is used to store Demographics data inside the UserData. + * + * Originally these data points were created by Plade (Player Demographics). * * @author Rsl1122 + * @since 2.0.0 */ public class DemographicsData { @@ -16,9 +21,10 @@ public class DemographicsData { /** * Creates demographics data object from existing data. * - * @param age - * @param gender - * @param geoLocation + * @param age Age, -1 if unknown + * @param gender Gender Enum. + * @param geoLocation Name of the geolocation. Phrase.DEM_UNKNOWN if not + * known. */ public DemographicsData(int age, Gender gender, String geoLocation) { this.age = age; @@ -30,17 +36,21 @@ public class DemographicsData { * Creates new demographics data object with default parameters. */ public DemographicsData() { - this(-1, Gender.UNKNOWN, Phrase.DEM_UNKNOWN+""); + this(-1, Gender.UNKNOWN, Phrase.DEM_UNKNOWN + ""); } /** - * @return Age of the player, -1 if not known + * Get the age of the player. + * + * @return 1 to 99, -1 if not known */ public int getAge() { return age; } /** + * Get the gender of the player. + * * @return Gender Enum of the Player. UNKNOWN if not known */ public Gender getGender() { @@ -48,28 +58,40 @@ public class DemographicsData { } /** - * @return Geolocation string of the player "Not known" if not known. + * Get the geolocation string. + * + * @return Geolocation string of the player Phrase.DEM_UNKNOWN if not known. */ public String getGeoLocation() { return geoLocation; } /** - * @param age + * Set the age of the player. + * + * @param age 0 to 99, -1 if not known. */ public void setAge(int age) { this.age = age; } /** - * @param gender + * Set the gender of the player. + * + * @param gender Gender Enum. UNKNOWN if not known. */ public void setGender(Gender gender) { this.gender = gender; } /** - * @param geoLocation + * Set the Geolocation of the player. + * + * LoginHandling.updateGeolocation() is used to get the geolocation + * information. + * + * @param geoLocation Country name, eg. Republic of Kongo, the + * @see LoginHandling */ public void setGeoLocation(String geoLocation) { this.geoLocation = geoLocation; @@ -79,6 +101,4 @@ public class DemographicsData { public String toString() { return "{" + "age:" + age + "|gender:" + gender + "|geoLocation:" + geoLocation + '}'; } - - } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/KillData.java b/Plan/src/main/java/com/djrapitops/plan/data/KillData.java index a296a9a77..f9aa6a4f8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/KillData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/KillData.java @@ -3,6 +3,8 @@ package main.java.com.djrapitops.plan.data; import java.util.UUID; /** + * This class is used to store data about a player kill inside the UserData + * object. * * @author Rsl1122 */ @@ -14,11 +16,12 @@ public class KillData { private final String weapon; /** + * Creates a KillData object with given parameters. * - * @param victim - * @param victimID - * @param weapon - * @param date + * @param victim UUID of the victim. + * @param victimID ID of the victim, get from the database. + * @param weapon Weapon used. + * @param date Epoch millisecond at which the kill occurrred. */ public KillData(UUID victim, int victimID, String weapon, long date) { this.victim = victim; @@ -28,32 +31,36 @@ public class KillData { } /** + * Get the victim's UUID * - * @return + * @return UUID of the victim. */ public UUID getVictim() { return victim; } /** + * Get the Epoch millisecond the kill occurred. * - * @return + * @return long in ms. */ public long getDate() { return date; } /** + * Get the Weapon used as string. * - * @return + * @return For example DIAMOND_SWORD */ public String getWeapon() { return weapon; } /** + * Get the UserID of the victim, found from the database. * - * @return + * @return For example: 6 */ public int getVictimUserID() { return victimUserID; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/RawAnalysisData.java b/Plan/src/main/java/com/djrapitops/plan/data/RawAnalysisData.java index 9a710a353..fb0902619 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/RawAnalysisData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/RawAnalysisData.java @@ -6,8 +6,11 @@ import java.util.List; import java.util.Map; /** + * This class is used for storing combined data of several UserData objects + * during Analysis. * * @author Rsl1122 + * @since 2.6.0 */ public class RawAnalysisData { @@ -36,7 +39,7 @@ public class RawAnalysisData { private int[] genders; /** - * + * Constructor for a new empty dataset. */ public RawAnalysisData() { gmZero = 0; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/SessionData.java b/Plan/src/main/java/com/djrapitops/plan/data/SessionData.java index d4e8d57d1..c0d46d68d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/SessionData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/SessionData.java @@ -1,6 +1,8 @@ package main.java.com.djrapitops.plan.data; /** + * This class is used for storing start and end of a playsession inside UserData + * object. * * @author Rsl1122 */ @@ -10,8 +12,9 @@ public class SessionData { private long sessionEnd; /** + * Creates a new session with given start and end of -1. * - * @param sessionStart + * @param sessionStart Epoch millisecond the session was started. */ public SessionData(long sessionStart) { this.sessionStart = sessionStart; @@ -19,9 +22,10 @@ public class SessionData { } /** + * Creates a new session with given start and end. * - * @param sessionStart - * @param sessionEnd + * @param sessionStart Epoch millisecond the session was started. + * @param sessionEnd Epoch millisecond the session ended. */ public SessionData(long sessionStart, long sessionEnd) { this.sessionStart = sessionStart; @@ -29,45 +33,52 @@ public class SessionData { } /** + * Ends the session with given end point. * - * @param endOfSession + * (Changes the end to the parameter.). + * + * @param endOfSession Epoch millisecond the session ended. */ public void endSession(long endOfSession) { sessionEnd = endOfSession; } /** + * Get the start of the session. * - * @return + * @return Epoch millisecond the session started. */ public long getSessionStart() { return sessionStart; } /** + * Get the end of the session. * - * @return + * @return Epoch millisecond the session ended. */ public long getSessionEnd() { return sessionEnd; } - + /** + * Get the length of the session in milliseconds. * - * @return + * @return Long in ms. */ public long getLength() { - return sessionEnd-sessionStart; + return sessionEnd - sessionStart; } @Override public String toString() { return "s:" + sessionStart + " e:" + sessionEnd; } - + /** + * Check if the session start was before the end. * - * @return + * @return Is the length positive? */ public boolean isValid() { return sessionStart <= sessionEnd; @@ -93,6 +104,4 @@ public class SessionData { } return true; } - - } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/UserData.java b/Plan/src/main/java/com/djrapitops/plan/data/UserData.java index 3c63bc92b..1389786be 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/UserData.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/UserData.java @@ -6,18 +6,19 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import main.java.com.djrapitops.plan.Log; -import main.java.com.djrapitops.plan.Plan; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; -import static org.bukkit.plugin.java.JavaPlugin.getPlugin; /** + * This class is used for storing information about a player during runtime. * * @author Rsl1122 */ @@ -29,8 +30,8 @@ public class UserData { private UUID uuid; private Location location; private List locations; - private HashSet ips; - private HashSet nicknames; + private Set ips; + private Set nicknames; private String lastNick; private long registered; private long lastPlayed; @@ -39,7 +40,7 @@ public class UserData { private int timesKicked; private long lastGmSwapTime; private GameMode lastGamemode; - private HashMap gmTimes; + private Map gmTimes; private boolean isOp; private boolean isBanned; private DemographicsData demData; @@ -55,15 +56,28 @@ public class UserData { private List sessions; /** + * Creates a new UserData object with given values & default values. * - * @param uuid - * @param reg - * @param loc - * @param op - * @param lastGM - * @param demData - * @param name - * @param online + * Some variables are left uninitialized: isBanned, lastPlayed, playTime, + * loginTimes, timesKicked, lastGmSwapTime, mobKills, deaths & + * currentSession. + * + * These variables need to be set with setters. + * + * All Collections are left empty: locations, nicknames, ips, sessions, + * playerKills. Because nicknames is empty, lastNick is an empty string. + * + * gmTimes Hashmap will contain 4 '0L' values: SURVIVAL, CREATIVE, + * ADVENTURE, SPECTATOR + * + * @param uuid UUID of the player + * @param reg Epoch millisecond the player registered. + * @param loc Current Location in a world. + * @param op Is the player op? (true/false) + * @param lastGM last GameMode the player was seen in. + * @param demData Demographics data. + * @param name Name of the player. + * @param online Is the player online? */ public UserData(UUID uuid, long reg, Location loc, boolean op, GameMode lastGM, DemographicsData demData, String name, boolean online) { accessing = 0; @@ -93,32 +107,60 @@ public class UserData { } /** + * Creates a new UserData object with the variables inside a Player object. * - * @param player - * @param demData + * Some variables are left uninitialized: lastPlayed, playTime, loginTimes, + * timesKicked, lastGmSwapTime, mobKills, deaths & currentSession. + * + * These variables need to be set with setters. + * + * All Collections are left empty: locations, nicknames, ips, sessions, + * playerKills. Because nicknames is empty, lastNick is an empty string. + * + * gmTimes Hashmap will contain 4 '0L' values: SURVIVAL, CREATIVE, + * ADVENTURE, SPECTATOR + * + * @param player Player object. + * @param demData Demographics data. */ public UserData(Player player, DemographicsData demData) { this(player.getUniqueId(), player.getFirstPlayed(), player.getLocation(), player.isOp(), player.getGameMode(), demData, player.getName(), player.isOnline()); try { isBanned = player.isBanned(); } catch (Exception e) { - Log.errorMsg("Error getting ban date from Bukkit files. " + uuid.toString()); + Log.error("Error getting ban date from Bukkit files. " + uuid.toString()); Log.toLog(this.getClass().getName(), e); isBanned = false; } } /** + * Creates a new UserData object with the variables inside a OfflinePlayer + * object. * - * @param player - * @param demData + * Some variables are left uninitialized: location, lastPlayed, playTime, + * loginTimes, timesKicked, lastGmSwapTime, mobKills, deaths & + * currentSession. + * + * These variables need to be set with setters. + * + * All Collections are left empty: locations, nicknames, ips, sessions, + * playerKills. Because nicknames is empty, lastNick is an empty string. + * + * gmTimes Hashmap will contain 4 '0L' values: SURVIVAL, CREATIVE, + * ADVENTURE, SPECTATOR + * + * lastGM will be set as SURVIVAL + * + * @param player OfflinePlayer object. + * @param demData Demographics data. */ public UserData(OfflinePlayer player, DemographicsData demData) { this(player.getUniqueId(), player.getFirstPlayed(), null, player.isOp(), GameMode.SURVIVAL, demData, player.getName(), player.isOnline()); try { isBanned = player.isBanned(); } catch (Exception e) { - Log.errorMsg("Error getting ban date from Bukkit files. " + uuid.toString()); + Log.error("Error getting ban date from Bukkit files. " + uuid.toString()); Log.toLog(this.getClass().getName(), e); isBanned = false; } @@ -171,18 +213,20 @@ public class UserData { } /** + * Adds an to the ips Set if it is not null or the set doesn't contain it. * - * @param ip + * @param ip InetAddress of the player. */ public void addIpAddress(InetAddress ip) { - if (ip != null && !ips.contains(ip)) { + if (ip != null) { ips.add(ip); } } /** + * Adds multiple ips to the ips set if they're not null. * - * @param addIps + * @param addIps a Collection of InetAddresses the player has logged from. */ public void addIpAddresses(Collection addIps) { ips.addAll(addIps.stream().filter(ip -> ip != null).collect(Collectors.toList())); @@ -190,8 +234,11 @@ public class UserData { } /** + * Adds a location to the locations list. * - * @param loc + * null value filtered. loc will be set as the latest location. + * + * @param loc Location of the player. */ public void addLocation(Location loc) { if (loc != null) { @@ -201,8 +248,11 @@ public class UserData { } /** + * Adds multiple locations to the locations list. * - * @param addLocs + * null value filtered. + * + * @param addLocs Collection of Locations. */ public void addLocations(Collection addLocs) { if (!addLocs.isEmpty()) { @@ -213,35 +263,43 @@ public class UserData { } /** + * Adds a nickname to the nicknames Set. * - * @param nick - * @return + * null or empty values filtered. + * + * lastNick will be set as the given parameter, if accepted. + * + * @param nick Displayname of the player. + * @return was lastNick updated? */ public boolean addNickname(String nick) { - if (!nicknames.contains(nick)) { - if (nick != null) { - if (!nick.isEmpty()) { - nicknames.add(nick); - lastNick = nick; - return true; - } + if (nick != null && !nick.isEmpty()) { + boolean isNew = !nicknames.contains(nick); + nicknames.add(nick); + if (isNew) { + lastNick = nick; } + return isNew; } return false; } /** + * Adds nicknames to the nicknames Set. * - * @param addNicks + * null or empty values filtered. + * + * @param addNicks Collection of nicknames. */ public void addNicknames(Collection addNicks) { - nicknames.addAll(addNicks.stream().filter(nick -> nick != null).collect(Collectors.toList())); + nicknames.addAll(addNicks.stream().filter(nick -> nick != null && !nick.isEmpty()).collect(Collectors.toList())); } /** + * Set a specific GameMode's millisecond value. * - * @param gm - * @param time + * @param gm GameMode. + * @param time Milliseconds spent in the gamemode. */ public void setGMTime(GameMode gm, long time) { if (gmTimes == null) { @@ -253,11 +311,12 @@ public class UserData { } /** + * Set every GameMode's millisecond value. * - * @param survivalTime - * @param creativeTime - * @param adventureTime - * @param spectatorTime + * @param survivalTime ms spent in SURVIVAL + * @param creativeTime ms spent in CREATIVE + * @param adventureTime ms spent in ADVENTURE + * @param spectatorTime ms spent in SPECTATOR */ public void setAllGMTimes(long survivalTime, long creativeTime, long adventureTime, long spectatorTime) { gmTimes.clear(); @@ -271,8 +330,11 @@ public class UserData { } /** + * Adds a new SessionData to the sessions list. * - * @param session + * null and invalid sessions filtered. + * + * @param session SessionData object */ public void addSession(SessionData session) { if (session != null && session.isValid()) { @@ -281,8 +343,11 @@ public class UserData { } /** + * Adds SessionData objects to the sessions list. * - * @param sessions + * null and invalid sessions filtered. + * + * @param sessions Collection of SessionData objects. */ public void addSessions(Collection sessions) { Collection filteredSessions = sessions.stream() @@ -290,143 +355,175 @@ public class UserData { .filter(session -> session.isValid()) .collect(Collectors.toList()); if (sessions.size() != filteredSessions.size()) { - Log.debug("Some sessions were filtered! "+getUuid()+": Org:"+sessions.size()+" Fil:"+filteredSessions.size()); + Log.debug(getUuid() + ": Some sessions were filtered! Org:" + sessions.size() + " Fil:" + filteredSessions.size()); } this.sessions.addAll(filteredSessions); } /** + * Sets the current session. * - * @param session + * Currently unused. + * + * @param session SessionData object, no restrictions. */ public void setCurrentSession(SessionData session) { currentSession = session; } /** + * Gets the current session. * - * @return + * Currently unused. + * + * @return SessionData object with a recent start. */ public SessionData getCurrentSession() { return currentSession; } /** + * Changes the value of isBanned. * - * @param isBanned + * @param isBanned Is the player banned? */ public void updateBanned(boolean isBanned) { this.isBanned = isBanned; } /** + * Checks whether or not the UserData object is accessed by different save + * processes. * - * @return + * @return true if accessed. */ public boolean isAccessed() { return accessing > 0; } /** - * + * Accesses the UserData object to protect it from being cleared. */ public void access() { accessing++; } /** - * + * Stops accessing the object so that it can now be cleared. */ public void stopAccessing() { accessing--; } - // Getters ------------------------------------------------------------- /** + * Used to get the UUID of the player. * - * @return + * @return UUID. */ public UUID getUuid() { return uuid; } /** + * Used to get the latest location. * - * @return + * NOT INITIALIZED BY CONSTRUCTORS + * + * @return Location. */ public Location getLocation() { return location; } /** + * Get the list of all locations inside the UserData object. * - * @return + * @return a list of Locations. */ public List getLocations() { return locations; } /** + * Get the InetAddress Set. * - * @return + * @return a HashSet of ips. */ - public HashSet getIps() { + public Set getIps() { return ips; } /** + * Get the nickname String Set * - * @return + * @return a HashSet of Strings. */ - public HashSet getNicknames() { + public Set getNicknames() { return nicknames; } /** + * Get the Epoch millisecond the player registered. * - * @return + * @return long in ms. */ public long getRegistered() { return registered; } /** + * Get the Epoch millisecond the player was last seen. * - * @return + * NOT INITIALIZED BY CONSTRUCTORS. Value is updated periodically by cache + * if the player is online. + * + * @return long in ms. */ public long getLastPlayed() { return lastPlayed; } /** + * Get the playtime in milliseconds. * - * @return + * NOT INITIALIZED BY CONSTRUCTORS. Value is updated periodically by cache + * if the player is online. + * + * @return time in ms. */ public long getPlayTime() { return playTime; } /** + * Get how many times the player has logged in. * - * @return + * NOT INITIALIZED BY CONSTRUCTORS. + * + * @return 0 to Integer.MAX */ public int getLoginTimes() { return loginTimes; } /** + * Get how many times the player has been kicked. * - * @return + * NOT INITIALIZED BY CONSTRUCTORS. + * + * @return 0 to Integer.MAX */ public int getTimesKicked() { return timesKicked; } /** + * Get the GMTimes Map. * - * @return + * @return a GameMode map with 4 keys: SURVIVAL, CREATIVE, ADVENTURE, + * SPECTATOR. */ - public HashMap getGmTimes() { + public Map getGmTimes() { if (gmTimes == null) { gmTimes = new HashMap<>(); } @@ -434,265 +531,325 @@ public class UserData { } /** + * Get the last time a Gamemode time was updated. * - * @return + * @return Epoch millisecond of last GM Time update. */ public long getLastGmSwapTime() { return lastGmSwapTime; } /** + * Get the last Gamemode that the user was seen in. * - * @return + * When player changes to SURVIVAL this is set to SURVIVAL. + * + * @return Gamemode. */ public GameMode getLastGamemode() { return lastGamemode; } /** + * Is the user Operator? * - * @return + * @return opped? */ public boolean isOp() { return isOp; } /** + * Is the user Banned? * - * @return + * @return banned? */ public boolean isBanned() { return isBanned; } /** + * Get the DemographicsData of the user. * - * @return + * @return Demographics data. */ public DemographicsData getDemData() { return demData; } /** + * Get the username of the player. * - * @return + * @return username. */ public String getName() { return name; } - // Setters ------------------------------------------------------------- /** + * Set the UUID. * - * @param uuid + * @param uuid UUID */ public void setUuid(UUID uuid) { this.uuid = uuid; } /** + * Set the current location. * - * @param location + * Not in use. + * + * @param location a location in the world. */ public void setLocation(Location location) { this.location = location; } /** + * Set the list of locations the user has been in. * - * @param locations + * Not in use. + * + * @param locations a list of Locations. */ public void setLocations(List locations) { - this.locations = locations; + if (locations != null) { + this.locations = locations; + } } /** + * Set the ips set. * - * @param ips + * @param ips ips of the user. */ - public void setIps(HashSet ips) { - this.ips = ips; + public void setIps(Set ips) { + if (ips != null) { + this.ips = ips; + } } /** + * Set the nicknames set. * - * @param nicknames + * @param nicknames nicknames of the user. */ - public void setNicknames(HashSet nicknames) { - this.nicknames = nicknames; + public void setNicknames(Set nicknames) { + if (nicknames != null) { + this.nicknames = nicknames; + } } /** + * Set the time the user was registered. * - * @param registered + * @param registered Epoch millisecond of register time. */ public void setRegistered(long registered) { this.registered = registered; } /** + * Set the time the user was last seen. * - * @param lastPlayed + * Affects playtime calculation, playtime should be updated before updating + * this value. + * + * @param lastPlayed Epoch millisecond of last seen moment. */ public void setLastPlayed(long lastPlayed) { this.lastPlayed = lastPlayed; } /** + * Set the time the user has been playing. * - * @param playTime + * @param playTime Time in ms. */ public void setPlayTime(long playTime) { this.playTime = playTime; } /** + * Set how many times the user has logged in. * - * @param loginTimes + * No check for input. + * + * @param loginTimes 0 to Int.MAX */ public void setLoginTimes(int loginTimes) { this.loginTimes = loginTimes; } /** + * Set how many times the user has been kicked. * - * @param timesKicked + * No check for input. + * + * @param timesKicked 0 to Int.MAX */ public void setTimesKicked(int timesKicked) { this.timesKicked = timesKicked; } /** + * Set the GM Times map containing playtime in each gamemode. * - * @param gmTimes + * @param gmTimes Map containing SURVIVAL, CREATIVE, ADVENTURE & SPECTATOR + * (After 1.8) keys. */ - public void setGmTimes(HashMap gmTimes) { - this.gmTimes = gmTimes; + public void setGmTimes(Map gmTimes) { + if (gmTimes != null) { + this.gmTimes = gmTimes; + } } /** + * Set the last time a Gamemode time was updated. * - * @param lastGmSwapTime + * @param lastGmSwapTime Epoch millisecond a gm time was updated. */ public void setLastGmSwapTime(long lastGmSwapTime) { this.lastGmSwapTime = lastGmSwapTime; } /** + * Set the last gamemode the user was seen in. * - * @param lastGamemode + * @param lastGamemode gamemode. */ public void setLastGamemode(GameMode lastGamemode) { this.lastGamemode = lastGamemode; } /** + * Set whether or not player is op. * - * @param isOp + * @param isOp operator? */ public void setIsOp(boolean isOp) { this.isOp = isOp; } /** + * Set the DemographicsData of the user. * - * @param demData + * @param demData demographics data. */ public void setDemData(DemographicsData demData) { this.demData = demData; } /** + * Set the username of the user. * - * @param name + * @param name username. */ public void setName(String name) { this.name = name; } /** + * Is the player online? * - * @return + * @return true if data is cached to datacache, false if not. */ public boolean isOnline() { return isOnline; } /** + * Get how many mob kills the player has. * - * @return + * @return 0 to Int.MAX */ public int getMobKills() { return mobKills; } /** + * Get how many mob kills the player has. * - * @param mobKills + * @param mobKills 0 to Int.MAX */ public void setMobKills(int mobKills) { this.mobKills = mobKills; } /** + * Get the player kills list. * - * @return + * @return playerkills list. */ public List getPlayerKills() { return playerKills; } /** + * Set the playerkills list. * - * @param playerKills + * @param playerKills list of players kills. */ public void setPlayerKills(List playerKills) { - this.playerKills = playerKills; + if (playerKills != null) { + this.playerKills = playerKills; + } } /** + * Add a Killdata to player's kills list. * - * @param kill + * @param kill KillData representing a player kill. */ public void addPlayerKill(KillData kill) { playerKills.add(kill); } /** + * Get how many times the player has died. * - * @return + * @return 0 to Int.MAX */ public int getDeaths() { return deaths; } /** + * Set how many times the player has died. * - * @param deaths + * @param deaths 0 to Int.MAX */ public void setDeaths(int deaths) { this.deaths = deaths; } /** + * Get the sessions of a player. * - * @return + * @return a list of SessionData. */ public List getSessions() { return sessions; } /** + * Get the last nickname the user has set. * - * @return + * Set when using addNickname(String) + * + * @return last nickname used. */ public String getLastNick() { return lastNick; } /** + * Set the last nickname the user has set. * - * @param lastNick + * Also set when using addNickname(String) + * + * @param lastNick last nickname used. */ public void setLastNick(String lastNick) { this.lastNick = lastNick; @@ -764,10 +921,22 @@ public class UserData { return true; } + /** + * Check wether or not the object should be cleared from cache after it has + * been saved. + * + * @return true/false + */ public boolean shouldClearAfterSave() { return clearAfterSave; } + /** + * Set wether or not the object should be cleared from cache after it has + * been saved. + * + * @param clearAfterSave true/false + */ public void setClearAfterSave(boolean clearAfterSave) { this.clearAfterSave = clearAfterSave; } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/DataCacheHandler.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/DataCacheHandler.java index 32074703e..5db92e811 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/DataCacheHandler.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/DataCacheHandler.java @@ -24,12 +24,12 @@ import main.java.com.djrapitops.plan.database.Database; import main.java.com.djrapitops.plan.utilities.NewPlayerCreator; import main.java.com.djrapitops.plan.utilities.comparators.HandlingInfoTimeComparator; import org.bukkit.Bukkit; -import static org.bukkit.Bukkit.getOfflinePlayer; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import static org.bukkit.plugin.java.JavaPlugin.getPlugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; +import static org.bukkit.Bukkit.getOfflinePlayer; /** * @@ -76,7 +76,7 @@ public class DataCacheHandler extends LocationCache { commandUse = new HashMap<>(); if (!getCommandUseFromDb()) { - plugin.logError(Phrase.DB_FAILURE_DISABLE + ""); + Log.error(Phrase.DB_FAILURE_DISABLE + ""); plugin.getServer().getPluginManager().disablePlugin(plugin); return; } @@ -92,7 +92,7 @@ public class DataCacheHandler extends LocationCache { commandUse = db.getCommandUse(); return true; } catch (SQLException e) { - plugin.toLog(this.getClass().getName(), e); + Log.toLog(this.getClass().getName(), e); } return false; } @@ -151,14 +151,14 @@ public class DataCacheHandler extends LocationCache { * of DataCacheHandler */ public void getUserDataForProcessing(DBCallableProcessor processor, UUID uuid, boolean cache) { + Log.debug(uuid+": HANDLER getForProcess,"+" Cache:"+cache); UserData uData = dataCache.get(uuid); if (uData == null) { if (cache) { DBCallableProcessor cacher = new DBCallableProcessor() { @Override public void process(UserData data) { - dataCache.put(uuid, data); - Log.info(Phrase.CACHE_ADD.parse(uuid.toString())); + cache(data); } }; getTask.scheduleForGet(uuid, cacher, processor); @@ -170,6 +170,11 @@ public class DataCacheHandler extends LocationCache { } } + public void cache(UserData data) { + dataCache.put(data.getUuid(), data); + Log.info(Phrase.CACHE_ADD.parse(data.getUuid().toString())); + } + /** ** Uses Database to retrieve the UserData of a matching player Caches the * data to the HashMap @@ -192,7 +197,7 @@ public class DataCacheHandler extends LocationCache { try { db.saveMultipleUserData(data); } catch (SQLException ex) { - plugin.toLog(this.getClass().getName(), ex); + Log.toLog(this.getClass().getName(), ex); } } @@ -201,7 +206,7 @@ public class DataCacheHandler extends LocationCache { * @param i */ public void addToPool(HandlingInfo i) { - Log.debug("Adding to pool, type:" + i.getType().name() + " " + i.getUuid()); + Log.debug(i.getUuid()+ ": Adding to pool, type:" + i.getType().name()); processTask.addToPool(i); } @@ -244,7 +249,7 @@ public class DataCacheHandler extends LocationCache { db.saveCommandUse(commandUse); db.close(); } catch (SQLException e) { - plugin.toLog(this.getClass().getName(), e); + Log.toLog(this.getClass().getName(), e); } Log.debug("SaveCacheOnDisable_END"); } @@ -273,7 +278,7 @@ public class DataCacheHandler extends LocationCache { * @param uuid Player's UUID */ public void saveCachedData(UUID uuid) { - Log.debug("SaveCachedData: " + uuid); + Log.debug(uuid+": SaveCachedData"); DBCallableProcessor saveProcessor = new DBCallableProcessor() { @Override public void process(UserData data) { @@ -298,7 +303,7 @@ public class DataCacheHandler extends LocationCache { try { db.saveCommandUse(commandUse); } catch (SQLException | NullPointerException e) { - plugin.toLog(this.getClass().getName(), e); + Log.toLog(this.getClass().getName(), e); } } @@ -330,9 +335,9 @@ public class DataCacheHandler extends LocationCache { * @param uuid Player's UUID */ public void clearFromCache(UUID uuid) { - Log.debug("Clear: " + uuid); + Log.debug(uuid+": Clear"); if (getOfflinePlayer(uuid).isOnline()) { - Log.debug("Online, did not clear: " + uuid); + Log.debug(uuid+": Online, did not clear"); UserData data = dataCache.get(uuid); if (data != null) { data.setClearAfterSave(false); @@ -392,6 +397,7 @@ public class DataCacheHandler extends LocationCache { */ public void newPlayer(UserData data) { saveTask.scheduleNewPlayer(data); + cache(data); } /** diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/SessionCache.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/SessionCache.java index e824443f5..d16dd4575 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/SessionCache.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/SessionCache.java @@ -28,7 +28,7 @@ public class SessionCache { */ public void startSession(UUID uuid) { long now = new Date().getTime(); - Log.debug("Starting a session: "+uuid+" "+now); + Log.debug(uuid+": Starting a session: "+now); SessionData session = new SessionData(now); activeSessions.put(uuid, session); } @@ -41,7 +41,7 @@ public class SessionCache { SessionData currentSession = activeSessions.get(uuid); if (currentSession != null) { long now = new Date().getTime(); - Log.debug("Ending a session: "+uuid+" "+now); + Log.debug(uuid+": Ending a session: "+now); currentSession.endSession(now); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheClearQueue.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheClearQueue.java index 1f18125ef..bcfe7e28c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheClearQueue.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheClearQueue.java @@ -9,8 +9,6 @@ import main.java.com.djrapitops.plan.Phrase; import main.java.com.djrapitops.plan.Plan; import main.java.com.djrapitops.plan.Settings; import main.java.com.djrapitops.plan.data.cache.DataCacheHandler; -import static org.bukkit.plugin.java.JavaPlugin.getPlugin; -import static org.bukkit.Bukkit.getOfflinePlayer; /** * @@ -37,7 +35,7 @@ public class DataCacheClearQueue { * @param uuid */ public void scheduleForClear(UUID uuid) { - Log.debug("Scheduling for clear: " + uuid); + Log.debug(uuid+": Scheduling for clear"); q.add(uuid); } @@ -53,7 +51,7 @@ public class DataCacheClearQueue { try { q.addAll(uuids); } catch (IllegalStateException e) { - getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Clear Queue", Settings.PROCESS_CLEAR_LIMIT.getNumber() + "")); + Log.error(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Clear Queue", Settings.PROCESS_CLEAR_LIMIT.getNumber() + "")); } } @@ -61,14 +59,18 @@ public class DataCacheClearQueue { * */ public void stop() { - s.stop(); + if (s != null) { + s.stop(); + } + s = null; + q.clear(); } } class ClearConsumer implements Runnable { private final BlockingQueue queue; - private final DataCacheHandler handler; + private DataCacheHandler handler; private boolean run; ClearConsumer(BlockingQueue q, DataCacheHandler handler) { @@ -88,6 +90,9 @@ class ClearConsumer implements Runnable { } void consume(UUID uuid) { + if (handler == null) { + return; + } try { if (handler.isDataAccessed(uuid)) { queue.add(uuid); @@ -96,12 +101,15 @@ class ClearConsumer implements Runnable { } // if online remove from clear list } catch (Exception ex) { - getPlugin(Plan.class).toLog(this.getClass().getName(), ex); + Log.toLog(this.getClass().getName(), ex); } } void stop() { run = false; + if (handler != null) { + handler = null; + } } } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheGetQueue.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheGetQueue.java index 8c5af7ab0..c27d4bc04 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheGetQueue.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheGetQueue.java @@ -14,7 +14,6 @@ import main.java.com.djrapitops.plan.Plan; import main.java.com.djrapitops.plan.Settings; import main.java.com.djrapitops.plan.data.cache.DBCallableProcessor; import main.java.com.djrapitops.plan.database.Database; -import static org.bukkit.plugin.java.JavaPlugin.getPlugin; /** * @@ -41,7 +40,7 @@ public class DataCacheGetQueue { * @param processors */ public void scheduleForGet(UUID uuid, DBCallableProcessor... processors) { - Log.debug("Scheduling for get: "+uuid); + Log.debug(uuid + ": Scheduling for get"); try { HashMap> map = new HashMap<>(); if (map.get(uuid) == null) { @@ -50,7 +49,7 @@ public class DataCacheGetQueue { map.get(uuid).addAll(Arrays.asList(processors)); q.add(map); } catch (IllegalStateException e) { - getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Get Queue", Settings.PROCESS_GET_LIMIT.getNumber()+"")); + Log.error(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Get Queue", Settings.PROCESS_GET_LIMIT.getNumber() + "")); } } @@ -58,14 +57,18 @@ public class DataCacheGetQueue { * */ public void stop() { - s.stop(); + if (s != null) { + s.stop(); + } + s = null; + q.clear(); } } class GetConsumer implements Runnable { private final BlockingQueue>> queue; - private final Database db; + private Database db; private boolean run; GetConsumer(BlockingQueue q, Database db) { @@ -85,6 +88,9 @@ class GetConsumer implements Runnable { } void consume(HashMap> processors) { + if (db == null) { + return; + } try { for (UUID uuid : processors.keySet()) { if (uuid == null) { @@ -92,7 +98,7 @@ class GetConsumer implements Runnable { } List processorsList = processors.get(uuid); if (processorsList != null) { - Log.debug("Get: "+uuid+" For:"+ processorsList.size()); + Log.debug(uuid+ ": Get, For:" + processorsList.size()); try { db.giveUserDataToProcessors(uuid, processorsList); } catch (SQLException e) { @@ -107,6 +113,9 @@ class GetConsumer implements Runnable { void stop() { run = false; + if (db != null) { + db = null; + } } } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueue.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueue.java index f4cb2bb79..eb571d83e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueue.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueue.java @@ -19,19 +19,17 @@ import main.java.com.djrapitops.plan.data.handling.info.HandlingInfo; */ public class DataCacheProcessQueue { - private BlockingQueue q; - private DataCacheHandler h; - private ProcessSetup s; + private BlockingQueue queue; + private ProcessSetup setup; /** * * @param handler */ public DataCacheProcessQueue(DataCacheHandler handler) { - h = handler; - q = new ArrayBlockingQueue(20000); - s = new ProcessSetup(); - s.go(q, h); + queue = new ArrayBlockingQueue(20000); + setup = new ProcessSetup(); + setup.go(queue, handler); } /** @@ -40,7 +38,7 @@ public class DataCacheProcessQueue { */ public void addToPool(HandlingInfo info) { try { - q.add(info); + queue.add(info); } catch (IllegalStateException e) { // getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); } @@ -52,7 +50,7 @@ public class DataCacheProcessQueue { */ public void addToPool(Collection info) { try { - q.addAll(info); + queue.addAll(info); } catch (IllegalStateException e) { // getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); } @@ -64,7 +62,7 @@ public class DataCacheProcessQueue { * @return */ public boolean containsUUID(UUID uuid) { - return new ArrayList<>(q).stream().map(d -> d.getUuid()).collect(Collectors.toList()).contains(uuid); + return new ArrayList<>(queue).stream().map(d -> d.getUuid()).collect(Collectors.toList()).contains(uuid); } /** @@ -72,14 +70,23 @@ public class DataCacheProcessQueue { * @return */ public List stop() { - return s.stop(); + try { + if (setup != null) { + setup.stop(); + return new ArrayList<>(queue); + } + return new ArrayList<>(); + } finally { + setup = null; + queue.clear(); + } } } class ProcessConsumer implements Runnable { private final BlockingQueue queue; - private final DataCacheHandler handler; + private DataCacheHandler handler; private boolean run; ProcessConsumer(BlockingQueue q, DataCacheHandler h) { @@ -99,21 +106,26 @@ class ProcessConsumer implements Runnable { } void consume(HandlingInfo info) { - Log.debug("Processing type: " + info.getType().name() + " " + info.getUuid()); + if (handler == null) { + return; + } + Log.debug(info.getUuid()+": Processing type: " + info.getType().name()); DBCallableProcessor p = new DBCallableProcessor() { @Override public void process(UserData data) { if (!info.process(data)) { - System.out.println("Attempted to process data for wrong uuid: W:" + data.getUuid() + " | R:" + info.getUuid() + " Type:" + info.getType().name()); + Log.error("Attempted to process data for wrong uuid: W:" + data.getUuid() + " | R:" + info.getUuid() + " Type:" + info.getType().name()); } } }; handler.getUserDataForProcessing(p, info.getUuid()); } - Collection stop() { + void stop() { run = false; - return queue; + if (handler != null) { + handler = null; + } } } @@ -129,9 +141,8 @@ class ProcessSetup { new Thread(two).start(); } - List stop() { - List i = new ArrayList<>(one.stop()); - i.addAll(two.stop()); - return i; + void stop() { + one.stop(); + two.stop(); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueue.java b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueue.java index ceda1959c..bc503cccb 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueue.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueue.java @@ -13,7 +13,6 @@ import main.java.com.djrapitops.plan.Plan; import main.java.com.djrapitops.plan.Settings; import main.java.com.djrapitops.plan.data.UserData; import main.java.com.djrapitops.plan.database.Database; -import static org.bukkit.plugin.java.JavaPlugin.getPlugin; /** * @@ -27,6 +26,7 @@ public class DataCacheSaveQueue { /** * * @param plugin + * @param clear */ public DataCacheSaveQueue(Plan plugin, DataCacheClearQueue clear) { q = new ArrayBlockingQueue(Settings.PROCESS_SAVE_LIMIT.getNumber()); @@ -39,11 +39,11 @@ public class DataCacheSaveQueue { * @param data */ public void scheduleForSave(UserData data) { - Log.debug("Scheduling for save: "+data.getUuid()); + Log.debug(data.getUuid()+": Scheduling for save"); try { q.add(data); } catch (IllegalStateException e) { - getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); + Log.error(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); } } @@ -56,7 +56,7 @@ public class DataCacheSaveQueue { try { q.addAll(data); } catch (IllegalStateException e) { - getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); + Log.error(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); } } @@ -65,11 +65,11 @@ public class DataCacheSaveQueue { * @param data */ public void scheduleNewPlayer(UserData data) { - Log.debug("Scheduling new Player: "+data.getUuid()); + Log.debug(data.getUuid()+": Scheduling new Player"); try { q.add(data); } catch (IllegalStateException e) { - getPlugin(Plan.class).logError(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); + Log.error(Phrase.ERROR_TOO_SMALL_QUEUE.parse("Save Queue", Settings.PROCESS_SAVE_LIMIT.getNumber() + "")); } } @@ -86,15 +86,19 @@ public class DataCacheSaveQueue { * */ public void stop() { - s.stop(); + if (s != null) { + s.stop(); + } + s = null; + q.clear(); } } class SaveConsumer implements Runnable { private final BlockingQueue queue; - private final Database db; - private final DataCacheClearQueue clear; + private Database db; + private DataCacheClearQueue clear; private boolean run; SaveConsumer(BlockingQueue q, DataCacheClearQueue clear, Database db) { @@ -115,14 +119,19 @@ class SaveConsumer implements Runnable { } void consume(UserData data) { + if (db == null) { + return; + } UUID uuid = data.getUuid(); - Log.debug("Saving: "+uuid); + Log.debug(uuid+": Saving: "+uuid); try { db.saveUserData(uuid, data); data.stopAccessing(); - Log.debug("Saved! "+uuid); + Log.debug(uuid+": Saved!"); if (data.shouldClearAfterSave()) { - clear.scheduleForClear(uuid); + if (clear != null) { + clear.scheduleForClear(uuid); + } } } catch (SQLException ex) { // queue.add(data); @@ -132,6 +141,12 @@ class SaveConsumer implements Runnable { void stop() { run = false; + if (db != null) { + db = null; + } + if (clear != null) { + clear = null; + } } } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/handling/GamemodeHandling.java b/Plan/src/main/java/com/djrapitops/plan/data/handling/GamemodeHandling.java index 6e57670e5..95216a825 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/handling/GamemodeHandling.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/handling/GamemodeHandling.java @@ -5,7 +5,7 @@ */ package main.java.com.djrapitops.plan.data.handling; -import java.util.HashMap; +import java.util.Map; import main.java.com.djrapitops.plan.data.UserData; import org.bukkit.GameMode; @@ -31,7 +31,7 @@ public class GamemodeHandling { data.setLastGamemode(newGM); } lastGamemode = data.getLastGamemode(); - HashMap times = data.getGmTimes(); + Map times = data.getGmTimes(); Long currentGMTime = times.get(lastGamemode); if (currentGMTime == null) { currentGMTime = 0L; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/handling/LoginHandling.java b/Plan/src/main/java/com/djrapitops/plan/data/handling/LoginHandling.java index 1f4cd11c9..bf5bb51ec 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/handling/LoginHandling.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/handling/LoginHandling.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package main.java.com.djrapitops.plan.data.handling; import java.io.BufferedReader; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/listeners/PlanPlayerListener.java b/Plan/src/main/java/com/djrapitops/plan/data/listeners/PlanPlayerListener.java index 5c9fb0eb1..58f0a01ab 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/listeners/PlanPlayerListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/listeners/PlanPlayerListener.java @@ -55,7 +55,7 @@ public class PlanPlayerListener implements Listener { Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); handler.startSession(uuid); - Log.debug("PlayerJoinEvent: "+uuid); + Log.debug(uuid+": PlayerJoinEvent"); BukkitTask asyncNewPlayerCheckTask = (new BukkitRunnable() { @Override public void run() { @@ -68,11 +68,11 @@ public class PlanPlayerListener implements Listener { } else { handler.addToPool(loginInfo); } - Log.debug("PlayerJoinEvent_AsyncTask_END: "+uuid+" New:"+isNewPlayer); + Log.debug(uuid+": PlayerJoinEvent_AsyncTask_END, New:"+isNewPlayer); this.cancel(); } }).runTaskAsynchronously(plugin); - Log.debug("PlayerJoinEvent_END: "+uuid); + Log.debug(uuid+": PlayerJoinEvent_END"); } /** @@ -88,10 +88,10 @@ public class PlanPlayerListener implements Listener { Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); handler.endSession(uuid); - Log.debug("PlayerQuitEvent: "+uuid); + Log.debug(uuid+": PlayerQuitEvent"); handler.addToPool(new LogoutInfo(uuid, new Date().getTime(), player.isBanned(), player.getGameMode(), handler.getSession(uuid))); handler.saveCachedData(uuid); - Log.debug("PlayerQuitEvent_END: "+uuid); + Log.debug(uuid+": PlayerQuitEvent_END"); } /** diff --git a/Plan/src/main/java/com/djrapitops/plan/database/databases/SQLDB.java b/Plan/src/main/java/com/djrapitops/plan/database/databases/SQLDB.java index 4381aa27e..9dce9ca17 100644 --- a/Plan/src/main/java/com/djrapitops/plan/database/databases/SQLDB.java +++ b/Plan/src/main/java/com/djrapitops/plan/database/databases/SQLDB.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import main.java.com.djrapitops.plan.Log; @@ -21,7 +22,6 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.scheduler.BukkitRunnable; import static org.bukkit.Bukkit.getOfflinePlayer; -import static org.bukkit.Bukkit.getOfflinePlayer; /** * @@ -395,7 +395,7 @@ public abstract class SQLDB extends Database { } } if (!exceptions.isEmpty()) { - Log.errorMsg("SEVERE: MULTIPLE ERRORS OCCURRED: " + exceptions.size()); + Log.error("SEVERE: MULTIPLE ERRORS OCCURRED: " + exceptions.size()); Log.toLog(this.getClass().getName(), exceptions); } } @@ -444,7 +444,7 @@ public abstract class SQLDB extends Database { * @throws SQLException */ @Deprecated - public void saveNickList(int userId, HashSet names, String lastNick) throws SQLException { + public void saveNickList(int userId, Set names, String lastNick) throws SQLException { nicknamesTable.saveNickList(userId, names, lastNick); } @@ -478,7 +478,7 @@ public abstract class SQLDB extends Database { * @throws SQLException */ @Deprecated - public void saveIPList(int userId, HashSet ips) throws SQLException { + public void saveIPList(int userId, Set ips) throws SQLException { ipsTable.saveIPList(userId, ips); } @@ -489,7 +489,7 @@ public abstract class SQLDB extends Database { * @throws SQLException */ @Deprecated - public void saveGMTimes(int userId, HashMap gamemodeTimes) throws SQLException { + public void saveGMTimes(int userId, Map gamemodeTimes) throws SQLException { gmTimesTable.saveGMTimes(userId, gamemodeTimes); } diff --git a/Plan/src/main/java/com/djrapitops/plan/database/tables/GMTimesTable.java b/Plan/src/main/java/com/djrapitops/plan/database/tables/GMTimesTable.java index 61bac9bd5..70b73218e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/database/tables/GMTimesTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/database/tables/GMTimesTable.java @@ -4,8 +4,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Map; import main.java.com.djrapitops.plan.Log; import main.java.com.djrapitops.plan.database.databases.SQLDB; import org.bukkit.GameMode; @@ -90,7 +89,7 @@ public class GMTimesTable extends Table { } } - public void saveGMTimes(int userId, HashMap gamemodeTimes) throws SQLException { + public void saveGMTimes(int userId, Map gamemodeTimes) throws SQLException { if (gamemodeTimes == null || gamemodeTimes.isEmpty()) { return; } @@ -127,7 +126,7 @@ public class GMTimesTable extends Table { } } - private void addNewGMTimesRow(int userId, HashMap gamemodeTimes) throws SQLException { + private void addNewGMTimesRow(int userId, Map gamemodeTimes) throws SQLException { PreparedStatement statement = null; GameMode[] gms = new GameMode[]{GameMode.SURVIVAL, GameMode.CREATIVE, GameMode.ADVENTURE, GameMode.SPECTATOR}; try { diff --git a/Plan/src/main/java/com/djrapitops/plan/database/tables/IPsTable.java b/Plan/src/main/java/com/djrapitops/plan/database/tables/IPsTable.java index 7dde1f2c8..4565a1f7d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/database/tables/IPsTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/database/tables/IPsTable.java @@ -6,8 +6,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +import java.util.Set; import main.java.com.djrapitops.plan.Log; import main.java.com.djrapitops.plan.database.databases.SQLDB; @@ -79,7 +79,7 @@ public class IPsTable extends Table { } } - public void saveIPList(int userId, HashSet ips) throws SQLException { + public void saveIPList(int userId, Set ips) throws SQLException { if (ips == null) { return; } diff --git a/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java b/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java index 92ba1e176..9a6f43d22 100644 --- a/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/database/tables/NicknamesTable.java @@ -4,8 +4,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +import java.util.Set; import main.java.com.djrapitops.plan.Log; import main.java.com.djrapitops.plan.database.databases.SQLDB; @@ -106,7 +106,7 @@ public class NicknamesTable extends Table { } } - public void saveNickList(int userId, HashSet names, String lastNick) throws SQLException { + public void saveNickList(int userId, Set names, String lastNick) throws SQLException { if (names == null || names.isEmpty()) { return; } diff --git a/Plan/src/main/java/com/djrapitops/plan/ui/Html.java b/Plan/src/main/java/com/djrapitops/plan/ui/Html.java index 8e2f666fa..6f152ccec 100644 --- a/Plan/src/main/java/com/djrapitops/plan/ui/Html.java +++ b/Plan/src/main/java/com/djrapitops/plan/ui/Html.java @@ -14,379 +14,80 @@ import static org.bukkit.plugin.java.JavaPlugin.getPlugin; */ public enum Html { - /** - * - */ REPLACE0("REPLACE0"), - - /** - * - */ REPLACE1("REPLACE1"), - - /** - * - */ WARN_INACCURATE("

Data might be inaccurate, player has just registered.
"), - - /** - * - */ COLOR_0(""), - - /** - * - */ COLOR_1(""), - - /** - * - */ COLOR_2(""), - - /** - * - */ COLOR_3(""), - - /** - * - */ COLOR_4(""), - - /** - * - */ COLOR_5(""), - - /** - * - */ COLOR_6(""), - - /** - * - */ COLOR_7(""), - - /** - * - */ COLOR_8(""), - - /** - * - */ COLOR_9(""), - - /** - * - */ COLOR_a(""), - - /** - * - */ COLOR_b(""), - - /** - * - */ COLOR_c(""), - - /** - * - */ COLOR_d(""), - - /** - * - */ COLOR_e(""), - - /** - * - */ COLOR_f(""), - - /** - * - */ SPAN("" + REPLACE0 + ""), - - /** - * - */ BUTTON("" + REPLACE1 + ""), - - /** - * - */ BUTTON_CLASS("class=\"button\""), - - /** - * - */ LINK("" + REPLACE1 + ""), - - /** - * - */ LINK_CLASS("class=\"link\""), - - /** - * - */ IMG(""), - - /** - * - */ TOP_TOWNS("

Top 20 Towns

"), - - /** - * - */ TOP_FACTIONS("

Top 20 Factions

"), - - /** - * - */ TOTAL_BALANCE("

Server Total Balance: " + REPLACE0 + "

"), - - /** - * - */ TOTAL_VOTES("

Players have voted total of " + REPLACE0 + " times.

"), - - /** - * - */ PLOT_OPTIONS("

Plot options: " + REPLACE0 + "

"), - - /** - * - */ FRIENDS("

Friends with " + REPLACE0 + "

"), - - /** - * - */ BALANCE("

Balance: " + REPLACE0 + "

"), - - /** - * - */ BANNED("| " + SPAN.parse(COLOR_4.parse() + "Banned")), - - /** - * - */ OPERATOR(", Operator (Op)"), - - /** - * - */ ONLINE("| " + SPAN.parse(COLOR_2.parse() + "Online")), - - /** - * - */ OFFLINE("| " + SPAN.parse(COLOR_4.parse() + "Offline")), - - /** - * - */ ACTIVE("Player is Active"), - - /** - * - */ INACTIVE("Player is inactive"), - - /** - * - */ ERROR_LIST("Error Creating List

"), - - /** - * - */ HIDDEN("Hidden (config)"), - - /** - * - */ ERROR_NOT_SET("Error: Replace rule was not set"), - - /** - * - */ FACTION_NOT_FOUND("Faction not found"), - - /** - * - */ FACTION_NO_LEADER("No leader"), - - /** - * - */ FACTION_NO_FACTIONS("No Factions"), - - /** - * - */ WARPS("
Warps: " + REPLACE0), - - /** - * - */ ACHIEVEMENTS("
Achievements: " + REPLACE0 + "/" + REPLACE1), - - /** - * - */ JAILED("| Jailed"), - - /** - * - */ MUTED("| Muted"), - - /** - * - */ VOTES("
Has voted " + REPLACE0 + "times"), - - /** - * - */ FACTION("
Faction: " + REPLACE0 + " | Power: " + REPLACE1 + "/REPLACE2"), - - /** - * - */ TOWN("
Town: " + REPLACE0), - - /** - * - */ TOWN_NO_TOWNS("No Towns"), - - /** - * - */ GRAPH_BANNED("Banned"), - - /** - * - */ GRAPH_UNKNOWN("Unknown"), - - /** - * - */ GRAPH_INACTIVE("Inactive"), - - /** - * - */ GRAPH_ACTIVE("Active"), - - /** - * - */ GRAPH_ONLINE("Players Online"), - - /** - * - */ GRAPH_PLAYERS("Players"), - - /** - * - */ GRAPH_DATE("Date"), - - /** - * - */ TABLE_START_3(""), - - /** - * - */ TABLE_START_4("
REPLACE0REPLACE1REPLACE2
"), - - /** - * - */ TABLE_SESSIONS_START(TABLE_START_3.parse("Session Started", "Session Ended", "Session Length")), - - /** - * - */ TABLE_KILLS_START(TABLE_START_3.parse("Date", "Killed", "With")), - - /** - * - */ TABLE_FACTIONS_START(TABLE_START_4.parse("Faction", "Power", "Land", "Leader")), - - /** - * - */ TABLE_TOWNS_START(TABLE_START_4.parse("Town", "Residents", "Land", "Mayor")), - - /** - * - */ TABLELINE_2(""), - - /** - * - */ TABLELINE_3(""), - - /** - * - */ TABLELINE_4(""), - - /** - * - */ TABLELINE_PLAYERS("" + ""), - - /** - * - */ TABLELINE_3_CUSTOMKEY(""), - - /** - * - */ TABLELINE_3_CUSTOMKEY_1(""), - - /** - * - */ ERROR_TABLE_2(TABLELINE_2.parse("No data", "No data")), - - /** - * - */ TABLE_END("
REPLACE0REPLACE1REPLACE2REPLACE3
" + REPLACE0 + "" + REPLACE1 + "
" + REPLACE0 + "" + REPLACE1 + "REPLACE2
" + REPLACE0 + "" + REPLACE1 + "REPLACE2REPLACE3
REPLACE0REPLACE1REPLACE3REPLACE4REPLACE6REPLACE8REPLACE9
REPLACE1REPLACE3REPLACE5
REPLACE1REPLACE2REPLACE3
"), - - /** - * - */ SESSIONDATA_NONE("No Session Data available"), - - /** - * - */ KILLDATA_NONE("No Kills"),; private String html; diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/Analysis.java b/Plan/src/main/java/com/djrapitops/plan/utilities/Analysis.java index 4134b0e92..95342bd58 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/Analysis.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/Analysis.java @@ -153,7 +153,7 @@ public class Analysis { // Fill Dataset with userdata. rawData.stream().forEach((uData) -> { // try { - HashMap gmTimes = uData.getGmTimes(); + Map gmTimes = uData.getGmTimes(); if (gmTimes != null) { Long survival = gmTimes.get(GameMode.SURVIVAL); if (survival != null) { diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/NewPlayerCreator.java b/Plan/src/main/java/com/djrapitops/plan/utilities/NewPlayerCreator.java index b1c1d4b38..7d21ff725 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/NewPlayerCreator.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/NewPlayerCreator.java @@ -52,7 +52,7 @@ public class NewPlayerCreator { data.setLastGmSwapTime(zero); data.setDeaths(0); data.setMobKills(0); - Log.debug("Created a new UserData object for "+player.getUniqueId()); + Log.debug(player.getUniqueId()+": Created a new UserData object."); return data; } diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/PlaceholderUtils.java b/Plan/src/main/java/com/djrapitops/plan/utilities/PlaceholderUtils.java index e018f5669..d898e4995 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/PlaceholderUtils.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/PlaceholderUtils.java @@ -4,6 +4,7 @@ import java.io.FileNotFoundException; import java.util.Arrays; import java.util.Date; import java.util.HashMap; +import java.util.Map; import java.util.UUID; import main.java.com.djrapitops.plan.Phrase; import main.java.com.djrapitops.plan.Plan; @@ -51,7 +52,7 @@ public class PlaceholderUtils { replaceMap.put("%ops%", "" + data.getOps()); replaceMap.put("%refresh%", FormatUtils.formatTimeAmountSinceString("" + data.getRefreshDate(), new Date())); replaceMap.put("%totallogins%", "" + data.getTotalLoginTimes()); - replaceMap.put("%top20mostactive%", data.getTop20ActivePlayers()); + replaceMap.put("%top20mostactive%", Html.ERROR_NOT_SET.parse()); replaceMap.put("%recentlogins%", data.getRecentPlayers()); replaceMap.put("%deaths%", data.getTotalDeaths() + ""); replaceMap.put("%playerkills%", data.getTotalPlayerKills() + ""); @@ -133,7 +134,7 @@ public class PlaceholderUtils { int age = data.getDemData().getAge(); replaceMap.put("%age%", (age != -1) ? "" + age : Phrase.DEM_UNKNOWN + ""); replaceMap.put("%gender%", "" + data.getDemData().getGender().name().toLowerCase()); - HashMap gmTimes = data.getGmTimes(); + Map gmTimes = data.getGmTimes(); long gmThree; try { Long gm3 = gmTimes.get(GameMode.SPECTATOR); diff --git a/Plan/src/test/java/main/java/com/djrapitops/plan/data/UserDataTest.java b/Plan/src/test/java/main/java/com/djrapitops/plan/data/UserDataTest.java index 225f2d6d3..7744b08e1 100644 --- a/Plan/src/test/java/main/java/com/djrapitops/plan/data/UserDataTest.java +++ b/Plan/src/test/java/main/java/com/djrapitops/plan/data/UserDataTest.java @@ -11,6 +11,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import main.java.com.djrapitops.plan.Plan; import main.java.com.djrapitops.plan.data.DemographicsData; @@ -169,9 +170,9 @@ public class UserDataTest { test.addNickname(null); assertTrue("Didn't add 1", test.getNicknames().contains(one)); assertTrue("Didn't add 2", test.getNicknames().contains(two)); - assertTrue("1 is new", n); - assertTrue("2 is new", n1); - assertTrue("2 is not new", !n2); + assertTrue("1 is supposed to be new", n); + assertTrue("2 is supposed to be new", n1); + assertTrue("2 is not supposed to be new", !n2); assertTrue("Added null", !test.getNicknames().contains(null)); assertTrue("Added multiples", test.getNicknames().size() == 2); assertTrue("Last nickname was not one", test.getLastNick().equals(one)); @@ -243,7 +244,7 @@ public class UserDataTest { gmTimes.put(null, 0L); test.setGmTimes(gmTimes); test.setAllGMTimes(1L, 2L, 3L, 4L); - HashMap times = test.getGmTimes(); + Map times = test.getGmTimes(); assertTrue("Cleared gmTimes", !times.containsKey(null)); assertTrue("Not equal 0", times.get(GameMode.SURVIVAL) == 1L); assertTrue("Not equal 1", times.get(GameMode.CREATIVE) == 2L); diff --git a/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueueTest.java b/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueueTest.java index 7f34406f8..ab332c210 100644 --- a/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueueTest.java +++ b/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheProcessQueueTest.java @@ -152,7 +152,7 @@ public class DataCacheProcessQueueTest { * * @throws InterruptedException */ - @Ignore @Test + @Test public void testContainsUUID() throws InterruptedException { DataCacheProcessQueue q = new DataCacheProcessQueue(handler); UUID uuid = MockUtils.getPlayerUUID(); @@ -164,7 +164,6 @@ public class DataCacheProcessQueueTest { } }; q.stop(); - Thread.sleep(2000); q.addToPool(h); assertTrue(q.containsUUID(uuid)); } diff --git a/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueueTest.java b/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueueTest.java index 4f79e45f0..0328efa7d 100644 --- a/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueueTest.java +++ b/Plan/src/test/java/main/java/com/djrapitops/plan/data/cache/queue/DataCacheSaveQueueTest.java @@ -58,15 +58,6 @@ public class DataCacheSaveQueueTest { TestInit t = new TestInit(); assertTrue("Not set up", t.setUp()); plan = t.getPlanMock(); - PowerMock.mockStatic(JavaPlugin.class); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - EasyMock.expect(JavaPlugin.getPlugin(Plan.class)).andReturn(plan); - PowerMock.replay(JavaPlugin.class); calledSaveUserData = false; calledSaveUserData2 = false; db = new SQLiteDB(plan, "debug" + new Date().getTime()) { diff --git a/docs/Plan-javadoc.jar b/docs/Plan-javadoc.jar deleted file mode 100644 index 0c64983e62340c4fe908facd0fc9ee898eca3f02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83534 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%0DEo_NnbUJJRQu=7!foJ?7*m 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 Y_M@XtPJyqGGWg+SgFx8fV15VrKb286