From f8227081c073a10a84992f62e255c901fbce9a32 Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 17:27:08 +0100 Subject: [PATCH 1/6] update metrics --- src/main/java/tsp/headdb/Metrics.java | 1131 ++++++++++++++----------- 1 file changed, 637 insertions(+), 494 deletions(-) diff --git a/src/main/java/tsp/headdb/Metrics.java b/src/main/java/tsp/headdb/Metrics.java index e97f0d5..2016398 100644 --- a/src/main/java/tsp/headdb/Metrics.java +++ b/src/main/java/tsp/headdb/Metrics.java @@ -1,157 +1,113 @@ +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Unallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ package tsp.headdb; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; +public class Metrics { -/** - * bStats collects some data for plugin authors. - *

- * Check out https://bStats.org/ to learn more about bStats! - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -class Metrics { - - static { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D - final String defaultPackage = new String( - new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - - // Is bStats enabled on this server? - private boolean enabled; - - // Should failed requests be logged? - private static boolean logFailedRequests; - - // Should the sent data be logged? - private static boolean logSentData; - - // Should the response text be logged? - private static boolean logResponseStatusText; - - // The uuid of the server - private static String serverUUID; - - // The plugin private final Plugin plugin; - // The plugin id - private final int pluginId; - - // A list with all custom charts - private final List charts = new ArrayList<>(); + private final MetricsBase metricsBase; /** - * Class constructor. + * Creates a new Metrics instance. * - * @param plugin The plugin which stats should be submitted. - * @param pluginId The id of the plugin. - * It can be found at What is my plugin id? + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? */ - public Metrics(Plugin plugin, int pluginId) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } + public Metrics(JavaPlugin plugin, int serviceId) { this.plugin = plugin; - this.pluginId = pluginId; - // Get the config file File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); File configFile = new File(bStatsFolder, "config.yml"); YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists if (!config.isSet("serverUuid")) { - - // Add default values config.addDefault("enabled", true); - // Every server gets it's unique random id. config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? config.addDefault("logFailedRequests", false); - // Should the sent data be logged? config.addDefault("logSentData", false); - // Should the response text be logged? config.addDefault("logResponseStatusText", false); - // Inform the server owners about bStats - config.options().header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + - "To honor their work, you should not disable it.\n" + - "This has nearly no effect on the server performance!\n" + - "Check out https://bStats.org/ to learn more :)" - ).copyDefaults(true); + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); try { config.save(configFile); - } catch (IOException ignored) { } + } catch (IOException ignored) { + } } - // Load the data - enabled = config.getBoolean("enabled", true); - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - logSentData = config.getBoolean("logSentData", false); - logResponseStatusText = config.getBoolean("logResponseStatusText", false); - - if (enabled) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException ignored) { } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Checks if bStats is enabled. - * - * @return Whether bStats is enabled or not. - */ - public boolean isEnabled() { - return enabled; + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); } /** @@ -160,354 +116,265 @@ class Metrics { * @param chart The chart to add. */ public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); + metricsBase.addCustomChart(chart); } - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); } - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - public JsonObject getPluginData() { - JsonObject data = new JsonObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.addProperty("pluginName", pluginName); // Append the name of the plugin - data.addProperty("id", pluginId); // Append the id of the plugin - data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin - JsonArray customCharts = new JsonArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JsonObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.add("customCharts", customCharts); - - return data; + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); } - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private JsonObject getServerData() { - // Minecraft specific data - int playerAmount; + private int getPlayerAmount() { try { - // Around MC 1.8 the return type was changed to a collection from an array, - // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + return onlinePlayersMethod.getReturnType().equals(Collection.class) ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; } catch (Exception e) { - playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); } - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = Bukkit.getVersion(); - String bukkitName = Bukkit.getName(); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JsonObject data = new JsonObject(); - - data.addProperty("serverUUID", serverUUID); - - data.addProperty("playerAmount", playerAmount); - data.addProperty("onlineMode", onlineMode); - data.addProperty("bukkitVersion", bukkitVersion); - data.addProperty("bukkitName", bukkitName); - - data.addProperty("javaVersion", javaVersion); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); - - return data; } - /** - * Collects the data and sends it afterwards. - */ - private void submitData() { - final JsonObject data = getServerData(); + public static class MetricsBase { - JsonArray pluginData = new JsonArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.0.0"; - for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { - try { - Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); - if (plugin instanceof JsonObject) { - pluginData.add((JsonObject) plugin); - } else { // old bstats version compatibility - try { - Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); - if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { - Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); - jsonStringGetter.setAccessible(true); - String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); - pluginData.add(object); - } - } catch (ClassNotFoundException e) { - // minecraft version 1.14+ - if (logFailedRequests) { - this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); - } + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); } } - } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } - } - } catch (NoSuchFieldException ignored) { } + }); } - data.add("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(() -> { - try { - // Send the data - sendData(plugin, data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); } } - }).start(); - } - - /** - * Sends the data to the bStats server. - * - * @param plugin Any plugin. It's just used to get a logger instance. - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(Plugin plugin, JsonObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); } } - if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder); + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } } - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - final String chartId; /** - * Class constructor. + * Gzips the given string. * - * @param chartId The id of the chart. + * @param str The string to gzip. + * @return The gzipped string. */ - CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - private JsonObject getRequestJsonObject() { - JsonObject chart = new JsonObject(); - chart.addProperty("chartId", chartId); - try { - JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.add("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } + private static byte[] compress(final String str) throws IOException { + if (str == null) { return null; } - return chart; - } - - protected abstract JsonObject getChartData() throws Exception; - - } - - /** - * Represents a custom simple pie. - */ - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); } - data.addProperty("value", value); - return data; + return outputStream.toByteArray(); } } - /** - * Represents a custom advanced pie. - */ - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - - /** - * Represents a custom drilldown pie. - */ public static class DrilldownPie extends CustomChart { private final Callable>> callable; @@ -524,9 +391,8 @@ class Metrics { } @Override - public JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); Map> map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -534,32 +400,28 @@ class Metrics { } boolean reallyAllSkipped = true; for (Map.Entry> entryValues : map.entrySet()) { - JsonObject value = new JsonObject(); + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); boolean allSkipped = true; for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.addProperty(valueEntry.getKey(), valueEntry.getValue()); + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); allSkipped = false; } if (!allSkipped) { reallyAllSkipped = false; - values.add(entryValues.getKey(), value); + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); } } if (reallyAllSkipped) { // Null = skip the chart return null; } - data.add("values", values); - return data; + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); } } - /** - * Represents a custom single line chart. - */ - public static class SingleLineChart extends CustomChart { + public static class AdvancedPie extends CustomChart { - private final Callable callable; + private final Callable> callable; /** * Class constructor. @@ -567,28 +429,36 @@ class Metrics { * @param chartId The id of the chart. * @param callable The callable which is used to request the chart data. */ - public SingleLineChart(String chartId, Callable callable) { + public AdvancedPie(String chartId, Callable> callable) { super(chartId); this.callable = callable; } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - int value = callable.call(); - if (value == 0) { + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { // Null = skip the chart return null; } - data.addProperty("value", value); - return data; + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); } - } - /** - * Represents a custom multi line chart. - */ public static class MultiLineChart extends CustomChart { private final Callable> callable; @@ -605,9 +475,8 @@ class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -616,24 +485,20 @@ class Metrics { boolean allSkipped = true; for (Map.Entry entry : map.entrySet()) { if (entry.getValue() == 0) { - continue; // Skip this invalid + // Skip this invalid + continue; } allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); + valuesBuilder.appendField(entry.getKey(), entry.getValue()); } if (allSkipped) { // Null = skip the chart return null; } - data.add("values", values); - return data; + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); } - } - /** - * Represents a custom simple bar chart. - */ public static class SimpleBarChart extends CustomChart { private final Callable> callable; @@ -650,28 +515,80 @@ class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart return null; } for (Map.Entry entry : map.entrySet()) { - JsonArray categoryValues = new JsonArray(); - categoryValues.add(new JsonPrimitive(entry.getValue())); - values.add(entry.getKey(), categoryValues); + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); } - data.add("values", values); - return data; + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); } - } - /** - * Represents a custom advanced bar chart. - */ public static class AdvancedBarChart extends CustomChart { private final Callable> callable; @@ -688,9 +605,8 @@ class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -699,22 +615,249 @@ class Metrics { boolean allSkipped = true; for (Map.Entry entry : map.entrySet()) { if (entry.getValue().length == 0) { - continue; // Skip this invalid + // Skip this invalid + continue; } allSkipped = false; - JsonArray categoryValues = new JsonArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(new JsonPrimitive(categoryValue)); - } - values.add(entry.getKey(), categoryValues); + valuesBuilder.appendField(entry.getKey(), entry.getValue()); } if (allSkipped) { // Null = skip the chart return null; } - data.add("values", values); - return data; + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); } } + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } } \ No newline at end of file From ef90061b70b12711de396ce155041b8b389aff6d Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 18:37:38 +0100 Subject: [PATCH 2/6] patch a lot of code smells and possible bugs (4.2.2) --- src/main/java/tsp/headdb/HeadDB.java | 6 +- src/main/java/tsp/headdb/api/HeadAPI.java | 42 +++++---- .../tsp/headdb/command/HeadDBCommand.java | 19 ++-- .../tsp/headdb/command/SearchCommand.java | 4 +- .../tsp/headdb/command/TagSearchCommand.java | 4 +- .../tsp/headdb/economy/TreasuryProvider.java | 45 +++++----- .../tsp/headdb/implementation/Category.java | 24 +++-- .../java/tsp/headdb/implementation/Head.java | 21 +---- .../headdb/implementation/HeadDatabase.java | 55 ++++++------ .../tsp/headdb/implementation/LocalHead.java | 3 +- .../java/tsp/headdb/inventory/Button.java | 13 ++- .../tsp/headdb/inventory/InventoryUtils.java | 10 ++- .../java/tsp/headdb/inventory/PagedPane.java | 90 ++++++++----------- .../headdb/listener/PagedPaneListener.java | 4 +- .../tsp/headdb/storage/PlayerDataFile.java | 13 ++- src/main/java/tsp/headdb/util/Utils.java | 8 +- 16 files changed, 165 insertions(+), 196 deletions(-) diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java index cc866b8..3d81696 100644 --- a/src/main/java/tsp/headdb/HeadDB.java +++ b/src/main/java/tsp/headdb/HeadDB.java @@ -67,7 +67,7 @@ public class HeadDB extends JavaPlugin { initMetrics(); Utils.isLatestVersion(this, 84967, latest -> { - if (!latest) { + if (!Boolean.TRUE.equals(latest)) { Log.warning("There is a new update available for HeadDB on spigot!"); Log.warning("Download: https://www.spigotmc.org/resources/84967"); } @@ -112,10 +112,6 @@ public class HeadDB extends JavaPlugin { private void initMetrics() { Metrics metrics = new Metrics(this, 9152); - if (!metrics.isEnabled()) { - Log.debug("Metrics are disabled."); - return; - } metrics.addCustomChart(new Metrics.SimplePie("economy_provider", () -> { if (this.getEconomyProvider() != null) { diff --git a/src/main/java/tsp/headdb/api/HeadAPI.java b/src/main/java/tsp/headdb/api/HeadAPI.java index 22a4e4a..2be3278 100644 --- a/src/main/java/tsp/headdb/api/HeadAPI.java +++ b/src/main/java/tsp/headdb/api/HeadAPI.java @@ -2,7 +2,6 @@ package tsp.headdb.api; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import tsp.headdb.HeadDB; import tsp.headdb.implementation.Category; @@ -14,8 +13,8 @@ import tsp.headdb.storage.PlayerDataFile; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -29,6 +28,11 @@ public final class HeadAPI { private HeadAPI() {} + private static final String VALIDATION_PLAYER_NULL = "Player can not be null!"; + private static final String VALIDATION_CATEGORY_NULL = "Category can not be null!"; + private static final String VALIDATION_UUID_NULL = "UUID can not be null!"; + private static final String VALIDATION_VALUE_NULL = "Value can not be null!"; + /** * Main {@link HeadDatabase} that he HeadDB plugin uses. */ @@ -49,7 +53,7 @@ public final class HeadAPI { * @param player Target player */ public static void openDatabase(@Nonnull Player player) { - Validate.notNull(player, "Player can not be null!"); + Validate.notNull(player, VALIDATION_PLAYER_NULL); InventoryUtils.openDatabase(player); } @@ -61,8 +65,8 @@ public final class HeadAPI { * @param category Category to open */ public static void openCategoryDatabase(@Nonnull Player player, @Nonnull Category category) { - Validate.notNull(player, "Player can not be null!"); - Validate.notNull(category, "Category can not be null!"); + Validate.notNull(player, VALIDATION_PLAYER_NULL); + Validate.notNull(category, VALIDATION_CATEGORY_NULL); InventoryUtils.openCategoryDatabase(player, category); } @@ -74,7 +78,7 @@ public final class HeadAPI { * @param search Search term */ public static void openSearchDatabase(@Nonnull Player player, @Nonnull String search) { - Validate.notNull(player, "Player can not be null!"); + Validate.notNull(player, VALIDATION_PLAYER_NULL); Validate.notNull(search, "Search can not be null!"); InventoryUtils.openSearchDatabase(player, search); @@ -87,7 +91,7 @@ public final class HeadAPI { * @param tag Tag search term */ public static void openTagSearchDatabase(@Nonnull Player player, @Nonnull String tag) { - Validate.notNull(player, "Player can not be null!"); + Validate.notNull(player, VALIDATION_PLAYER_NULL); Validate.notNull(tag, "Tag can not be null!"); InventoryUtils.openTagSearchDatabase(player, tag); @@ -112,7 +116,7 @@ public final class HeadAPI { */ @Nullable public static Head getHeadByUniqueId(@Nonnull UUID uuid) { - Validate.notNull(uuid, "UUID can not be null!"); + Validate.notNull(uuid, VALIDATION_UUID_NULL); return database.getHeadByUniqueId(uuid); } @@ -152,7 +156,7 @@ public final class HeadAPI { */ @Nonnull public static List getHeadsByName(@Nonnull Category category, @Nonnull String name) { - Validate.notNull(category, "Category can not be null!"); + Validate.notNull(category, VALIDATION_CATEGORY_NULL); Validate.notNull(name, "Name can not be null!"); return database.getHeadsByName(category, name); @@ -166,7 +170,7 @@ public final class HeadAPI { */ @Nullable public static Head getHeadByValue(@Nonnull String value) { - Validate.notNull(value, "Value can not be null!"); + Validate.notNull(value, VALIDATION_VALUE_NULL); return database.getHeadByValue(value); } @@ -179,7 +183,7 @@ public final class HeadAPI { */ @Nonnull public static List getHeads(@Nonnull Category category) { - Validate.notNull(category, "Category can not be null!"); + Validate.notNull(category, VALIDATION_CATEGORY_NULL); return database.getHeads(category); } @@ -201,8 +205,8 @@ public final class HeadAPI { * @param textureValue The head's texture value */ public static void addFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) { - Validate.notNull(uuid, "UUID can not be null!"); - Validate.notNull(textureValue, "Value can not be null!"); + Validate.notNull(uuid, VALIDATION_UUID_NULL); + Validate.notNull(textureValue, VALIDATION_VALUE_NULL); HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.SET); } @@ -214,8 +218,8 @@ public final class HeadAPI { * @param textureValue The head's texture value */ public static void removeFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) { - Validate.notNull(uuid, "UUID can not be null!"); - Validate.notNull(textureValue, "Value can not be null!"); + Validate.notNull(uuid, VALIDATION_UUID_NULL); + Validate.notNull(textureValue, VALIDATION_VALUE_NULL); HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.REMOVE); } @@ -228,12 +232,12 @@ public final class HeadAPI { */ @Nonnull public static List getFavoriteHeads(@Nonnull UUID uuid) { - Validate.notNull(uuid, "UUID can not be null!"); + Validate.notNull(uuid, VALIDATION_UUID_NULL); return HeadDB.getInstance().getPlayerData().getFavoriteHeadsByTexture(uuid).stream() .map(HeadAPI::getHeadByValue) - .filter(head -> head != null) - .collect(Collectors.toList()); + .filter(Objects::nonNull) + .toList(); } /** @@ -248,7 +252,7 @@ public final class HeadAPI { return HeadDB.getInstance().getPlayerData().getEntries().stream() .map(entry -> Bukkit.getOfflinePlayer(UUID.fromString(entry))) .map(player -> new LocalHead(player.getUniqueId()).name(player.getName())) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/tsp/headdb/command/HeadDBCommand.java b/src/main/java/tsp/headdb/command/HeadDBCommand.java index e9f7516..da0f779 100644 --- a/src/main/java/tsp/headdb/command/HeadDBCommand.java +++ b/src/main/java/tsp/headdb/command/HeadDBCommand.java @@ -11,23 +11,21 @@ import tsp.headdb.util.Utils; public class HeadDBCommand implements CommandExecutor { - @Override - public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + private void handle(CommandSender sender, String[] args) { Localization localization = HeadDB.getInstance().getLocalization(); if (args.length == 0) { if (!sender.hasPermission("headdb.open")) { Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; + return; } - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); - return true; + return; } - Player player = (Player) sender; Utils.sendMessage(player, localization.getMessage("databaseOpen")); HeadAPI.openDatabase(player); - return true; + return; } String sub = args[0]; @@ -48,7 +46,7 @@ public class HeadDBCommand implements CommandExecutor { if (subCommand != null) { subCommand.handle(sender, args); - return true; + return; } Utils.sendMessage(sender, " "); @@ -62,6 +60,11 @@ public class HeadDBCommand implements CommandExecutor { Utils.sendMessage(sender, "&7 > &c/hdb update &9(u) &7- Forcefully update the database"); Utils.sendMessage(sender, "&7 > &c/hdb give &9(g) &c &6[amount] &7- Give player a head"); Utils.sendMessage(sender, " "); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + handle(sender, args); return true; } diff --git a/src/main/java/tsp/headdb/command/SearchCommand.java b/src/main/java/tsp/headdb/command/SearchCommand.java index e709b93..792e276 100644 --- a/src/main/java/tsp/headdb/command/SearchCommand.java +++ b/src/main/java/tsp/headdb/command/SearchCommand.java @@ -13,11 +13,10 @@ public class SearchCommand implements HeadSubCommand { Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); return; } - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); return; } - Player player = (Player) sender; if (args.length < 2) { Utils.sendMessage(player, "&c/hdb search "); @@ -34,7 +33,6 @@ public class SearchCommand implements HeadSubCommand { String name = builder.toString(); Utils.sendMessage(sender, "&7Searching for &e" + name); HeadAPI.openSearchDatabase(player, name); - return; } } diff --git a/src/main/java/tsp/headdb/command/TagSearchCommand.java b/src/main/java/tsp/headdb/command/TagSearchCommand.java index 664c409..bf601e2 100644 --- a/src/main/java/tsp/headdb/command/TagSearchCommand.java +++ b/src/main/java/tsp/headdb/command/TagSearchCommand.java @@ -19,16 +19,14 @@ public class TagSearchCommand implements HeadSubCommand { return; } - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); return; } - Player player = (Player) sender; String tag = args[1]; Utils.sendMessage(sender, "&7Searching for heads with tag &e" + tag); HeadAPI.openTagSearchDatabase(player, tag); - return; } } diff --git a/src/main/java/tsp/headdb/economy/TreasuryProvider.java b/src/main/java/tsp/headdb/economy/TreasuryProvider.java index 480f107..6b56682 100644 --- a/src/main/java/tsp/headdb/economy/TreasuryProvider.java +++ b/src/main/java/tsp/headdb/economy/TreasuryProvider.java @@ -34,16 +34,14 @@ public class TreasuryProvider implements BasicEconomyProvider { EconomySubscriber .asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s)) .thenCompose(val -> { - if (val) { + if (Boolean.TRUE.equals(val)) { return EconomySubscriber.asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s)); } else { return EconomySubscriber.asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s)); } }) .thenCompose(account -> EconomySubscriber.asFuture(s -> account.retrieveBalance(currency, s))) - .whenComplete((bal, ex) -> { - result.accept(bal.compareTo(cost) >= 0); - }); + .whenComplete((bal, ex) -> result.accept(bal.compareTo(cost) >= 0)); } @Override @@ -51,29 +49,27 @@ public class TreasuryProvider implements BasicEconomyProvider { EconomySubscriber .asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s)) .thenCompose(val -> { - if (val) { + if (Boolean.TRUE.equals(val)) { return EconomySubscriber.asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s)); } else { return EconomySubscriber.asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s)); } - }).whenComplete((account, ex) -> { - account.withdrawBalance( - amount, - transactionInitiator, - currency, - new EconomySubscriber() { - @Override - public void succeed(@NotNull BigDecimal bigDecimal) { - result.accept(true); - } + }).whenComplete((account, ex) -> account.withdrawBalance( + amount, + transactionInitiator, + currency, + new EconomySubscriber() { + @Override + public void succeed(@NotNull BigDecimal bigDecimal) { + result.accept(true); + } - @Override - public void fail(@NotNull EconomyException exception) { - result.accept(false); - exception.printStackTrace(); - } - }); - }); + @Override + public void fail(@NotNull EconomyException exception) { + result.accept(false); + Log.error(ex); + } + })); } @Override @@ -92,9 +88,10 @@ public class TreasuryProvider implements BasicEconomyProvider { if (rawCurrency == null || rawCurrency.isEmpty()) { currency = provider.getPrimaryCurrency(); } else { - currency = provider.getCurrencies().stream() + provider.getCurrencies().stream() .filter(currency -> currency.getIdentifier().equalsIgnoreCase(rawCurrency)) - .findFirst().get(); + .findFirst() + .ifPresentOrElse(c -> currency = c, () -> Log.error("Could not find currency: " + rawCurrency)); } } diff --git a/src/main/java/tsp/headdb/implementation/Category.java b/src/main/java/tsp/headdb/implementation/Category.java index 155f689..a5d1d19 100644 --- a/src/main/java/tsp/headdb/implementation/Category.java +++ b/src/main/java/tsp/headdb/implementation/Category.java @@ -1,12 +1,13 @@ package tsp.headdb.implementation; import org.bukkit.ChatColor; +import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import tsp.headdb.api.HeadAPI; import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; +import java.util.Objects; +import java.util.Optional; /** * Represents a category for heads @@ -29,8 +30,7 @@ public enum Category { private final String name; private final ChatColor color; private final int location; - private final Map item = new HashMap<>(); - public static final Category[] cache = values(); + private static final Category[] cache = values(); Category(String name, ChatColor color, int location) { this.name = name; @@ -56,9 +56,15 @@ public enum Category { * @return First valid head */ public ItemStack getItem() { - return HeadAPI.getHeads(this).stream() - .filter(head -> head != null) - .findFirst().get().getMenuItem(); + Optional result = HeadAPI.getHeads(this).stream() + .filter(Objects::nonNull) + .findFirst(); + + if (result.isPresent()) { + return result.get().getMenuItem(); + } else { + return new ItemStack(Material.PLAYER_HEAD); + } } /** @@ -78,4 +84,8 @@ public enum Category { return null; } + public static Category[] getCache() { + return cache; + } + } diff --git a/src/main/java/tsp/headdb/implementation/Head.java b/src/main/java/tsp/headdb/implementation/Head.java index f30c0ff..a915576 100644 --- a/src/main/java/tsp/headdb/implementation/Head.java +++ b/src/main/java/tsp/headdb/implementation/Head.java @@ -28,7 +28,6 @@ public class Head { private int id; private List tags; private ItemStack menuItem; - //Unimplemented private ItemStack itemStack; public Head() {} @@ -55,7 +54,7 @@ public class Head { profileField.set(meta, profile); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:"); - ex.printStackTrace(); + Log.error(ex); } meta.setLore(Arrays.asList( @@ -72,24 +71,6 @@ public class Head { return menuItem; } - /* For some reason this causes issues with some heads in categories not having lore - public ItemStack getItemStack() { - if (itemStack == null) { - itemStack = menuItem; - ItemMeta meta = itemStack.getItemMeta(); - meta.setDisplayName(HeadDB.getInstance().getLocalization().getMessage("head.name") - .replace("%name%", name) - .replace("%id%", String.valueOf(id)) - .replace("%value%", value) - .replace("%tags%", buildTagLore(tags))); - meta.setLore(HeadDB.getInstance().getLocalization().getData().getStringList("head.lore")); - itemStack.setItemMeta(meta); - } - - return itemStack; - } - */ - public String getName() { return name; } diff --git a/src/main/java/tsp/headdb/implementation/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/HeadDatabase.java index 488d3c8..b53f50b 100644 --- a/src/main/java/tsp/headdb/implementation/HeadDatabase.java +++ b/src/main/java/tsp/headdb/implementation/HeadDatabase.java @@ -19,6 +19,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -36,7 +37,7 @@ import javax.annotation.Nonnull; public class HeadDatabase { private final JavaPlugin plugin; - private final Map> HEADS = new HashMap<>(); + private final EnumMap> heads = new EnumMap<>(Category.class); private long refresh; private int timeout; private long updated; @@ -49,8 +50,8 @@ public class HeadDatabase { } public Head getHeadByValue(String value) { - List heads = getHeads(); - for (Head head : heads) { + List fetched = getHeads(); + for (Head head : fetched) { if (head.getValue().equals(value)) { return head; } @@ -60,8 +61,8 @@ public class HeadDatabase { } public Head getHeadByID(int id) { - List heads = getHeads(); - for (Head head : heads) { + List fetched = getHeads(); + for (Head head : fetched) { if (head.getId() == id) { return head; } @@ -71,8 +72,8 @@ public class HeadDatabase { } public Head getHeadByUniqueId(UUID uuid) { - List heads = getHeads(); - for (Head head : heads) { + List fetched = getHeads(); + for (Head head : fetched) { if (head.getUniqueId().equals(uuid)) { return head; } @@ -83,9 +84,9 @@ public class HeadDatabase { public List getHeadsByTag(String tag) { List result = new ArrayList<>(); - List heads = getHeads(); + List fetched = getHeads(); tag = tag.toLowerCase(Locale.ROOT); - for (Head head : heads) { + for (Head head : fetched) { for (String t : head.getTags()) { if (t.toLowerCase(Locale.ROOT).contains(tag)) { result.add(head); @@ -98,8 +99,8 @@ public class HeadDatabase { public List getHeadsByName(Category category, String name) { List result = new ArrayList<>(); - List heads = getHeads(category); - for (Head head : heads) { + List fetched = getHeads(category); + for (Head head : fetched) { String hName = ChatColor.stripColor(head.getName().toLowerCase(Locale.ROOT)); if (hName.contains(ChatColor.stripColor(name.toLowerCase(Locale.ROOT)))) { result.add(head); @@ -111,7 +112,7 @@ public class HeadDatabase { public List getHeadsByName(String name) { List result = new ArrayList<>(); - for (Category category : Category.values()) { + for (Category category : Category.getCache()) { result.addAll(getHeadsByName(category, name)); } @@ -120,7 +121,7 @@ public class HeadDatabase { @Nonnull public List getHeads(Category category) { - return HEADS.get(category) != null ? Collections.unmodifiableList(HEADS.get(category)) : new ArrayList<>(); + return heads.get(category) != null ? Collections.unmodifiableList(heads.get(category)) : new ArrayList<>(); } /** @@ -130,18 +131,18 @@ public class HeadDatabase { */ @Nonnull public List getHeads() { - List heads = new ArrayList<>(); - for (Category category : HEADS.keySet()) { - heads.addAll(getHeads(category)); + List result = new ArrayList<>(); + for (Category category : heads.keySet()) { + result.addAll(getHeads(category)); } - return heads; + return result; } public void getHeadsNoCache(Consumer>> resultSet) { Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> { Log.debug("[" + plugin.getName() + "] Updating database... "); - Map> result = new HashMap<>(); - Category[] categories = Category.cache; + EnumMap> result = new EnumMap<>(Category.class); + Category[] categories = Category.getCache(); for (Category category : categories) { Log.debug("Caching heads from: " + category.getName()); @@ -160,7 +161,7 @@ public class HeadDatabase { heads = gather("https://heads.pages.dev/archive/" + category.getName() + ".json", category); } catch (IOException | ParseException ex) { Log.error("Failed to fetch heads for " + category.getName() + "! (OF)"); // OF = Original-Fallback, both failed - ex.printStackTrace(); + Log.error(ex); } } } @@ -185,7 +186,7 @@ public class HeadDatabase { */ private List gather(String url, Category category) throws IOException, ParseException { long start = System.currentTimeMillis(); - List heads = new ArrayList<>(); + List headList = new ArrayList<>(); JSONParser parser = new JSONParser(); JSONArray array = (JSONArray) parser.parse(fetch(url)); for (Object o : array) { @@ -207,12 +208,12 @@ public class HeadDatabase { .category(category); nextId++; - heads.add(head); + headList.add(head); } long elapsed = (System.currentTimeMillis() - start); Log.debug(category.getName() + " -> Done! Time: " + elapsed + "ms (" + TimeUnit.MILLISECONDS.toSeconds(elapsed) + "s)"); - return heads; + return headList; } /** @@ -239,15 +240,15 @@ public class HeadDatabase { } public void update(Consumer>> result) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCache(heads -> { - if (heads == null) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCache(headsList -> { + if (headsList == null) { Log.error("[" + plugin.getName() + "] Failed to update database! Check above for any errors."); result.accept(null); return; } - HEADS.clear(); - HEADS.putAll(heads); + heads.clear(); + heads.putAll(headsList); result.accept(heads); Bukkit.getPluginManager().callEvent(new DatabaseUpdateEvent(this, heads)); })); diff --git a/src/main/java/tsp/headdb/implementation/LocalHead.java b/src/main/java/tsp/headdb/implementation/LocalHead.java index df47e3c..dfaffd0 100644 --- a/src/main/java/tsp/headdb/implementation/LocalHead.java +++ b/src/main/java/tsp/headdb/implementation/LocalHead.java @@ -8,6 +8,7 @@ import org.bukkit.inventory.meta.SkullMeta; import tsp.headdb.util.Utils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -68,7 +69,7 @@ public class LocalHead extends Head { @Override public List getTags() { - return null; + return Collections.emptyList(); } @Override diff --git a/src/main/java/tsp/headdb/inventory/Button.java b/src/main/java/tsp/headdb/inventory/Button.java index c8f48cb..7d1040d 100644 --- a/src/main/java/tsp/headdb/inventory/Button.java +++ b/src/main/java/tsp/headdb/inventory/Button.java @@ -11,10 +11,10 @@ import java.util.function.Consumer; */ public class Button { - private static int counter; - private final int ID = counter++; + private static int counter = 0; + private static final int id = counter++; - private ItemStack itemStack; + private final ItemStack itemStack; private Consumer action; /** @@ -67,15 +67,14 @@ public class Button { if (this == o) { return true; } - if (!(o instanceof Button)) { + if (!(o instanceof Button button)) { return false; } - Button button = (Button) o; - return ID == button.ID; + return id == button.id; } @Override public int hashCode() { - return Objects.hash(ID); + return Objects.hash(id); } } diff --git a/src/main/java/tsp/headdb/inventory/InventoryUtils.java b/src/main/java/tsp/headdb/inventory/InventoryUtils.java index c58c970..8830e8d 100644 --- a/src/main/java/tsp/headdb/inventory/InventoryUtils.java +++ b/src/main/java/tsp/headdb/inventory/InventoryUtils.java @@ -31,6 +31,8 @@ import java.util.function.Consumer; */ public class InventoryUtils { + private InventoryUtils() {} + private static final Localization localization = HeadDB.getInstance().getLocalization(); private static final Map uiLocation = new HashMap<>(); private static final Map uiItem = new HashMap<>(); @@ -179,7 +181,7 @@ public class InventoryUtils { Inventory inventory = Bukkit.createInventory(null, 54, replace(localization.getMessage("menu.main"), HeadAPI.getHeads().size(), "Main", "None", player)); - for (Category category : Category.cache) { + for (Category category : Category.getCache()) { ItemStack item = getUIItem(category.getName(), category.getItem()); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(Utils.colorize(localization.getMessage("menu.heads." + category.getName()))); @@ -325,9 +327,9 @@ public class InventoryUtils { // If the cost is higher than zero, attempt to charge for it. if (cost.compareTo(BigDecimal.ZERO) > 0) { economyProvider.canPurchase(player, cost, paymentResult -> { - if (paymentResult) { + if (Boolean.TRUE.equals(paymentResult)) { economyProvider.charge(player, cost, chargeResult -> { - if (chargeResult) { + if (Boolean.TRUE.equals(chargeResult)) { Utils.sendMessage(player, String.format(localization.getMessage("purchasedHead"), amount, description, cost)); Utils.playSound(player, "paid"); result.accept(true); @@ -353,7 +355,7 @@ public class InventoryUtils { public static void purchaseHead(Player player, Head head, int amount, String category, String description) { Utils.sendMessage(player, String.format(localization.getMessage("processPayment"), amount, head.getName())); processPayment(player, amount, category, description, result -> { - if (result) { + if (Boolean.TRUE.equals(result)) { PlayerHeadPurchaseEvent event = new PlayerHeadPurchaseEvent(player, head, getCategoryCost(player, category)); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { diff --git a/src/main/java/tsp/headdb/inventory/PagedPane.java b/src/main/java/tsp/headdb/inventory/PagedPane.java index fde3210..e41edfb 100644 --- a/src/main/java/tsp/headdb/inventory/PagedPane.java +++ b/src/main/java/tsp/headdb/inventory/PagedPane.java @@ -241,43 +241,41 @@ public class PagedPane implements InventoryHolder { inventory.setItem(inventory.getSize() - 2, itemStack); } - { - String name = String.format( - Locale.ROOT, - "&3&lPage &a&l%d &7/ &c&l%d", - getCurrentPage(), getPageAmount() - ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getMenuItem(), - name, - "&7Left-Click to go to the &cMain Menu", - "&7Right-Click to go to a &6Specific Page"); - controlMain = new Button(itemStack, event -> { - if (event.getClick() == ClickType.RIGHT) { - new AnvilGUI.Builder() - .onComplete((player, text) -> { - try { - int i = Integer.parseInt(text); - if (i > getPageAmount()) { - Utils.sendMessage(player, "&cPage number is out of bounds! Max: &e" + getPageAmount()); - return AnvilGUI.Response.text("&cOut of bounds!"); - } - selectPage(i - 1); - return AnvilGUI.Response.openInventory(this.getInventory()); - } catch (NumberFormatException nfe) { - Utils.sendMessage(player, "&cValue must be a number!"); - return AnvilGUI.Response.text(Utils.colorize("&cValue must be a number!")); + String name = String.format( + Locale.ROOT, + "&3&lPage &a&l%d &7/ &c&l%d", + getCurrentPage(), getPageAmount() + ); + ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getMenuItem(), + name, + "&7Left-Click to go to the &cMain Menu", + "&7Right-Click to go to a &6Specific Page"); + controlMain = new Button(itemStack, event -> { + if (event.getClick() == ClickType.RIGHT) { + new AnvilGUI.Builder() + .onComplete((player, text) -> { + try { + int i = Integer.parseInt(text); + if (i > getPageAmount()) { + Utils.sendMessage(player, "&cPage number is out of bounds! Max: &e" + getPageAmount()); + return AnvilGUI.Response.text("&cOut of bounds!"); } - }) - .title("Select Page") - .text("Page number...") - .plugin(HeadDB.getInstance()) - .open((Player) event.getWhoClicked()); - } else { - InventoryUtils.openDatabase((Player) event.getWhoClicked()); - } - }); - inventory.setItem(inventory.getSize() - 5, itemStack); - } + selectPage(i - 1); + return AnvilGUI.Response.openInventory(this.getInventory()); + } catch (NumberFormatException nfe) { + Utils.sendMessage(player, "&cValue must be a number!"); + return AnvilGUI.Response.text(Utils.colorize("&cValue must be a number!")); + } + }) + .title("Select Page") + .text("Page number...") + .plugin(HeadDB.getInstance()) + .open((Player) event.getWhoClicked()); + } else { + InventoryUtils.openDatabase((Player) event.getWhoClicked()); + } + }); + inventory.setItem(inventory.getSize() - 5, itemStack); } private void fillRow(int rowIndex, ItemStack itemStack, Inventory inventory) { @@ -291,29 +289,11 @@ public class PagedPane implements InventoryHolder { protected ItemStack setMeta(ItemStack itemStack, String name, String... lore) { ItemMeta meta = itemStack.getItemMeta(); meta.setDisplayName(Utils.colorize(name)); - meta.setLore(Arrays.stream(lore).map(this::color).collect(Collectors.toList())); + meta.setLore(Arrays.stream(lore).map(this::color).toList()); itemStack.setItemMeta(meta); return itemStack; } - @SuppressWarnings("WeakerAccess") - @Deprecated - protected ItemStack getItemStack(Material type, int durability, String name, String... lore) { - ItemStack itemStack = new ItemStack(type, 1, (short) durability); - - ItemMeta itemMeta = itemStack.getItemMeta(); - - if (name != null) { - itemMeta.setDisplayName(color(name)); - } - if (lore != null && lore.length != 0) { - itemMeta.setLore(Arrays.stream(lore).map(this::color).collect(Collectors.toList())); - } - itemStack.setItemMeta(itemMeta); - - return itemStack; - } - @SuppressWarnings("WeakerAccess") protected String color(String input) { return ChatColor.translateAlternateColorCodes('&', input); diff --git a/src/main/java/tsp/headdb/listener/PagedPaneListener.java b/src/main/java/tsp/headdb/listener/PagedPaneListener.java index 6d20774..a8d2dec 100644 --- a/src/main/java/tsp/headdb/listener/PagedPaneListener.java +++ b/src/main/java/tsp/headdb/listener/PagedPaneListener.java @@ -20,8 +20,8 @@ public class PagedPaneListener implements Listener { public void onClick(InventoryClickEvent event) { InventoryHolder holder = event.getInventory().getHolder(); - if (holder instanceof PagedPane) { - ((PagedPane) holder).onClick(event); + if (holder instanceof PagedPane pane) { + pane.onClick(event); } } } diff --git a/src/main/java/tsp/headdb/storage/PlayerDataFile.java b/src/main/java/tsp/headdb/storage/PlayerDataFile.java index 81ed280..5e1f8ec 100644 --- a/src/main/java/tsp/headdb/storage/PlayerDataFile.java +++ b/src/main/java/tsp/headdb/storage/PlayerDataFile.java @@ -30,11 +30,11 @@ public class PlayerDataFile { public PlayerDataFile(String name) { HeadDB plugin = HeadDB.getInstance(); // This check avoids warning in console - if (plugin.getResource(name) != null && !new File(plugin.getDataFolder() + "/" + name).exists()) { + if (plugin.getResource(name) != null && !new File(plugin.getDataFolder(), name).exists()) { plugin.saveResource(name, false); } - this.file = new File(plugin.getDataFolder() + "/" + name); + this.file = new File(plugin.getDataFolder(), name); } @Nonnull @@ -127,15 +127,12 @@ public class PlayerDataFile { return; } - FileWriter writer; - try { - writer = new FileWriter(file); + try (FileWriter writer = new FileWriter(file)) { writer.write(main.toString()); - writer.close(); Log.debug("Saved data to " + file.getName()); - } catch (IOException e) { + } catch (IOException ex) { Log.error("Failed to save player_data.json contents!"); - Log.error(e); + Log.error(ex); } } diff --git a/src/main/java/tsp/headdb/util/Utils.java b/src/main/java/tsp/headdb/util/Utils.java index ea4a41e..4c0f1d1 100644 --- a/src/main/java/tsp/headdb/util/Utils.java +++ b/src/main/java/tsp/headdb/util/Utils.java @@ -21,7 +21,9 @@ import java.util.regex.Pattern; /** * Several utilities used by the plugin */ -public class Utils { +public final class Utils { + + private Utils() {} private static final FileConfiguration config = HeadDB.getInstance().getConfig(); public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"); @@ -45,9 +47,9 @@ public class Utils { connection.setRequestProperty("User-Agent", plugin.getName() + "-VersionChecker"); latest.accept(new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(plugin.getDescription().getVersion())); - } catch (IOException e) { + } catch (IOException ex) { latest.accept(true); // Assume the version is latest if checking fails - e.printStackTrace(); + Log.error(ex); } }); } From 69808f451678d0180d67dd05aba22650048f5988 Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 18:43:29 +0100 Subject: [PATCH 3/6] typo --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index cd05e9d..726b2cc 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,4 +1,4 @@ -name: SonarCLoud Scanner +name: SonarCloud Scanner on: push: branches: From 17b62f359c1ee177c83a8b61fd8b02725f59ee6b Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 19:00:56 +0100 Subject: [PATCH 4/6] exclude metrics --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index d44e4b4..91089d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ TheSilentPro_HeadDB silent https://sonarcloud.io + src/main/java/tsp/headdb/Metrics 84967 From 19f117f9d3d5b8f03293d897a9c1c642cc763808 Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 19:03:11 +0100 Subject: [PATCH 5/6] exclude pls --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 91089d3..913bf78 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ TheSilentPro_HeadDB silent https://sonarcloud.io - src/main/java/tsp/headdb/Metrics + src/main/java/tsp/headdb/Metrics.java 84967 From 474ab18d3cc593bb76c1b61520bf42dbf5390d77 Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 18 Mar 2022 19:17:13 +0100 Subject: [PATCH 6/6] disable coverage --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 913bf78..f00de03 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ silent https://sonarcloud.io src/main/java/tsp/headdb/Metrics.java + * 84967