consumer, String pluginVersion, Integer spigotID, String gitKey) {
+ String RepoURL = "https://git.t2code.net/api/v1/repos/" + gitKey + "/releases?limit=1";
+ if (!(boolean)T2CBlibConfig.VALUES.seePreReleaseUpdates.getValue() ) {
+ RepoURL = RepoURL + "&pre-release=false";
+ }
+ String finalRepoURL = RepoURL;
+
+ try {
+ URL url = new URL(finalRepoURL);
+ URLConnection yc = url.openConnection();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(
+ yc.getInputStream()));
+ String inputLine;
+ String data = "";
+ while ((inputLine = in.readLine()) != null)
+ data = inputLine;
+ in.close();
+ data = data.substring(1, data.length() - 1);
+ if (data.isEmpty()) {
+ consumer.accept(null);
+ return;
+ }
+ JSONObject obj = new JSONObject(data);
+ String updateTitle = obj.getString("name");
+ String version = obj.getString("tag_name");
+ String updateDescription = obj.getString("body").replace("\n", "
").replace("\r", "").replace("'", "''");
+ String updateUrl = obj.getString("html_url");
+ boolean preRelease = obj.getBoolean("prerelease");
+
+ String date = obj.getString("published_at");
+ SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a");
+ Date parsedDate = inputFormat.parse(date);
+ String publishedAt = outputFormat.format(parsedDate);
+
+ JSONArray downloadArray = obj.getJSONArray("assets");
+ String gitURL = updateUrl;
+ String downloadURL;
+ if (downloadArray.isEmpty()) {
+ downloadURL = "https://www.spigotmc.org/resources/" + spigotID;
+ } else {
+ downloadURL = downloadArray.getJSONObject(0).getString("browser_download_url");
+ }
+
+ if (!preRelease) {
+ downloadURL = "https://www.spigotmc.org/resources/" + spigotID;
+ updateUrl = "https://www.spigotmc.org/resources/" + spigotID;
+ }
+
+ T2CupdateWebData webData = new T2CupdateWebData(updateTitle, version, updateDescription, updateUrl, publishedAt, downloadURL, gitURL, preRelease);
+ consumer.accept(webData);
+ } catch (Exception var10) {
+ Boolean load = false;
+ if (T2CVupdateAPI.bungeePluginVersins.containsKey(plugin.getDescription().getName().toString())) {
+ load = T2CVupdateAPI.bungeePluginVersins.get(plugin.getDescription().getName().toString()).load;
+ }
+ T2CupdateObject update = new T2CupdateObject(
+ plugin.getDescription().getName().toString(),
+ pluginVersion,
+ null,
+ load,
+ false,
+ true
+ );
+ T2CVupdateAPI.bungeePluginVersins.put(plugin.getDescription().getName().toString(), update);
+ logger.error("Cannot look for updates: {}", var10.getMessage());
+ }
+
+ }
+}
diff --git a/src/main/java/net/t2code/t2codelib/VELOCITY/api/yml/T2CVconfigWriter.java b/src/main/java/net/t2code/t2codelib/VELOCITY/api/yml/T2CVconfigWriter.java
index 6a8bb10..12a191d 100644
--- a/src/main/java/net/t2code/t2codelib/VELOCITY/api/yml/T2CVconfigWriter.java
+++ b/src/main/java/net/t2code/t2codelib/VELOCITY/api/yml/T2CVconfigWriter.java
@@ -2,6 +2,7 @@ package net.t2code.t2codelib.VELOCITY.api.yml;// This class was created by JaTiT
import net.t2code.t2codelib.T2CconfigItem;
+import org.slf4j.Logger;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
@@ -15,7 +16,8 @@ public class T2CVconfigWriter {
- public static void createConfig(File configFile, T2CconfigItem[] manager, String... header) throws IOException {
+ public static void createConfig(Logger logger, File configFile, T2CconfigItem[] manager, String... header) throws IOException {
+
if (!configFile.exists()) {
configFile.getParentFile().mkdirs();
try {
@@ -99,7 +101,7 @@ public class T2CVconfigWriter {
try {
StringBuilder configContent = new StringBuilder();
for(String h : headers){
- configContent.append("# ").append(h).append("\n");
+ configContent.append(h).append("\n");
}
configContent.append("\n");
addSection(config, comments, configContent, "", 0);
diff --git a/src/main/java/net/t2code/t2codelib/VELOCITY/system/T2CodeVMain.java b/src/main/java/net/t2code/t2codelib/VELOCITY/system/T2CodeVMain.java
index e416d90..781c50d 100644
--- a/src/main/java/net/t2code/t2codelib/VELOCITY/system/T2CodeVMain.java
+++ b/src/main/java/net/t2code/t2codelib/VELOCITY/system/T2CodeVMain.java
@@ -2,5 +2,87 @@
package net.t2code.t2codelib.VELOCITY.system;
+import com.google.inject.Inject;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.plugin.PluginContainer;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.ProxyServer;
+import lombok.Getter;
+
+import net.t2code.t2codelib.Util;
+import net.t2code.t2codelib.VELOCITY.api.update.T2CVupdateAPI;
+import net.t2code.t2codelib.VELOCITY.system.bstats.Metrics;
+import net.t2code.t2codelib.VELOCITY.system.config.T2CVlibConfig;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
public class T2CodeVMain {
+
+ private final ProxyServer server;
+ @Getter
+ private static Logger logger;
+ @Getter
+ private PluginContainer plugin;
+ @Getter
+ private final long starttime;
+ @Getter
+ private static Path dataDirectory;
+
+ private final Metrics.Factory metricsFactory;
+
+
+ @Inject
+ public T2CodeVMain(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) {
+ starttime = System.currentTimeMillis();
+ this.server = server;
+ T2CodeVMain.logger = logger;
+ T2CodeVMain.dataDirectory = dataDirectory;
+ this.metricsFactory = metricsFactory;
+
+
+ }
+
+ @Subscribe
+ public void onLoad(ProxyInitializeEvent e) {
+ new pl(this, server);
+ logger.info("╔════════════════════════════════════");
+ // logger.info("║");
+ for (String s : Util.getLoadLogo()) {
+ logger.info("║ {}", s);
+ }
+ logger.info("║");
+ // Thread.sleep(5000);
+ logger.info("║ Author: {}", String.valueOf(plugin.getDescription().getAuthors()).replace("[", "").replace("]", ""));
+ logger.info("║ Version: {}", plugin.getDescription().getVersion().orElse("unknown"));
+ logger.info("║ Spigot: {}", Util.getSpigot());
+ logger.info("║ Discord: {}", Util.getDiscord());
+
+ try {
+ T2CVlibConfig.set(dataDirectory,logger);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ // new T2CVcmd(server, logger);
+ // server.getEventManager().register(this, new T2CVpluginMessagingCmd(server, logger));
+
+ logger.info("║");
+ logger.info("║ Plugin loaded successfully - {}ms", System.currentTimeMillis() - starttime);
+ logger.info("╚════════════════════════════════════");
+ Metrics.bStats(this, Util.getBstatsID(), metricsFactory);
+
+ T2CVupdateAPI.onUpdateCheckTimer(logger, plugin, server, Util.getVPrefix(), Util.getDiscord(), Util.getSpigotID(), Util.getGit());
+
+ }
+
+
+ public class pl {
+ public pl(Object pl, ProxyServer server) {
+ plugin = server.getPluginManager().fromInstance(pl)
+ .orElseThrow(() -> new IllegalArgumentException("The provided instance is not a plugin"));
+ }
+ }
}
diff --git a/src/main/java/net/t2code/t2codelib/VELOCITY/system/bstats/Metrics.java b/src/main/java/net/t2code/t2codelib/VELOCITY/system/bstats/Metrics.java
new file mode 100644
index 0000000..c226a03
--- /dev/null
+++ b/src/main/java/net/t2code/t2codelib/VELOCITY/system/bstats/Metrics.java
@@ -0,0 +1,1070 @@
+// This class was created by JaTiTV.
+
+package net.t2code.t2codelib.VELOCITY.system.bstats;
+
+/*
+ * 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.
+ *
+ * Disallowed 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 obfuscator 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.
+ */
+
+import com.google.inject.Inject;
+import com.velocitypowered.api.plugin.PluginContainer;
+import com.velocitypowered.api.plugin.PluginDescription;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.ProxyServer;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPOutputStream;
+import javax.net.ssl.HttpsURLConnection;
+
+import net.t2code.t2codelib.VELOCITY.system.T2CodeVMain;
+
+import org.slf4j.Logger;
+
+public class Metrics {
+
+ public static void bStats(T2CodeVMain plugin, Integer bstatsID, Metrics.Factory metricsFactory) {
+ int pluginId = bstatsID; // <-- Replace with the id of your plugin!
+ Metrics metrics = metricsFactory.make(plugin, pluginId);
+ }
+
+ /** A factory to create new Metrics classes. */
+ public static class Factory {
+
+ private final ProxyServer server;
+
+ private final Logger logger;
+
+ private final Path dataDirectory;
+
+ // The constructor is not meant to be called by the user.
+ // The instance is created using Dependency Injection
+ @Inject
+ private Factory(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
+ this.server = server;
+ this.logger = logger;
+ this.dataDirectory = dataDirectory;
+ }
+
+ /**
+ * Creates a new Metrics class.
+ *
+ * @param plugin The plugin instance.
+ * @param serviceId The id of the service. It can be found at What is my plugin id?
+ * Not to be confused with Velocity's {@link PluginDescription#getId()} method!
+ * @return A Metrics instance that can be used to register custom charts.
+ *
The return value can be ignored, when you do not want to register custom charts.
+ */
+ public Metrics make(Object plugin, int serviceId) {
+ return new Metrics(plugin, server, logger, dataDirectory, serviceId);
+ }
+ }
+
+ private final PluginContainer pluginContainer;
+
+ private final ProxyServer server;
+
+ private MetricsBase metricsBase;
+
+ private Metrics(
+ Object plugin, ProxyServer server, Logger logger, Path dataDirectory, int serviceId) {
+ pluginContainer =
+ server
+ .getPluginManager()
+ .fromInstance(plugin)
+ .orElseThrow(
+ () -> new IllegalArgumentException("The provided instance is not a plugin"));
+ this.server = server;
+ File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile();
+ MetricsConfig config;
+ try {
+ config = new MetricsConfig(configFile, true);
+ } catch (IOException e) {
+ logger.error("Failed to create bStats config", e);
+ return;
+ }
+ metricsBase =
+ new MetricsBase(
+ "velocity",
+ config.getServerUUID(),
+ serviceId,
+ config.isEnabled(),
+ this::appendPlatformData,
+ this::appendServiceData,
+ task -> server.getScheduler().buildTask(plugin, task).schedule(),
+ () -> true,
+ logger::warn,
+ logger::info,
+ config.isLogErrorsEnabled(),
+ config.isLogSentDataEnabled(),
+ config.isLogResponseStatusTextEnabled());
+ if (!config.didExistBefore()) {
+ // Send an info message when the bStats config file gets created for the first time
+ logger.info(
+ "Velocity and some of its plugins collect metrics and send them to bStats (https://bStats.org).");
+ logger.info(
+ "bStats collects some basic information for plugin authors, like how many people use");
+ logger.info(
+ "their plugin and their total player count. It's recommend to keep bStats enabled, but");
+ logger.info(
+ "if you're not comfortable with this, you can opt-out by editing the config.txt file in");
+ logger.info("the '/plugins/bStats/' folder and setting enabled to false.");
+ }
+ }
+
+ /** Shuts down the underlying scheduler service. */
+ public void shutdown() {
+ metricsBase.shutdown();
+ }
+
+ /**
+ * Adds a custom chart.
+ *
+ * @param chart The chart to add.
+ */
+ public void addCustomChart(CustomChart chart) {
+ if (metricsBase != null) {
+ metricsBase.addCustomChart(chart);
+ }
+ }
+
+ private void appendPlatformData(JsonObjectBuilder builder) {
+ builder.appendField("playerAmount", server.getPlayerCount());
+ builder.appendField("managedServers", server.getAllServers().size());
+ builder.appendField("onlineMode", server.getConfiguration().isOnlineMode() ? 1 : 0);
+ builder.appendField("velocityVersionVersion", server.getVersion().getVersion());
+ builder.appendField("velocityVersionName", server.getVersion().getName());
+ builder.appendField("velocityVersionVendor", server.getVersion().getVendor());
+ 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());
+ }
+
+ private void appendServiceData(JsonObjectBuilder builder) {
+ builder.appendField(
+ "pluginVersion", pluginContainer.getDescription().getVersion().orElse("unknown"));
+ }
+
+ public static class MetricsBase {
+
+ /** The version of the Metrics class. */
+ public static final String METRICS_VERSION = "3.0.2";
+
+ private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
+
+ private final ScheduledExecutorService scheduler;
+
+ 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) {
+ ScheduledThreadPoolExecutor scheduler =
+ new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics"));
+ // We want delayed tasks (non-periodic) that will execute in the future to be
+ // cancelled when the scheduler is shutdown.
+ // Otherwise, we risk preventing the server from shutting down even when
+ // MetricsBase#shutdown() is called
+ scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ this.scheduler = scheduler;
+ 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);
+ }
+
+ public void shutdown() {
+ scheduler.shutdown();
+ }
+
+ 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);
+ }
+ }
+ });
+ }
+
+ 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);
+ }
+ }
+ if (logResponseStatusText) {
+ infoLogger.accept("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.
+ */
+ 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();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ public static class MultiLineChart extends CustomChart {
+
+ private final Callable