mirror of
https://github.com/FancyInnovations/FancyPlugins.git
synced 2025-12-06 07:43:36 +00:00
Add libs
This commit is contained in:
122
libraries/common/.gitignore
vendored
Normal file
122
libraries/common/.gitignore
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# Eclipse
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
plugin/bin/
|
||||
api/bin/
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
target/
|
||||
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.flattened-pom.xml
|
||||
|
||||
# Common working directory
|
||||
run/
|
||||
build
|
||||
.gradle
|
||||
/*.jar
|
||||
13
libraries/common/README.md
Normal file
13
libraries/common/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# How to use
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
maven("https://repo.fancyplugins.de/releases/")
|
||||
// or
|
||||
maven("https://repo.fancyplugins.de/snapshots/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("de.oliver:FancyLib:<version>")
|
||||
}
|
||||
```
|
||||
91
libraries/common/build.gradle.kts
Normal file
91
libraries/common/build.gradle.kts
Normal file
@@ -0,0 +1,91 @@
|
||||
plugins {
|
||||
id("java")
|
||||
id("maven-publish")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
group = "de.oliver"
|
||||
version = "35"
|
||||
description = "Library for all Fancy plugins"
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.fancyplugins.de/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT")
|
||||
compileOnly("de.oliver.FancyAnalytics:logger:0.0.5")
|
||||
|
||||
// database drivers
|
||||
compileOnly("org.xerial:sqlite-jdbc:3.46.0.0")
|
||||
compileOnly("mysql:mysql-connector-java:8.0.33")
|
||||
|
||||
// testing
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.1")
|
||||
testImplementation("com.google.code.gson:gson:2.11.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "fancypluginsReleases"
|
||||
url = uri("https://repo.fancyplugins.de/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "fancypluginsSnapshots"
|
||||
url = uri("https://repo.fancyplugins.de/snapshots")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
groupId = project.group.toString()
|
||||
artifactId = project.name
|
||||
version = project.version.toString()
|
||||
from(project.components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
|
||||
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
|
||||
// See https://openjdk.java.net/jeps/247 for more information.
|
||||
options.release.set(17)
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
processResources {
|
||||
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
public class ConfigHelper {
|
||||
|
||||
public static Object getOrDefault(FileConfiguration config, String path, Object defaultVal) {
|
||||
if (!config.contains(path)) {
|
||||
config.set(path, defaultVal);
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
return config.get(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
|
||||
import de.oliver.fancylib.gui.inventoryClick.InventoryClickListener;
|
||||
import de.oliver.fancylib.gui.inventoryClick.impl.CancelInventoryItemClick;
|
||||
import de.oliver.fancylib.gui.inventoryClick.impl.ChangePageInventoryItemClick;
|
||||
import de.oliver.fancylib.itemClick.PlayerInteractListener;
|
||||
import de.oliver.fancylib.serverSoftware.ServerSoftware;
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.BukkitScheduler;
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.FancyScheduler;
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.FoliaScheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public class FancyLib {
|
||||
|
||||
private static FancyLib instance;
|
||||
private static ExtendedFancyLogger logger = new ExtendedFancyLogger("FancyLib");
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final FancyScheduler scheduler;
|
||||
|
||||
public FancyLib(JavaPlugin plugin) {
|
||||
instance = this;
|
||||
this.plugin = plugin;
|
||||
this.scheduler = ServerSoftware.isFolia()
|
||||
? new FoliaScheduler(plugin)
|
||||
: new BukkitScheduler(plugin);
|
||||
}
|
||||
|
||||
public static FancyLib getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static ExtendedFancyLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listeners for the inventory click and player interact events.
|
||||
*/
|
||||
public void registerListeners() {
|
||||
CancelInventoryItemClick.INSTANCE.register();
|
||||
ChangePageInventoryItemClick.INSTANCE.register();
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(new InventoryClickListener(), plugin);
|
||||
Bukkit.getPluginManager().registerEvents(new PlayerInteractListener(), plugin);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public FancyScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public JavaPlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
111
libraries/common/src/main/java/de/oliver/fancylib/FileUtils.java
Normal file
111
libraries/common/src/main/java/de/oliver/fancylib/FileUtils.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static String getSHA256Checksum(File file) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
byte[] byteArray = new byte[1024];
|
||||
int bytesCount;
|
||||
|
||||
// Read the file data and update the MessageDigest
|
||||
while ((bytesCount = fis.read(byteArray)) != -1) {
|
||||
digest.update(byteArray, 0, bytesCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the final hash bytes
|
||||
byte[] hashBytes = digest.digest();
|
||||
|
||||
// Convert hash bytes to hex format
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte hashByte : hashBytes) {
|
||||
String hex = Integer.toHexString(0xff & hashByte);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
|
||||
return hexString.toString();
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
return "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
public static File findFirstFileByName(File directory, String name) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null) {
|
||||
return null;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.getName().toLowerCase().contains(name.toLowerCase())) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String readResource(String name) {
|
||||
URL url = getClass().getClassLoader().getResource(name);
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
URLConnection connection = null;
|
||||
try {
|
||||
connection = url.openConnection();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
connection.setUseCaches(false);
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
byte[] file_raw = new byte[inputStream.available()];
|
||||
inputStream.read(file_raw);
|
||||
inputStream.close();
|
||||
return new String(file_raw, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void saveFile(Plugin plugin, String name) {
|
||||
URL url = getClass().getClassLoader().getResource(name);
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
File file = new File(plugin.getDataFolder() + File.separator + name);
|
||||
if (file.exists()) return;
|
||||
if (!file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
URLConnection connection = null;
|
||||
try {
|
||||
connection = url.openConnection();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
connection.setUseCaches(false);
|
||||
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||
int read;
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
byte[] bytes = new byte[1024];
|
||||
while ((read = inputStream.read(bytes)) != -1) {
|
||||
outputStream.write(bytes, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Deprecated
|
||||
public class MessageHelper {
|
||||
private static String primaryColor = "#59bdff";
|
||||
private static String secondaryColor = "#6696e3";
|
||||
private static String successColor = "#81e366";
|
||||
private static String warningColor = "#e3ca66";
|
||||
private static String errorColor = "#e36666";
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull String transform(@NotNull String message) {
|
||||
message = message.replace("<primaryColor>", "<color:" + primaryColor + ">")
|
||||
.replace("<secondaryColor>", "<color:" + secondaryColor + ">")
|
||||
.replace("<successColor>", "<color:" + successColor + ">")
|
||||
.replace("<warningColor>", "<color:" + warningColor + ">")
|
||||
.replace("<errorColor>", "<color:" + errorColor + ">");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull Component send(@Nullable CommandSender receiver, @NotNull String message) {
|
||||
Component msg = MiniMessage.miniMessage().deserialize(transform(message));
|
||||
if (receiver != null) {
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull Component info(@Nullable CommandSender receiver, @NotNull String message) {
|
||||
Component msg = MiniMessage.miniMessage().deserialize(transform("<primaryColor>" + message));
|
||||
if (receiver != null) {
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull Component success(@Nullable CommandSender receiver, @NotNull String message) {
|
||||
Component msg = MiniMessage.miniMessage().deserialize(transform("<successColor>" + message));
|
||||
if (receiver != null) {
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull Component warning(@Nullable CommandSender receiver, @NotNull String message) {
|
||||
Component msg = MiniMessage.miniMessage().deserialize(transform("<warningColor>" + message));
|
||||
if (receiver != null) {
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static @NotNull Component error(@Nullable CommandSender receiver, @NotNull String message) {
|
||||
Component msg = MiniMessage.miniMessage().deserialize(transform("<errorColor>" + message));
|
||||
if (receiver != null) {
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String getPrimaryColor() {
|
||||
return primaryColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setPrimaryColor(String primaryColor) {
|
||||
MessageHelper.primaryColor = primaryColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String getSecondaryColor() {
|
||||
return secondaryColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setSecondaryColor(String secondaryColor) {
|
||||
MessageHelper.secondaryColor = secondaryColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String getSuccessColor() {
|
||||
return successColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setSuccessColor(String successColor) {
|
||||
MessageHelper.successColor = successColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String getWarningColor() {
|
||||
return warningColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setWarningColor(String warningColor) {
|
||||
MessageHelper.warningColor = warningColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String getErrorColor() {
|
||||
return errorColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setErrorColor(String errorColor) {
|
||||
MessageHelper.errorColor = errorColor;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static Component removeDecoration(Component component, TextDecoration decoration) {
|
||||
return component.decoration(decoration, TextDecoration.State.FALSE);
|
||||
}
|
||||
}
|
||||
881
libraries/common/src/main/java/de/oliver/fancylib/Metrics.java
Normal file
881
libraries/common/src/main/java/de/oliver/fancylib/Metrics.java
Normal file
@@ -0,0 +1,881 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package de.oliver.fancylib;
|
||||
|
||||
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.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.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.java.JavaPlugin;
|
||||
|
||||
public class Metrics {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
private final MetricsBase metricsBase;
|
||||
|
||||
/**
|
||||
* Creates a new Metrics instance.
|
||||
*
|
||||
* @param plugin Your plugin instance.
|
||||
* @param serviceId The id of the service. It can be found at <a
|
||||
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
|
||||
*/
|
||||
public Metrics(JavaPlugin plugin, int serviceId) {
|
||||
this.plugin = plugin;
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
File configFile = new File(bStatsFolder, "config.yml");
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||
if (!config.isSet("serverUuid")) {
|
||||
config.addDefault("enabled", true);
|
||||
config.addDefault("serverUuid", UUID.randomUUID().toString());
|
||||
config.addDefault("logFailedRequests", false);
|
||||
config.addDefault("logSentData", false);
|
||||
config.addDefault("logResponseStatusText", false);
|
||||
// Inform the server owners about bStats
|
||||
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) {
|
||||
}
|
||||
}
|
||||
// Load the data
|
||||
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);
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
metricsBase.addCustomChart(chart);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private void appendServiceData(JsonObjectBuilder builder) {
|
||||
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
|
||||
}
|
||||
|
||||
private int getPlayerAmount() {
|
||||
try {
|
||||
// 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");
|
||||
return onlinePlayersMethod.getReturnType().equals(Collection.class)
|
||||
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
|
||||
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
|
||||
} catch (Exception e) {
|
||||
// Just use the new method if the reflection failed
|
||||
return Bukkit.getOnlinePlayers().size();
|
||||
}
|
||||
}
|
||||
|
||||
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<JsonObjectBuilder> appendPlatformDataConsumer;
|
||||
|
||||
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
|
||||
|
||||
private final Consumer<Runnable> submitTaskConsumer;
|
||||
|
||||
private final Supplier<Boolean> checkServiceEnabledSupplier;
|
||||
|
||||
private final BiConsumer<String, Throwable> errorLogger;
|
||||
|
||||
private final Consumer<String> infoLogger;
|
||||
|
||||
private final boolean logErrors;
|
||||
|
||||
private final boolean logSentData;
|
||||
|
||||
private final boolean logResponseStatusText;
|
||||
|
||||
private final Set<CustomChart> 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<JsonObjectBuilder> appendPlatformDataConsumer,
|
||||
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
|
||||
Consumer<Runnable> submitTaskConsumer,
|
||||
Supplier<Boolean> checkServiceEnabledSupplier,
|
||||
BiConsumer<String, Throwable> errorLogger,
|
||||
Consumer<String> 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<String> 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<String> 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<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> 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();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> 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<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> 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();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, int[]>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, int[]> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
||||
if (entry.getValue().length == 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();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DrilldownPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Map<String, Integer>>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Map<String, Integer>> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean reallyAllSkipped = true;
|
||||
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
|
||||
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
|
||||
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
|
||||
allSkipped = false;
|
||||
}
|
||||
if (!allSkipped) {
|
||||
reallyAllSkipped = false;
|
||||
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
|
||||
}
|
||||
}
|
||||
if (reallyAllSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
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<String, Throwable> 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 SingleLineChart extends CustomChart {
|
||||
|
||||
private final Callable<Integer> 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<Integer> 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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class RandomUtils {
|
||||
|
||||
public static boolean random(double percentage) {
|
||||
if (ThreadLocalRandom.current().nextDouble(0, 100) <= percentage) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static double randomInRange(double min, double max) {
|
||||
return (ThreadLocalRandom.current().nextDouble() * (max - min)) + min;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Object getValue(Object instance, String name) {
|
||||
Object result = null;
|
||||
|
||||
try {
|
||||
Field field = instance.getClass().getDeclaredField(name);
|
||||
|
||||
field.setAccessible(true);
|
||||
result = field.get(instance);
|
||||
field.setAccessible(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object getStaticValue(Class clazz, String name) {
|
||||
Object result = null;
|
||||
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(name);
|
||||
|
||||
field.setAccessible(true);
|
||||
result = field.get(clazz);
|
||||
field.setAccessible(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void setValue(Object instance, String name, Object value) {
|
||||
try {
|
||||
Field field = instance.getClass().getDeclaredField(name);
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(instance, value);
|
||||
field.setAccessible(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
/*
|
||||
From: https://gist.github.com/Jofkos/d0c469528b032d820f42
|
||||
*/
|
||||
|
||||
public class UUIDFetcher {
|
||||
|
||||
/**
|
||||
* Date when name changes were introduced
|
||||
*
|
||||
* @see UUIDFetcher#getUUIDAt(String, long)
|
||||
*/
|
||||
public static final long FEBRUARY_2015 = 1422748800000L;
|
||||
private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d";
|
||||
private static final String NAME_URL = "https://api.mojang.com/user/profile/%s";
|
||||
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
|
||||
private static Map<String, UUID> uuidCache = new HashMap<String, UUID>();
|
||||
private static Map<UUID, String> nameCache = new HashMap<UUID, String>();
|
||||
|
||||
private static ExecutorService pool = Executors.newCachedThreadPool();
|
||||
|
||||
private String name;
|
||||
private UUID id;
|
||||
|
||||
/**
|
||||
* Fetches the uuid asynchronously and passes it to the consumer
|
||||
*
|
||||
* @param name The name
|
||||
* @param action Do what you want to do with the uuid her
|
||||
*/
|
||||
public static void getUUID(String name, Consumer<UUID> action) {
|
||||
pool.execute(() -> action.accept(getUUID(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the uuid synchronously and returns it
|
||||
*
|
||||
* @param name The name
|
||||
* @return The uuid
|
||||
*/
|
||||
public static UUID getUUID(String name) {
|
||||
return getUUIDAt(name, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the uuid synchronously for a specified name and time and passes the result to the consumer
|
||||
*
|
||||
* @param name The name
|
||||
* @param timestamp Time when the player had this name in milliseconds
|
||||
* @param action Do what you want to do with the uuid her
|
||||
*/
|
||||
public static void getUUIDAt(String name, long timestamp, Consumer<UUID> action) {
|
||||
pool.execute(() -> action.accept(getUUIDAt(name, timestamp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the uuid synchronously for a specified name and time
|
||||
*
|
||||
* @param name The name
|
||||
* @param timestamp Time when the player had this name in milliseconds
|
||||
* @see UUIDFetcher#FEBRUARY_2015
|
||||
*/
|
||||
public static UUID getUUIDAt(String name, long timestamp) {
|
||||
name = name.toLowerCase();
|
||||
if (uuidCache.containsKey(name)) {
|
||||
return uuidCache.get(name);
|
||||
}
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name, timestamp / 1000)).openConnection();
|
||||
connection.setReadTimeout(5000);
|
||||
UUIDFetcher data = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
|
||||
|
||||
uuidCache.put(name, data.id);
|
||||
nameCache.put(data.id, data.name);
|
||||
|
||||
return data.id;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the name asynchronously and passes it to the consumer
|
||||
*
|
||||
* @param uuid The uuid
|
||||
* @param action Do what you want to do with the name her
|
||||
*/
|
||||
public static void getName(UUID uuid, Consumer<String> action) {
|
||||
pool.execute(() -> action.accept(getName(uuid)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the name synchronously and returns it
|
||||
*
|
||||
* @param uuid The uuid
|
||||
* @return The name
|
||||
*/
|
||||
public static String getName(UUID uuid) {
|
||||
if (nameCache.containsKey(uuid)) {
|
||||
return nameCache.get(uuid);
|
||||
}
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
|
||||
connection.setReadTimeout(5000);
|
||||
UUIDFetcher currentNameData = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
|
||||
|
||||
uuidCache.put(currentNameData.name.toLowerCase(), uuid);
|
||||
nameCache.put(uuid, currentNameData.name);
|
||||
|
||||
return currentNameData.name;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UUIDTypeAdapter extends TypeAdapter<UUID> {
|
||||
public static String fromUUID(UUID value) {
|
||||
return value.toString().replace("-", "");
|
||||
}
|
||||
|
||||
public static UUID fromString(String input) {
|
||||
return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
|
||||
}
|
||||
|
||||
public void write(JsonWriter out, UUID value) throws IOException {
|
||||
out.value(fromUUID(value));
|
||||
}
|
||||
|
||||
public UUID read(JsonReader in) throws IOException {
|
||||
return fromString(in.nextString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package de.oliver.fancylib;
|
||||
|
||||
import de.oliver.fancylib.versionFetcher.VersionFetcher;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class VersionConfig {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final VersionFetcher fetcher;
|
||||
private String version;
|
||||
private String build;
|
||||
private String hash;
|
||||
|
||||
public VersionConfig(Plugin plugin, VersionFetcher fetcher) {
|
||||
this.plugin = plugin;
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
try {
|
||||
config.loadFromString(new FileUtils().readResource("version.yml"));
|
||||
} catch (InvalidConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
version = config.getString("version");
|
||||
build = config.getString("build");
|
||||
hash = config.getString("hash");
|
||||
}
|
||||
|
||||
public void checkVersionAndDisplay(CommandSender sender, boolean displayOnlyIfOutdated) {
|
||||
String newestVersion = usingLatestVersion();
|
||||
|
||||
if (newestVersion == null) {
|
||||
MessageHelper.error(sender, "Could not fetch latest version");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newestVersion.isEmpty()) {
|
||||
MessageHelper.warning(sender, outdatedVersion(newestVersion, fetcher.getDownloadUrl()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!displayOnlyIfOutdated) {
|
||||
MessageHelper.success(sender, latestVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if could not fetch, empty string if it's the newest, the newest version if it's not the current
|
||||
*/
|
||||
private String usingLatestVersion() {
|
||||
ComparableVersion newestVersion = fetcher.fetchNewestVersion();
|
||||
ComparableVersion currentVersion = new ComparableVersion(version);
|
||||
if (newestVersion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (newestVersion.compareTo(currentVersion) <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return newestVersion.toString();
|
||||
}
|
||||
|
||||
private String latestVersion() {
|
||||
String result = "This server is using the latest version of {plugin}!\n" +
|
||||
"Version: {version} (Git: {hash})" +
|
||||
"{build}";
|
||||
|
||||
result = result.replace("{plugin}", plugin.getName())
|
||||
.replace("{version}", version)
|
||||
.replace("{hash}", hash.substring(0, 7))
|
||||
.replace("{build}", build.equalsIgnoreCase("undefined") ? "" : "\nBuild: " + build);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String outdatedVersion(String latestVersion, String downloadUrl) {
|
||||
String result = "This server is using an outdated version of {plugin}\n" +
|
||||
"Current version: {current_ver}\n" +
|
||||
"Latest version: {latest_ver}\n" +
|
||||
"Download latest version: <click:open_url:'{download_url}'><u>click here</u></click>";
|
||||
|
||||
result = result.replace("{plugin}", plugin.getName())
|
||||
.replace("{current_ver}", version)
|
||||
.replace("{latest_ver}", latestVersion)
|
||||
.replace("{download_url}", downloadUrl);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.oliver.fancylib.databases;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public interface Database {
|
||||
|
||||
/**
|
||||
* Connects to the database
|
||||
*
|
||||
* @return true if success, false if failed
|
||||
*/
|
||||
boolean connect();
|
||||
|
||||
/**
|
||||
* Closes the database connection
|
||||
*
|
||||
* @return true if success, false if failed
|
||||
*/
|
||||
boolean close();
|
||||
|
||||
/**
|
||||
* @return true if connected, false if not
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* @return the connection object, null if not connected
|
||||
*/
|
||||
Connection getConnection();
|
||||
|
||||
/**
|
||||
* Executes a statement on the database
|
||||
*
|
||||
* @return true if success, false if failed
|
||||
*/
|
||||
boolean executeNonQuery(String sql);
|
||||
|
||||
/**
|
||||
* Executes a query on the database
|
||||
*
|
||||
* @return the result or null if failed
|
||||
*/
|
||||
ResultSet executeQuery(String query);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.oliver.fancylib.databases;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public class MySqlDatabase implements Database {
|
||||
|
||||
protected final String host;
|
||||
protected final String port;
|
||||
protected final String database;
|
||||
protected final String username;
|
||||
protected final String password;
|
||||
protected Connection connection;
|
||||
|
||||
public MySqlDatabase(String host, String port, String database, String username, String password) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.database = database;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Class.forName("com.mysql/.jdbc.Driver");
|
||||
connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, username, password);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean close() {
|
||||
try {
|
||||
if (!isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
connection.close();
|
||||
connection = null;
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return connection != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeNonQuery(String sql) {
|
||||
try {
|
||||
connection.createStatement().execute(sql);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet executeQuery(String query) {
|
||||
try {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery(query);
|
||||
return resultSet;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.oliver.fancylib.databases;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
public class SqliteDatabase extends MySqlDatabase {
|
||||
|
||||
protected final String path;
|
||||
protected File file;
|
||||
|
||||
public SqliteDatabase(String path) {
|
||||
super(null, null, null, null, null);
|
||||
this.path = path;
|
||||
this.file = new File(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file.exists()) {
|
||||
if (!file.createNewFile()) {
|
||||
Bukkit.getLogger().warning("Could not create database file.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + file.getPath());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.oliver.fancylib.featureFlags;
|
||||
|
||||
public class FeatureFlag {
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private boolean enabled;
|
||||
private final boolean forceDisabled;
|
||||
|
||||
public FeatureFlag(String name, String description, boolean forceDisabled) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.forceDisabled = forceDisabled;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
if(forceDisabled) return false;
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isForceDisabled() {
|
||||
return forceDisabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.oliver.fancylib.featureFlags;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FeatureFlagConfig {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final File configFile;
|
||||
private final List<FeatureFlag> featureFlags;
|
||||
|
||||
public FeatureFlagConfig(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.configFile = new File("plugins" + File.separator + plugin.getName() + File.separator + "featureFlags.yml");
|
||||
this.featureFlags = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
if (!configFile.exists()) {
|
||||
try {
|
||||
new File(configFile.getParent()).mkdirs();
|
||||
configFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||
|
||||
for (FeatureFlag featureFlag : featureFlags) {
|
||||
config.setInlineComments("feature-flags." + featureFlag.getName(), List.of(featureFlag.getDescription()));
|
||||
|
||||
if (config.isSet("feature-flags." + featureFlag.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
config.set("feature-flags." + featureFlag.getName(), false);
|
||||
}
|
||||
|
||||
for (String flagName : config.getConfigurationSection("feature-flags").getKeys(false)) {
|
||||
boolean enabled = config.getBoolean("feature-flags." + flagName, false);
|
||||
FeatureFlag flag = getFeatureFlag(flagName);
|
||||
if (flag == null) {
|
||||
flag = new FeatureFlag(flagName, "", false);
|
||||
featureFlags.add(flag);
|
||||
}
|
||||
|
||||
flag.setEnabled(enabled);
|
||||
}
|
||||
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public FeatureFlag getFeatureFlag(String name) {
|
||||
for (FeatureFlag featureFlag : featureFlags) {
|
||||
if (featureFlag.getName().equalsIgnoreCase(name.toLowerCase())) {
|
||||
return featureFlag;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addFeatureFlag(FeatureFlag featureFlag) {
|
||||
if (!featureFlags.contains(featureFlag)) {
|
||||
featureFlags.add(featureFlag);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.oliver.fancylib.gui.customInventories;
|
||||
|
||||
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class CustomPlayerInventory implements InventoryHolder {
|
||||
|
||||
protected final Player player;
|
||||
protected Inventory inventory;
|
||||
|
||||
protected CustomPlayerInventory(Player player, int amountRows, Component title) {
|
||||
this.player = player;
|
||||
this.inventory = Bukkit.createInventory(this, amountRows * 9, title);
|
||||
}
|
||||
|
||||
public static ItemStack getPlaceholder() {
|
||||
ItemStack item = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
item.editMeta(itemMeta -> {
|
||||
itemMeta.displayName(Component.empty());
|
||||
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "cancelClick");
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
player.openInventory(inventory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.oliver.fancylib.gui.customInventories;
|
||||
|
||||
import de.oliver.fancylib.FancyLib;
|
||||
import de.oliver.fancylib.MessageHelper;
|
||||
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public interface PageInventory {
|
||||
|
||||
NamespacedKey PAGE_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "page");
|
||||
|
||||
static ItemStack previousPage(int currentPage) {
|
||||
ItemStack previousPage = new ItemStack(Material.ARROW);
|
||||
previousPage.editMeta(itemMeta -> {
|
||||
itemMeta.displayName(MessageHelper.removeDecoration(MiniMessage.miniMessage().deserialize("<gradient:gold:yellow:gold>Previous page</gradient>"), TextDecoration.ITALIC));
|
||||
|
||||
itemMeta.getPersistentDataContainer().set(PageInventory.PAGE_KEY, PersistentDataType.INTEGER, currentPage - 1);
|
||||
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "changePage");
|
||||
});
|
||||
|
||||
return previousPage;
|
||||
}
|
||||
|
||||
static ItemStack nextPage(int currentPage) {
|
||||
ItemStack nextPage = new ItemStack(Material.ARROW);
|
||||
nextPage.editMeta(itemMeta -> {
|
||||
itemMeta.displayName(MessageHelper.removeDecoration(MiniMessage.miniMessage().deserialize("<gradient:gold:yellow:gold>Next page</gradient>"), TextDecoration.ITALIC));
|
||||
|
||||
itemMeta.getPersistentDataContainer().set(PageInventory.PAGE_KEY, PersistentDataType.INTEGER, currentPage + 1);
|
||||
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "changePage");
|
||||
});
|
||||
|
||||
return nextPage;
|
||||
}
|
||||
|
||||
void loadPage(int page);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.oliver.fancylib.gui.inventoryClick;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public class InventoryClickListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
|
||||
try {
|
||||
ItemStack item = event.getCurrentItem();
|
||||
|
||||
if (item.getItemMeta().getPersistentDataContainer().has(InventoryItemClick.ON_CLICK_KEY)) {
|
||||
String id = item.getItemMeta().getPersistentDataContainer().get(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING);
|
||||
InventoryClickRegistry.getInventoryItemClick(id).onClick(event, (Player) event.getWhoClicked());
|
||||
}
|
||||
|
||||
} catch (NullPointerException ignore) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.oliver.fancylib.gui.inventoryClick;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InventoryClickRegistry {
|
||||
|
||||
private static final Map<String, InventoryItemClick> inventoryItemClickMap = new HashMap<>();
|
||||
|
||||
public static InventoryItemClick getInventoryItemClick(String id) {
|
||||
return inventoryItemClickMap.getOrDefault(id, InventoryItemClick.EMPTY);
|
||||
}
|
||||
|
||||
public static void registerInventoryItemClick(InventoryItemClick inventoryItemClick) {
|
||||
inventoryItemClickMap.put(inventoryItemClick.getId(), inventoryItemClick);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.oliver.fancylib.gui.inventoryClick;
|
||||
|
||||
import de.oliver.fancylib.FancyLib;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface InventoryItemClick {
|
||||
|
||||
NamespacedKey ON_CLICK_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "onclick");
|
||||
|
||||
InventoryItemClick EMPTY = new InventoryItemClick() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(InventoryClickEvent event, Player player) {
|
||||
}
|
||||
};
|
||||
|
||||
static boolean hasKeys(ItemStack item, List<NamespacedKey> keys) {
|
||||
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
|
||||
for (NamespacedKey key : keys) {
|
||||
if (!data.has(key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
void onClick(InventoryClickEvent event, Player player);
|
||||
|
||||
default void register() {
|
||||
InventoryClickRegistry.registerInventoryItemClick(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.oliver.fancylib.gui.inventoryClick.impl;
|
||||
|
||||
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
|
||||
public class CancelInventoryItemClick implements InventoryItemClick {
|
||||
|
||||
public static final CancelInventoryItemClick INSTANCE = new CancelInventoryItemClick();
|
||||
|
||||
private CancelInventoryItemClick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "cancelClick";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(InventoryClickEvent event, Player player) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.oliver.fancylib.gui.inventoryClick.impl;
|
||||
|
||||
import de.oliver.fancylib.gui.customInventories.PageInventory;
|
||||
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ChangePageInventoryItemClick implements InventoryItemClick {
|
||||
|
||||
public static final ChangePageInventoryItemClick INSTANCE = new ChangePageInventoryItemClick();
|
||||
|
||||
private final static List<NamespacedKey> REQUIRED_KEYS = Collections.singletonList(
|
||||
PageInventory.PAGE_KEY
|
||||
);
|
||||
|
||||
private ChangePageInventoryItemClick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "changePage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(InventoryClickEvent event, Player player) {
|
||||
ItemStack item = event.getCurrentItem();
|
||||
|
||||
if (item != null && InventoryItemClick.hasKeys(item, REQUIRED_KEYS)) {
|
||||
event.setCancelled(true);
|
||||
|
||||
int page = item.getItemMeta().getPersistentDataContainer().get(PageInventory.PAGE_KEY, PersistentDataType.INTEGER);
|
||||
|
||||
if (event.getInventory().getHolder() == null || !(event.getInventory().getHolder() instanceof PageInventory pageInventory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pageInventory.loadPage(page);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.oliver.fancylib.itemClick;
|
||||
|
||||
import de.oliver.fancylib.FancyLib;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
public interface ItemClick {
|
||||
NamespacedKey ON_CLICK_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "oninteract");
|
||||
|
||||
ItemClick EMPTY = new ItemClick() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(PlayerInteractEvent event, Player player) {
|
||||
}
|
||||
};
|
||||
|
||||
String getId();
|
||||
|
||||
void onClick(PlayerInteractEvent event, Player player);
|
||||
|
||||
default void register() {
|
||||
ItemClickRegistry.registerItemClick(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.oliver.fancylib.itemClick;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ItemClickRegistry {
|
||||
|
||||
private static final Map<String, ItemClick> itemClickMap = new HashMap<>();
|
||||
|
||||
public static ItemClick getItemClick(String id) {
|
||||
return itemClickMap.getOrDefault(id, ItemClick.EMPTY);
|
||||
}
|
||||
|
||||
public static void registerItemClick(ItemClick itemClick) {
|
||||
itemClickMap.put(itemClick.getId(), itemClick);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.oliver.fancylib.itemClick;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public class PlayerInteractListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
try {
|
||||
ItemStack item = event.getItem();
|
||||
if (event.getHand() == EquipmentSlot.HAND && item.getItemMeta().getPersistentDataContainer().has(ItemClick.ON_CLICK_KEY)) {
|
||||
String id = item.getItemMeta().getPersistentDataContainer().get(ItemClick.ON_CLICK_KEY, PersistentDataType.STRING);
|
||||
|
||||
ItemClickRegistry.getItemClick(id).onClick(event, event.getPlayer());
|
||||
}
|
||||
} catch (NullPointerException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
137
libraries/common/src/main/java/de/oliver/fancylib/jdb/JDB.java
Normal file
137
libraries/common/src/main/java/de/oliver/fancylib/jdb/JDB.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package de.oliver.fancylib.jdb;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The JDB class provides a simple JSON document-based storage system in a specified directory.
|
||||
*/
|
||||
public class JDB {
|
||||
private final static Gson GSON = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
private static final String FILE_EXTENSION = ".json";
|
||||
private final @NotNull String basePath;
|
||||
private final @NotNull File baseDirectory;
|
||||
|
||||
/**
|
||||
* Constructs a new JDB instance with the specified base path.
|
||||
*
|
||||
* @param basePath the base directory path where documents will be stored
|
||||
*/
|
||||
public JDB(@NotNull String basePath) {
|
||||
this.basePath = basePath;
|
||||
this.baseDirectory = new File(basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a document from the specified path, deserializing it into the given class type.
|
||||
*
|
||||
* @param <T> the type of the object to be returned
|
||||
* @param path the relative path (excluding .json extension) where the document is located
|
||||
* @param clazz the class type to which the document should be deserialized
|
||||
* @return a JDocument containing the deserialized object and its path, or null if the file does not exist
|
||||
* @throws IOException if an I/O error occurs during file reading
|
||||
*/
|
||||
public <T> T get(@NotNull String path, @NotNull Class<T> clazz) throws IOException {
|
||||
File documentFile = new File(baseDirectory, createFilePath(path));
|
||||
if (!documentFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
BufferedReader bufferedReader = Files.newBufferedReader(documentFile.toPath());
|
||||
return GSON.fromJson(bufferedReader, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a document from the specified path.
|
||||
*
|
||||
* @param path the relative path (excluding .json extension) where the document is located
|
||||
* @return a JDocument containing the deserialized data or null if the file does not exist
|
||||
* @throws IOException if an I/O error occurs during file reading
|
||||
*/
|
||||
public JDocument getDocument(@NotNull String path) throws IOException {
|
||||
File documentFile = new File(baseDirectory, createFilePath(path));
|
||||
if (!documentFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
BufferedReader bufferedReader = Files.newBufferedReader(documentFile.toPath());
|
||||
Map<String, Object> data = (Map<String, Object>) GSON.fromJson(bufferedReader, Map.class);
|
||||
return new JDocument(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all documents from the specified directory path, deserializing them into the given class type.
|
||||
*
|
||||
* @param <T> the type of objects to be returned
|
||||
* @param path the relative directory path containing the documents
|
||||
* @param clazz the class type to which the documents should be deserialized
|
||||
* @return a List of JDocument objects containing the deserialized objects and their paths, or null if the directory or files do not exist
|
||||
* @throws IOException if an I/O error occurs during file reading
|
||||
*/
|
||||
public <T> List<T> getAll(@NotNull String path, @NotNull Class<T> clazz) throws IOException {
|
||||
File directory = new File(baseDirectory, path);
|
||||
if (!directory.exists()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<T> documents = new ArrayList<>(files.length);
|
||||
for (File file : files) {
|
||||
documents.add(get(path + "/" + file.getName().replace(FILE_EXTENSION, ""), clazz));
|
||||
}
|
||||
return documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given value as a document at the specified path.
|
||||
*
|
||||
* @param <T> the type of the object to be saved
|
||||
* @param path the relative path (excluding .json extension) where the document will be saved
|
||||
* @param value the object to be saved as a JSON document
|
||||
* @throws IOException if an I/O error occurs during file writing
|
||||
*/
|
||||
public <T> void set(@NotNull String path, @NotNull T value) throws IOException {
|
||||
File documentFile = new File(baseDirectory, createFilePath(path));
|
||||
if (!documentFile.exists()) {
|
||||
documentFile.getParentFile().mkdirs();
|
||||
documentFile.createNewFile();
|
||||
}
|
||||
String json = GSON.toJson(value);
|
||||
Files.write(documentFile.toPath(), json.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the document(s) at the specified path.
|
||||
*
|
||||
* @param path the relative path (excluding .json extension) where the document(s) are located
|
||||
*/
|
||||
public void delete(@NotNull String path) {
|
||||
File documentFile = new File(baseDirectory, createFilePath(path));
|
||||
if (documentFile.exists()) {
|
||||
documentFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the file path by appending the base path, provided path, and the file extension.
|
||||
*
|
||||
* @param path the relative path (excluding .json extension)
|
||||
* @return the full file path
|
||||
*/
|
||||
private String createFilePath(@NotNull String path) {
|
||||
return path + FILE_EXTENSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package de.oliver.fancylib.jdb;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a document that holds a map of key-value pairs with support for nested keys.
|
||||
*/
|
||||
public class JDocument {
|
||||
private final @NotNull Map<String, Object> data;
|
||||
|
||||
public JDocument(@NotNull Map<String, Object> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value from the document using the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the value associated with the given key, or null if the key is not found
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return getValue(key, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the document contains a value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return true if the given key exists in the document, otherwise false
|
||||
*/
|
||||
public boolean contains(String key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the set of keys from a map value associated with a given key in the document.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the map value in the document (e.g. "foo.bar.baz")
|
||||
* @return a set of keys from the map associated with the given key, or an empty set if the key
|
||||
* is not found or the value is not a map
|
||||
*/
|
||||
public Set<String> getKeys(String key) {
|
||||
Map<String, Object> map = (Map<String, Object>) getValue(key, Map.class);
|
||||
return map != null ? map.keySet() : new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a string value from the document using the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the string value associated with the given key, or an empty string if the key is not found or the value is not a string
|
||||
*/
|
||||
public String getString(String key) {
|
||||
return (String) getValueOrDefault(key, String.class, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a boolean value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the boolean value associated with the given key, or false if the key is not found or the value is not a boolean
|
||||
*/
|
||||
public boolean getBoolean(String key) {
|
||||
return (boolean) getValueOrDefault(key, Boolean.class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a byte value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the byte value associated with the given key, or 0 if the key is not found or the value is not a byte
|
||||
*/
|
||||
public byte getByte(String key) {
|
||||
return (byte) getValueOrDefault(key, Byte.class, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a short value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the short value associated with the given key, or 0 if the key is not found or the value is not a short
|
||||
*/
|
||||
public short getShort(String key) {
|
||||
return (short) getValueOrDefault(key, Short.class, (short) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an integer value associated with the given key from the document.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the integer value associated with the given key, or 0 if the key is not found or the value is not an integer
|
||||
*/
|
||||
public int getInt(String key) {
|
||||
return (int) getValueOrDefault(key, Integer.class, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a long value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the long value associated with the given key, or 0 if the key is not found or the value is not a long
|
||||
*/
|
||||
public long getLong(String key) {
|
||||
return (long) getValueOrDefault(key, Long.class, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a float value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the float value associated with the given key, or 0 if the key is not found or the value is not a float
|
||||
*/
|
||||
public float getFloat(String key) {
|
||||
return (float) getValueOrDefault(key, Float.class, 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a double value associated with the given key.
|
||||
*
|
||||
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
|
||||
* @return the double value associated with the given key, or 0 if the key is not found or the value is not a double
|
||||
*/
|
||||
public double getDouble(String key) {
|
||||
return (double) getValueOrDefault(key, Double.class, 0d);
|
||||
}
|
||||
|
||||
private Object getValue(String key, Class<?> clazz) {
|
||||
String[] parts = key.split("\\.");
|
||||
Map<String, Object> current = data;
|
||||
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
Object value = current.get(parts[i]);
|
||||
if (value == null || (i < parts.length - 1 && !(value instanceof Map))) {
|
||||
return null;
|
||||
}
|
||||
if (i == parts.length - 1) {
|
||||
return clazz.isInstance(value) ? value : null;
|
||||
}
|
||||
current = (Map<String, Object>) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private <T> T getValueOrDefault(String key, Class<T> clazz, T defaultValue) {
|
||||
T value = (T) getValue(key, clazz);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.oliver.fancylib.serverSoftware;
|
||||
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.BukkitScheduler;
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.FancyScheduler;
|
||||
import de.oliver.fancylib.serverSoftware.schedulers.FoliaScheduler;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ServerSoftware {
|
||||
|
||||
public static boolean isFolia() {
|
||||
return Arrays.stream(PluginMeta.class.getDeclaredMethods())
|
||||
.map(Method::getName)
|
||||
.anyMatch(s -> s.equals("isFoliaSupported"));
|
||||
}
|
||||
|
||||
public static boolean isPaper() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isBukkit() {
|
||||
try {
|
||||
Class.forName("org.bukkit.Bukkit");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static FancyScheduler getCorrectScheduler(JavaPlugin plugin) {
|
||||
if (isFolia()) {
|
||||
return new FoliaScheduler(plugin);
|
||||
}
|
||||
|
||||
return new BukkitScheduler(plugin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.oliver.fancylib.serverSoftware.schedulers;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitScheduler implements FancyScheduler {
|
||||
|
||||
BukkitTask bukkitTask;
|
||||
JavaPlugin plugin;
|
||||
|
||||
public BukkitScheduler(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTask(Location location, Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTask(plugin, task);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskAsynchronously(Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTaskLater(plugin, task, delay);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delay);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTaskTimer(plugin, task, delay, period);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task) {
|
||||
bukkitTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay, period);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (!bukkitTask.isCancelled()) {
|
||||
bukkitTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.oliver.fancylib.serverSoftware.schedulers;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface FancyScheduler {
|
||||
|
||||
/**
|
||||
* Run the task.
|
||||
*
|
||||
* @param task task...
|
||||
* @param location required for Folia, in Bukkit can be null
|
||||
* @return The created {@link FancyScheduler}.
|
||||
*/
|
||||
@NotNull FancyScheduler runTask(Location location, Runnable task);
|
||||
|
||||
/**
|
||||
* Run the task asynchronously.
|
||||
*
|
||||
* @param task task...
|
||||
* @return The created {@link FancyScheduler}
|
||||
*/
|
||||
@NotNull FancyScheduler runTaskAsynchronously(Runnable task);
|
||||
|
||||
/**
|
||||
* Run the task after a specified number of ticks.
|
||||
*
|
||||
* @param location required for Folia, in Bukkit can be null
|
||||
* @param task task...
|
||||
* @param delay The number of ticks to wait.
|
||||
* @return The created {@link FancyScheduler}
|
||||
*/
|
||||
@NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task);
|
||||
|
||||
/**
|
||||
* Run the task asynchronously after a specified number of ticks.
|
||||
*
|
||||
* @param task task...
|
||||
* @param delay The number of ticks to wait.
|
||||
* @return The created {@link FancyScheduler}
|
||||
*/
|
||||
@NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task);
|
||||
|
||||
/**
|
||||
* Run the task repeatedly on a timer.
|
||||
*
|
||||
* @param location required for Folia, in Bukkit can be null
|
||||
* @param task task...
|
||||
* @param delay The delay before the task is first run (in ticks).
|
||||
* @param period The ticks elapsed before the task is run again.
|
||||
* @return The created {@link FancyScheduler}
|
||||
*/
|
||||
@NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task);
|
||||
|
||||
/**
|
||||
* Run the task repeatedly on a timer asynchronously.
|
||||
*
|
||||
* @param task task...
|
||||
* @param delay The delay before the task is first run (in ticks).
|
||||
* @param period The ticks elapsed before the task is run again.
|
||||
* @return The created {@link FancyScheduler}
|
||||
*/
|
||||
@NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task);
|
||||
|
||||
/**
|
||||
* Cancel the task.
|
||||
*/
|
||||
void cancel();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.oliver.fancylib.serverSoftware.schedulers;
|
||||
|
||||
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FoliaScheduler implements FancyScheduler {
|
||||
private static final long NANOSECONDS_PER_TICK = 50000000L;
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private ScheduledTask scheduledTask;
|
||||
|
||||
public FoliaScheduler(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTask(Location location, Runnable task) {
|
||||
if (location != null) {
|
||||
scheduledTask = plugin.getServer().getRegionScheduler().run(plugin, location, scheduledTask1 -> task.run());
|
||||
} else {
|
||||
scheduledTask = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask1 -> task.run());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskAsynchronously(Runnable task) {
|
||||
scheduledTask = plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask1 -> task.run());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task) {
|
||||
if (location != null) {
|
||||
scheduledTask = plugin.getServer().getRegionScheduler().runDelayed(plugin, location, scheduledTask1 -> task.run(), delay);
|
||||
} else {
|
||||
scheduledTask = plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask1 -> task.run(), delay);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task) {
|
||||
scheduledTask = plugin.getServer().getAsyncScheduler().runDelayed(plugin, scheduledTask1 -> task.run(), delay * NANOSECONDS_PER_TICK, TimeUnit.NANOSECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task) {
|
||||
scheduledTask = plugin.getServer().getRegionScheduler().runAtFixedRate(plugin, location, scheduledTask1 -> task.run(), delay, period);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task) {
|
||||
scheduledTask = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, scheduledTask1 -> task.run(), delay * NANOSECONDS_PER_TICK, period * NANOSECONDS_PER_TICK, TimeUnit.NANOSECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (!scheduledTask.isCancelled()) {
|
||||
scheduledTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package de.oliver.fancylib.tests;
|
||||
|
||||
/**
|
||||
* A generic class for making assertions on the expected values.
|
||||
*
|
||||
* @param <T> the type of the value to be asserted.
|
||||
*/
|
||||
public class Expectable<T> {
|
||||
|
||||
/**
|
||||
* The value that is being wrapped by this Expectable instance.
|
||||
* This is the object against which all expectations will be verified.
|
||||
*/
|
||||
private final T t;
|
||||
|
||||
private Expectable(T t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Expectable for the given value.
|
||||
*
|
||||
* @param <T> the type of the value being tested
|
||||
* @param t the actual value to create an expectation for
|
||||
* @return a new Expectable instance for the given value
|
||||
*/
|
||||
public static <T> Expectable<T> expect(T t) {
|
||||
return new Expectable<>(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the actual value is not null.
|
||||
* <p>
|
||||
* Throws an AssertionError if the value of the field 't' is null,
|
||||
* indicating that the actual value is expected to be non-null.
|
||||
*
|
||||
* @throws AssertionError if the value of the field 't' is null
|
||||
*/
|
||||
public void toBeDefined() {
|
||||
if (t == null) {
|
||||
throw new AssertionError("Expected not null but got null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value of the field 't' is null.
|
||||
* <p>
|
||||
* Throws an AssertionError if the value of 't' is not null,
|
||||
* indicating the expectation that the value should be null.
|
||||
*
|
||||
* @throws AssertionError if the value of 't' is not null
|
||||
*/
|
||||
public void toBeNull() {
|
||||
if (t != null) {
|
||||
throw new AssertionError("Expected null but got not null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value is equal to the expected value.
|
||||
*
|
||||
* @param expected the value that the actual value is expected to be equal to
|
||||
* @throws AssertionError if the actual value is not equal to the expected value
|
||||
*/
|
||||
public void toBe(T expected) {
|
||||
if (t != expected) {
|
||||
throw new AssertionError("Expected " + expected + " but got " + t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value is equal to the expected value using the {@code equals} method.
|
||||
*
|
||||
* @param expected the value that the actual value is expected to be equal to
|
||||
* @throws AssertionError if the actual value is not equal to the expected value
|
||||
*/
|
||||
public void toEqual(T expected) {
|
||||
if (!t.equals(expected)) {
|
||||
throw new AssertionError("Expected " + expected + " but got " + t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value is greater than the expected value.
|
||||
*
|
||||
* @param expected the value that the actual value is expected to be greater than
|
||||
* @throws AssertionError if the actual value is not greater than the expected value,
|
||||
* or if the type of the actual value is not one of Integer, Long, Float, or Double
|
||||
*/
|
||||
public void toBeGreaterThan(T expected) {
|
||||
if (t instanceof Integer) {
|
||||
if ((Integer) t <= (Integer) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be greater than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Long) {
|
||||
if ((Long) t <= (Long) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be greater than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Float) {
|
||||
if ((Float) t <= (Float) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be greater than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Double) {
|
||||
if ((Double) t <= (Double) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be greater than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("toBeGreaterThan can only be used on Integers, Longs, Floats, and Doubles");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value is less than the expected value.
|
||||
*
|
||||
* @param expected the value that the actual value is expected to be less than
|
||||
* @throws AssertionError if the actual value is not less than the expected value,
|
||||
* or if the type of the actual value is not one of Integer, Long, Float, or Double
|
||||
*/
|
||||
public void toBeLessThan(T expected) {
|
||||
if (t instanceof Integer) {
|
||||
if ((Integer) t >= (Integer) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be less than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Long) {
|
||||
if ((Long) t >= (Long) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be less than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Float) {
|
||||
if ((Float) t >= (Float) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be less than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Double) {
|
||||
if ((Double) t >= (Double) expected) {
|
||||
throw new AssertionError("Expected " + t + " to be less than " + expected);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("toBeLessThan can only be used on Integers, Longs, Floats, and Doubles");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value is an instance of the expected class.
|
||||
* This method checks whether the value held in the field 't' is an instance of the provided Class.
|
||||
*
|
||||
* @param expected the Class object that the actual value is expected to be an instance of
|
||||
* @throws AssertionError if the actual value is not an instance of the expected class
|
||||
*/
|
||||
public void toBeInstanceOf(Class<?> expected) {
|
||||
if (!expected.isInstance(t)) {
|
||||
throw new AssertionError("Expected " + t + " to be an instance of " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expected value is contained within the actual value.
|
||||
* <p>
|
||||
* This method checks if the expected value is present in a String, Iterable, or Array.
|
||||
* If the actual value is a String, it uses the contains method to check if the expected value
|
||||
* is a substring. If the actual value is an Iterable, it checks if the expected value is an element.
|
||||
* If the actual value is an Array, it checks if the expected value is present in the array.
|
||||
*
|
||||
* @param expected the value that is expected to be contained within the actual value
|
||||
* @throws AssertionError if the expected value is not contained within the actual value
|
||||
*/
|
||||
public void toContain(Object expected) {
|
||||
if (t instanceof String) {
|
||||
if (!((String) t).contains((String) expected)) {
|
||||
throw new AssertionError("Expected " + expected + " to be contained in " + t);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Iterable) {
|
||||
if (!((Iterable<?>) t).spliterator().tryAdvance(o -> {
|
||||
if (o.equals(expected)) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected " + expected + " to be contained in " + t);
|
||||
})) {
|
||||
throw new AssertionError("Expected " + expected + " to be contained in " + t);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Object[]) {
|
||||
for (Object o : (Object[]) t) {
|
||||
if (o.equals(expected)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Expected " + expected + " to be contained in " + t);
|
||||
}
|
||||
|
||||
throw new AssertionError("toContain can only be used on Strings, Iterables and Arrays");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the actual value has the expected length.
|
||||
* This method checks if the actual value is a String, Iterable, or Array,
|
||||
* and compares their length or size to the given expected length.
|
||||
*
|
||||
* @param expected the expected length of the actual value
|
||||
* @throws AssertionError if the actual value does not have the expected length,
|
||||
* or if the actual value is not of type String, Iterable, or Array
|
||||
*/
|
||||
public void toHaveLength(int expected) {
|
||||
if (t instanceof String) {
|
||||
if (((String) t).length() != expected) {
|
||||
throw new AssertionError("Expected " + expected + " but got " + ((String) t).length());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Iterable) {
|
||||
if (((Iterable<?>) t).spliterator().getExactSizeIfKnown() != expected) {
|
||||
throw new AssertionError("Expected " + expected + " but got " + ((Iterable<?>) t).spliterator().getExactSizeIfKnown());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (t instanceof Object[]) {
|
||||
if (((Object[]) t).length != expected) {
|
||||
throw new AssertionError("Expected " + expected + " but got " + ((Object[]) t).length);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("toHaveLength can only be used on Strings");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package de.oliver.fancylib.tests;
|
||||
|
||||
import de.oliver.fancylib.tests.annotations.FPAfterEach;
|
||||
import de.oliver.fancylib.tests.annotations.FPBeforeEach;
|
||||
import de.oliver.fancylib.tests.annotations.FPTest;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* FPTestClass is a record that encapsulates information about a test class and its associated test methods.
|
||||
* This class supports running tests annotated with {@link FPTest}.
|
||||
*
|
||||
* @param testClass the test class to run tests for (must be annotated with {@link FPTest})
|
||||
* @param beforeEach the method annotated with {@link FPBeforeEach} to run before each test
|
||||
* @param afterEach the method annotated with {@link FPAfterEach} to run after each test
|
||||
* @param testMethods the list of test methods annotated with {@link FPTest}
|
||||
*/
|
||||
public record FPTestClass(
|
||||
Class<?> testClass,
|
||||
Method beforeEach,
|
||||
Method afterEach,
|
||||
List<Method> testMethods
|
||||
) {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FPTestClass.class.getName());
|
||||
|
||||
/**
|
||||
* Creates an instance of FPTestClass by inspecting the provided test class for methods annotated
|
||||
* with FPTest, FPBeforeEach, and FPAfterEach annotations.
|
||||
* These methods are used to define the setup, teardown, and test methods for the class.
|
||||
*
|
||||
* @param testClass the class to be inspected for annotated methods
|
||||
* @return an instance of FPTestClass containing the test class and its annotated methods
|
||||
*/
|
||||
public static FPTestClass fromClass(Class<?> testClass) {
|
||||
Method beforeEach = null;
|
||||
Method afterEach = null;
|
||||
List<Method> testMethods = new ArrayList<>();
|
||||
|
||||
for (Method method : testClass.getDeclaredMethods()) {
|
||||
if (method.isAnnotationPresent(FPTest.class)) {
|
||||
if (method.getParameterCount() != 1) continue;
|
||||
if (method.getParameterTypes()[0] != Player.class) continue;
|
||||
|
||||
testMethods.add(method);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(FPBeforeEach.class)) {
|
||||
if (method.getParameterCount() != 1) continue;
|
||||
if (method.getParameterTypes()[0] != Player.class) continue;
|
||||
|
||||
beforeEach = method;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(FPAfterEach.class)) {
|
||||
if (method.getParameterCount() != 1) continue;
|
||||
if (method.getParameterTypes()[0] != Player.class) continue;
|
||||
|
||||
afterEach = method;
|
||||
}
|
||||
}
|
||||
|
||||
return new FPTestClass(testClass, beforeEach, afterEach, testMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test methods belonging to the test class, performing any necessary setup and teardown operations.
|
||||
*
|
||||
* @param player The player context to pass to the test methods.
|
||||
* @return true if all tests completed successfully, false if any test failed or an unexpected exception occurred.
|
||||
*/
|
||||
public boolean runTests(Player player) {
|
||||
logger.info("Running tests for " + testClass.getSimpleName());
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Running tests for " + testClass.getSimpleName()));
|
||||
|
||||
for (Method testMethod : testMethods) {
|
||||
Object testClassObj;
|
||||
try {
|
||||
testClassObj = testClass.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to create test class instance: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
FPTest fpTest = testMethod.getAnnotation(FPTest.class);
|
||||
if (fpTest.skip()) {
|
||||
logger.info("Skipping test " + displayName(testMethod));
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<gold>Skipping test " + displayName(testMethod)));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
long testStart = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
if (beforeEach != null) beforeEach.invoke(testClassObj, player);
|
||||
|
||||
testMethod.invoke(testClassObj, player);
|
||||
|
||||
if (afterEach != null) afterEach.invoke(testClassObj, player);
|
||||
} catch (InvocationTargetException e) {
|
||||
logger.warning("Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage());
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<red>Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage()));
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.warning("Unexpected exception in test " + fpTest.name() + ": " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
long testEnd = System.currentTimeMillis();
|
||||
logger.info("Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms");
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms"));
|
||||
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
logger.warning("Thread interrupted while waiting between tests: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a display name for a given test method, incorporating annotation details if present.
|
||||
*
|
||||
* @param m the method for which to generate the display name
|
||||
* @return a display name that includes the test class and method name, and optionally the value of the FPTest annotation's name attribute if the annotation is present
|
||||
*/
|
||||
public String displayName(Method m) {
|
||||
if (!m.isAnnotationPresent(FPTest.class)) {
|
||||
return testClass.getSimpleName() + "#" + m.getName();
|
||||
}
|
||||
|
||||
FPTest fpTest = m.getAnnotation(FPTest.class);
|
||||
return testClass.getSimpleName() + "#" + m.getName() + " (" + fpTest.name() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.fancylib.tests.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a method that should be executed after each test case in a test class.
|
||||
* This annotation is used to identify methods that perform teardown operations, ensuring
|
||||
* that the test environment is cleaned up and reset after each individual test method is executed.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface FPAfterEach {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.fancylib.tests.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* FPBeforeEach is a custom annotation designed to be used on methods that should be executed before each test method.
|
||||
* Methods annotated with FPBeforeEach are typically used to perform setup operations needed before executing each test case.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface FPBeforeEach {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.oliver.fancylib.tests.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* FPTest is a custom annotation designed to be used on methods for marking them as test cases.
|
||||
* It helps to identify methods that should be treated as test cases in the testing framework.
|
||||
* The annotation's attributes allow for providing a human-readable test name and an optional flag to skip the test.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface FPTest {
|
||||
|
||||
/**
|
||||
* Specifies the name of the test case. This name is used to identify the test case
|
||||
* in reports, logs, and other contexts where the test case is referenced.
|
||||
*
|
||||
* @return the name of the test case
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Indicates whether the annotated test case should be skipped during test execution.
|
||||
*
|
||||
* @return true if the test case should be skipped, false otherwise
|
||||
*/
|
||||
boolean skip() default false;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.oliver.fancylib.translations;
|
||||
|
||||
import de.oliver.fancylib.translations.message.Message;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Language {
|
||||
|
||||
private final String languageCode;
|
||||
private final String languageName;
|
||||
private final Map<String, Message> messages;
|
||||
|
||||
public Language(String languageCode, String languageName) {
|
||||
this.languageCode = languageCode;
|
||||
this.languageName = languageName;
|
||||
this.messages = new HashMap<>();
|
||||
}
|
||||
|
||||
public void addMessage(String key, Message message) {
|
||||
messages.put(key, message);
|
||||
}
|
||||
|
||||
public Message getMessage(String key) {
|
||||
return messages.getOrDefault(key, null);
|
||||
}
|
||||
|
||||
public String getLanguageCode() {
|
||||
return languageCode;
|
||||
}
|
||||
|
||||
public String getLanguageName() {
|
||||
return languageName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.oliver.fancylib.translations;
|
||||
|
||||
public record TextConfig(
|
||||
String primaryColor,
|
||||
String secondaryColor,
|
||||
String successColor,
|
||||
String warningColor,
|
||||
String errorColor,
|
||||
String prefix
|
||||
) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package de.oliver.fancylib.translations;
|
||||
|
||||
import de.oliver.fancylib.translations.message.Message;
|
||||
import de.oliver.fancylib.translations.message.MultiMessage;
|
||||
import de.oliver.fancylib.translations.message.SimpleMessage;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Translator {
|
||||
|
||||
private final TextConfig textConfig;
|
||||
private final List<Language> languages;
|
||||
private Language selectedLanguage;
|
||||
private Language fallbackLanguage;
|
||||
|
||||
public Translator(TextConfig textConfig) {
|
||||
this.textConfig = textConfig;
|
||||
this.languages = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void loadLanguages(String pluginFolderPath) {
|
||||
languages.clear();
|
||||
selectedLanguage = null;
|
||||
fallbackLanguage = null;
|
||||
|
||||
File langFolder = new File(pluginFolderPath + File.separator + "languages");
|
||||
if (!langFolder.exists()) {
|
||||
if (!langFolder.mkdirs()) {
|
||||
throw new RuntimeException("Could not create languages folder");
|
||||
}
|
||||
}
|
||||
|
||||
File defaultFile = new File(langFolder, "default.yml");
|
||||
try {
|
||||
InputStream defaultStream = getClass().getResourceAsStream("/languages/default.yml");
|
||||
if (defaultStream == null) {
|
||||
throw new RuntimeException("Could not find default language file");
|
||||
}
|
||||
|
||||
// only copy if hash is different
|
||||
Files.copy(defaultStream, defaultFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not copy default language file");
|
||||
}
|
||||
|
||||
File[] langFiles = langFolder.listFiles();
|
||||
if (langFiles == null) {
|
||||
throw new RuntimeException("Could not list language files");
|
||||
}
|
||||
|
||||
for (File langFile : langFiles) {
|
||||
languages.add(loadLanguageFile(langFile));
|
||||
}
|
||||
|
||||
fallbackLanguage = languages.stream()
|
||||
.filter(language -> language.getLanguageCode().equals("default"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (fallbackLanguage == null) {
|
||||
throw new RuntimeException("Could not find fallback language");
|
||||
}
|
||||
}
|
||||
|
||||
private Language loadLanguageFile(File langFile) {
|
||||
String fileName = langFile.getName();
|
||||
String languageCode = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
|
||||
YamlConfiguration lang = YamlConfiguration.loadConfiguration(langFile);
|
||||
String languageName = lang.getString("language_name", languageCode);
|
||||
|
||||
Language language = new Language(languageCode, languageName);
|
||||
|
||||
|
||||
ConfigurationSection messages = lang.getConfigurationSection("messages");
|
||||
if (messages == null) {
|
||||
throw new RuntimeException("Language file " + langFile.getName() + " does not contain a messages section");
|
||||
}
|
||||
|
||||
for (String key : messages.getKeys(true)) {
|
||||
if (messages.isString(key)) {
|
||||
SimpleMessage message = new SimpleMessage(textConfig, messages.getString(key));
|
||||
language.addMessage(key, message);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (messages.isList(key)) {
|
||||
List<String> list = messages.getStringList(key);
|
||||
language.addMessage(key, new MultiMessage(textConfig, list));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
public Message translate(String key) {
|
||||
Message message = selectedLanguage.getMessage(key);
|
||||
|
||||
if (message == null) {
|
||||
message = fallbackLanguage.getMessage(key);
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
return new SimpleMessage(textConfig, "<red>Missing translation for key <i>" + key);
|
||||
}
|
||||
|
||||
return message.copy();
|
||||
}
|
||||
|
||||
public List<Language> getLanguages() {
|
||||
return languages;
|
||||
}
|
||||
|
||||
public Language getSelectedLanguage() {
|
||||
return selectedLanguage;
|
||||
}
|
||||
|
||||
public Translator setSelectedLanguage(Language selectedLanguage) {
|
||||
this.selectedLanguage = selectedLanguage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Language getFallbackLanguage() {
|
||||
return fallbackLanguage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package de.oliver.fancylib.translations.message;
|
||||
|
||||
import de.oliver.fancylib.translations.TextConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public abstract class Message {
|
||||
|
||||
protected final TextConfig config;
|
||||
protected TagResolver.Builder tagResolverBuilder = TagResolver.builder();
|
||||
|
||||
public Message(TextConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Message addTagResolver(TagResolver resolver) {
|
||||
tagResolverBuilder = tagResolverBuilder.resolver(resolver);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void applyColorPlaceholders() {
|
||||
replace("primaryColor", "<color:" + config.primaryColor() + ">");
|
||||
replace("secondaryColor", "<color:" + config.secondaryColor() + ">");
|
||||
replace("successColor", "<color:" + config.successColor() + ">");
|
||||
replace("warningColor", "<color:" + config.warningColor() + ">");
|
||||
replace("errorColor", "<color:" + config.errorColor() + ">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a placeholder in the message
|
||||
*
|
||||
* @param placeholder the placeholder to replace
|
||||
* @param replacement the replacement
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message replace(String placeholder, String replacement);
|
||||
|
||||
/**
|
||||
* Replaces a placeholder in the message, all known tags are stripped from the replacement
|
||||
*
|
||||
* @param placeholder the placeholder to replace
|
||||
* @param replacement the replacement
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message replaceStripped(String placeholder, String replacement);
|
||||
|
||||
/**
|
||||
* Adds the prefix to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message withPrefix();
|
||||
|
||||
/**
|
||||
* Adds the primary color to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message primary();
|
||||
|
||||
/**
|
||||
* Adds the secondary color to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message secondary();
|
||||
|
||||
/**
|
||||
* Adds the success color to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message success();
|
||||
|
||||
/**
|
||||
* Adds the warning color to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message warning();
|
||||
|
||||
/**
|
||||
* Adds the error color to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message error();
|
||||
|
||||
/**
|
||||
* Applies custom placeholders to the message
|
||||
*
|
||||
* @return this message
|
||||
*/
|
||||
public abstract Message applyCustomPlaceholders();
|
||||
|
||||
/**
|
||||
* Builds the message as a component
|
||||
*
|
||||
* @return the built component
|
||||
*/
|
||||
public abstract Component buildComponent();
|
||||
|
||||
/**
|
||||
* Copies the message
|
||||
*
|
||||
* @return the copied message
|
||||
*/
|
||||
public abstract Message copy();
|
||||
|
||||
public void send(CommandSender receiver) {
|
||||
Component msg = buildComponent();
|
||||
if (Component.empty().equals(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
receiver.sendMessage(msg);
|
||||
}
|
||||
|
||||
public void actionbar(Player receiver) {
|
||||
Component msg = buildComponent();
|
||||
if (Component.empty().equals(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
receiver.sendActionBar(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package de.oliver.fancylib.translations.message;
|
||||
|
||||
import de.oliver.fancylib.translations.TextConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MultiMessage extends Message {
|
||||
|
||||
private final List<String> messages;
|
||||
|
||||
public MultiMessage(TextConfig config, List<String> messages) {
|
||||
super(config);
|
||||
this.messages = new ArrayList<>(messages);
|
||||
|
||||
applyColorPlaceholders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message replace(String placeholder, String replacement) {
|
||||
messages.replaceAll(s -> s
|
||||
.replace("{" + placeholder + "}", replacement)
|
||||
.replace("%" + placeholder + "%", replacement));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message replaceStripped(String placeholder, String replacement) {
|
||||
return replace(placeholder, MiniMessage.miniMessage().stripTags(replacement, tagResolverBuilder.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message withPrefix() {
|
||||
messages.replaceAll(s -> config.prefix() + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message primary() {
|
||||
messages.replaceAll(s -> "<color:" + config.primaryColor() + ">" + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message secondary() {
|
||||
messages.replaceAll(s -> "<color:" + config.secondaryColor() + ">" + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message success() {
|
||||
messages.replaceAll(s -> "<color:" + config.successColor() + ">" + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message warning() {
|
||||
messages.replaceAll(s -> "<color:" + config.warningColor() + ">" + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message error() {
|
||||
messages.replaceAll(s -> "<color:" + config.errorColor() + ">" + s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message applyCustomPlaceholders() {
|
||||
//TODO: add ChatColorHandler support
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component buildComponent() {
|
||||
String joined = String.join("\n", messages);
|
||||
return MiniMessage.miniMessage().deserialize(joined, tagResolverBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message copy() {
|
||||
return new MultiMessage(config, messages);
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return String.join("\n", messages);
|
||||
}
|
||||
|
||||
public List<String> getRawMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public List<SimpleMessage> getSimpleMessages() {
|
||||
List<SimpleMessage> messages = new ArrayList<>();
|
||||
for (String s : this.messages) {
|
||||
messages.add(new SimpleMessage(config, s));
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public MultiMessage page(int page, int messagesPerPage) {
|
||||
List<String> pageMessages = new ArrayList<>();
|
||||
int start = (page - 1) * messagesPerPage;
|
||||
int end = Math.min(start + messagesPerPage, messages.size());
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
pageMessages.add(messages.get(i));
|
||||
}
|
||||
|
||||
return new MultiMessage(config, pageMessages);
|
||||
}
|
||||
|
||||
public int getPages(int messagesPerPage) {
|
||||
return (int) Math.ceil((double) messages.size() / messagesPerPage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package de.oliver.fancylib.translations.message;
|
||||
|
||||
import de.oliver.fancylib.translations.TextConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class SimpleMessage extends Message {
|
||||
|
||||
private String message;
|
||||
|
||||
public SimpleMessage(TextConfig config, String message) {
|
||||
super(config);
|
||||
this.message = message;
|
||||
|
||||
applyColorPlaceholders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage replace(String placeholder, String replacement) {
|
||||
message = message
|
||||
.replace("{" + placeholder + "}", replacement)
|
||||
.replace("%" + placeholder + "%", replacement);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message replaceStripped(String placeholder, String replacement) {
|
||||
return replace(placeholder, MiniMessage.miniMessage().stripTags(replacement, tagResolverBuilder.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage withPrefix() {
|
||||
message = config.prefix() + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage primary() {
|
||||
message = "<color:" + config.primaryColor() + ">" + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage secondary() {
|
||||
message = "<color:" + config.secondaryColor() + ">" + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage success() {
|
||||
message = "<color:" + config.successColor() + ">" + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage warning() {
|
||||
message = "<color:" + config.warningColor() + ">" + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage error() {
|
||||
message = "<color:" + config.errorColor() + ">" + message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMessage applyCustomPlaceholders() {
|
||||
// TODO: add ChatColorHandler support
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component buildComponent() {
|
||||
return MiniMessage.miniMessage().deserialize(message, tagResolverBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message copy() {
|
||||
return new SimpleMessage(config, message);
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void actionbar(Player receiver) {
|
||||
receiver.sendActionBar(buildComponent());
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.oliver.fancylib.versionFetcher;
|
||||
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
public class HangarVersionFetcher implements VersionFetcher{
|
||||
|
||||
private final String pluginName;
|
||||
private ComparableVersion newestVersion;
|
||||
|
||||
public HangarVersionFetcher(String pluginName) {
|
||||
this.pluginName = pluginName;
|
||||
this.newestVersion = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparableVersion fetchNewestVersion() {
|
||||
if(newestVersion != null) return newestVersion;
|
||||
|
||||
String versionStr = VersionFetcher.getDataFromUrl("https://hangar.papermc.io/api/v1/projects/" + pluginName + "/latestrelease");
|
||||
if(versionStr == null || versionStr.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
|
||||
newestVersion = new ComparableVersion(versionStr);
|
||||
return newestVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl() {
|
||||
return "https://hangar.papermc.io/Oliver/" + pluginName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.oliver.fancylib.versionFetcher;
|
||||
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class MasterVersionFetcher implements VersionFetcher{
|
||||
|
||||
private final String pluginName;
|
||||
private ComparableVersion newestVersion;
|
||||
private LinkedList<VersionFetcher> fetchers;
|
||||
|
||||
public MasterVersionFetcher(String pluginName) {
|
||||
this.pluginName = pluginName;
|
||||
this.fetchers = new LinkedList<>();
|
||||
fetchers.push(new ReposiliteVersionFetcher(pluginName));
|
||||
fetchers.push(new ModrinthVersionFetcher(pluginName));
|
||||
fetchers.push(new HangarVersionFetcher(pluginName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparableVersion fetchNewestVersion() {
|
||||
for (VersionFetcher fetcher : fetchers) {
|
||||
ComparableVersion version = fetcher.fetchNewestVersion();
|
||||
if(version == null) continue;
|
||||
newestVersion = version;
|
||||
return newestVersion;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl() {
|
||||
return "https://modrinth.com/plugin/" + pluginName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package de.oliver.fancylib.versionFetcher;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ModrinthVersionFetcher implements VersionFetcher {
|
||||
|
||||
private final String pluginName;
|
||||
private ComparableVersion newestVersion;
|
||||
|
||||
public ModrinthVersionFetcher(String pluginName) {
|
||||
this.pluginName = pluginName;
|
||||
this.newestVersion = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparableVersion fetchNewestVersion() {
|
||||
if (newestVersion != null) return newestVersion;
|
||||
|
||||
String jsonString = de.oliver.fancylib.versionFetcher.VersionFetcher.getDataFromUrl("https://api.modrinth.com/v2/project/" + pluginName.toLowerCase() + "/version");
|
||||
if (jsonString == null || jsonString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
Map<String, Object>[] versions = gson.fromJson(jsonString, Map[].class);
|
||||
|
||||
|
||||
for (Map<String, Object> version : versions) {
|
||||
if (!version.get("version_type").equals("release")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String versionNumber = (String) version.get("version_number");
|
||||
newestVersion = new ComparableVersion(versionNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
return newestVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl() {
|
||||
return "https://modrinth.com/plugin/" + pluginName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package de.oliver.fancylib.versionFetcher;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ReposiliteVersionFetcher implements VersionFetcher{
|
||||
|
||||
private final String pluginName;
|
||||
private ComparableVersion newestVersion;
|
||||
|
||||
public ReposiliteVersionFetcher(String pluginName) {
|
||||
this.pluginName = pluginName;
|
||||
this.newestVersion = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparableVersion fetchNewestVersion() {
|
||||
if (newestVersion != null) return newestVersion;
|
||||
|
||||
String jsonString = VersionFetcher.getDataFromUrl("https://repo.fancyplugins.de/api/maven/latest/version/releases/de/oliver/" + pluginName);
|
||||
if (jsonString == null || jsonString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
Map<String, Object> data = gson.fromJson(jsonString, Map.class);
|
||||
String versionStr = (String) data.get("version");
|
||||
|
||||
newestVersion = new ComparableVersion(versionStr);
|
||||
return newestVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl() {
|
||||
return "https://modrinth.com/plugin/" + pluginName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.oliver.fancylib.versionFetcher;
|
||||
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Scanner;
|
||||
|
||||
public interface VersionFetcher {
|
||||
|
||||
ComparableVersion fetchNewestVersion();
|
||||
String getDownloadUrl();
|
||||
|
||||
static String getDataFromUrl(String urlString) {
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
URLConnection connection = url.openConnection();
|
||||
|
||||
connection.setConnectTimeout(300);
|
||||
Scanner scanner = new Scanner(connection.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A");
|
||||
|
||||
return scanner.hasNext() ? scanner.next() : "";
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package de.oliver.fancylib.jdb;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class JDBTest {
|
||||
|
||||
//This class tests the get operation of the JDB class,
|
||||
//which is supposed to retrieve and deserialize a JSON document
|
||||
//from a given path in the file system.
|
||||
|
||||
private final String basePath = "./test_files/";
|
||||
|
||||
public static void cleanUpDirectory(String path) throws IOException {
|
||||
Path directory = Paths.get(path);
|
||||
if (Files.exists(directory)) {
|
||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
cleanUpDirectory(basePath);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
cleanUpDirectory(basePath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetObject() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "test_file";
|
||||
jdb.set(path, "Test message");
|
||||
|
||||
// Act
|
||||
String result = jdb.get(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertEquals("Test message", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetObjectNonExisting() throws IOException {
|
||||
// Prepare
|
||||
JDB jdb = new JDB("./test_files/");
|
||||
String path = "does_not_exist";
|
||||
|
||||
// Act
|
||||
Object result = jdb.get(path, Object.class);
|
||||
|
||||
// Assert
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllObjects() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "test_files";
|
||||
jdb.set(path + "/obj1", "Test message 1");
|
||||
jdb.set(path + "/obj2", "Test message 2");
|
||||
jdb.set(path + "/obj3", "Test message 3");
|
||||
|
||||
// Act
|
||||
List<String> result = jdb.getAll(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertEquals(3, result.size());
|
||||
assertTrue(result.contains("Test message 1"));
|
||||
assertTrue(result.contains("Test message 2"));
|
||||
assertTrue(result.contains("Test message 3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllObjectsNonExisting() throws IOException {
|
||||
// Prepare
|
||||
JDB jdb = new JDB("./test_files/");
|
||||
String path = "does_not_exist";
|
||||
|
||||
// Act
|
||||
List<Object> result = jdb.getAll(path, Object.class);
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetNewObject() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "new_object";
|
||||
String value = "New message";
|
||||
|
||||
// Act
|
||||
jdb.set(path, value);
|
||||
String result = jdb.get(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertEquals(value, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetExistingObject() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "existing_object";
|
||||
String value = "Existing message";
|
||||
jdb.set(path, "Old message");
|
||||
|
||||
// Act
|
||||
jdb.set(path, value);
|
||||
String result = jdb.get(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertEquals(value, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetObjectNull() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "null_object";
|
||||
|
||||
// Act
|
||||
jdb.set(path, null);
|
||||
String result = jdb.get(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteWhenFileExists() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "existing_file";
|
||||
String value = "Test message";
|
||||
jdb.set(path, value);
|
||||
|
||||
// Act
|
||||
jdb.delete(path);
|
||||
String result = jdb.get(path, String.class);
|
||||
|
||||
// Assert
|
||||
assertNull(result);
|
||||
|
||||
File file = new File(basePath + path + ".json");
|
||||
assertFalse(file.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteWhenFileNotExists() {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "non_existing_file";
|
||||
|
||||
// Act
|
||||
jdb.delete(path);
|
||||
|
||||
// Assert
|
||||
File file = new File(basePath + path + ".json");
|
||||
assertFalse(file.exists());
|
||||
}
|
||||
|
||||
// The getDocument method in the JDB class is supposed to retrieve and deserialize a JSON document(but encapsulated in a JDocument) from a given path in the file system.
|
||||
// Testing getDocument method when the file exists
|
||||
@Test
|
||||
public void testGetDocumentWhenFileExists() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "existing_file";
|
||||
TestObject value = new TestObject("Test message");
|
||||
jdb.set(path, value);
|
||||
|
||||
// Act
|
||||
JDocument document = jdb.getDocument(path);
|
||||
|
||||
// Assert
|
||||
assertNotNull(document);
|
||||
assertEquals(value.message(), document.getString("message"));
|
||||
}
|
||||
|
||||
// Testing the getDocument method when the file does not exist
|
||||
@Test
|
||||
public void testGetDocumentWhenFileDoesNotExist() throws IOException {
|
||||
// Prepare
|
||||
String basePath = "./test_files/";
|
||||
JDB jdb = new JDB(basePath);
|
||||
String path = "non_existing_file";
|
||||
|
||||
// Act
|
||||
JDocument document = jdb.getDocument(path);
|
||||
|
||||
// Assert
|
||||
assertNull(document);
|
||||
}
|
||||
|
||||
record TestObject(String message) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
package de.oliver.fancylib.jdb;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class JDocumentTest {
|
||||
|
||||
/**
|
||||
* The JDocumentTest class contains unit tests for the JDocument class.
|
||||
* The get method in the JDocument class is being tested here.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGet_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", "value1");
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
Object result = jDocument.get("key1");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("value1", result.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
Object result = jDocument.get("key1");
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
Object result = jDocument.get("key1.key2");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("value2", result.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
Object result = jDocument.get("key1.key3");
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The contains method in the JDocument class is being tested here.
|
||||
* It checks whether a given key is present in the JDocument's data or not.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testContains_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", "value1");
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.contains("key1");
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
boolean result = jDocument.contains("key1");
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.contains("key1.key2");
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.contains("key1.key3");
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getKeys method in the JDocument class is being tested here.
|
||||
* It retrieves the keys of the nested Map present within the data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetKeys_Success_SingleKey() {
|
||||
Map<String, Object> innerData = new HashMap<>();
|
||||
innerData.put("innerKey1", "value1");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", innerData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
Set<String> keys = jDocument.getKeys("key1");
|
||||
|
||||
assertNotNull(keys);
|
||||
assertEquals(1, keys.size());
|
||||
assertTrue(keys.contains("innerKey1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetKeys_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
Set<String> keys = jDocument.getKeys("key1");
|
||||
|
||||
assertNotNull(keys);
|
||||
assertTrue(keys.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetKeys_Success_MultipleKeys() {
|
||||
Map<String, Object> innerData = new HashMap<>();
|
||||
innerData.put("innerKey1", "value1");
|
||||
innerData.put("innerKey2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", innerData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
Set<String> keys = jDocument.getKeys("key1");
|
||||
|
||||
assertNotNull(keys);
|
||||
assertEquals(2, keys.size());
|
||||
assertTrue(keys.contains("innerKey1"));
|
||||
assertTrue(keys.contains("innerKey2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The getString method in the JDocument class is being tested here.
|
||||
* It returns a String value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetString_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", "value1");
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
String result = jDocument.getString("key1");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("value1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetString_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
String result = jDocument.getString("key1");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetString_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
String result = jDocument.getString("key1.key2");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("value2", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetString_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", "value2");
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
String result = jDocument.getString("key1.key3");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getBoolean method in the JDocument class is being tested here.
|
||||
* It retrieves a boolean value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetBoolean_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", true);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.getBoolean("key1");
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoolean_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
boolean result = jDocument.getBoolean("key1");
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoolean_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", true);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.getBoolean("key1.key2");
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoolean_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", true);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
boolean result = jDocument.getBoolean("key1.key3");
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getByte method in the JDocument class is being tested here.
|
||||
* It retrieves a byte value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetByte_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", (byte) 1);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
byte result = jDocument.getByte("key1");
|
||||
|
||||
assertEquals((byte) 1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByte_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
byte result = jDocument.getByte("key1");
|
||||
|
||||
assertEquals((byte) 0, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByte_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", (byte) 2);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
byte result = jDocument.getByte("key1.key2");
|
||||
|
||||
assertEquals((byte) 2, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByte_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", (byte) 2);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
byte result = jDocument.getByte("key1.key3");
|
||||
|
||||
assertEquals((byte) 0, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getShort method in the JDocument class is being tested here.
|
||||
* It retrieves a short value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetShort_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", (short) 1);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
short result = jDocument.getShort("key1");
|
||||
|
||||
assertEquals((short) 1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShort_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
short result = jDocument.getShort("key1");
|
||||
|
||||
assertEquals((short) 0, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShort_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", (short) 2);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
short result = jDocument.getShort("key1.key2");
|
||||
|
||||
assertEquals((short) 2, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShort_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", (short) 2);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
short result = jDocument.getShort("key1.key3");
|
||||
|
||||
assertEquals((short) 0, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getInt method in the JDocument class is being tested here.
|
||||
* It retrieves an integer value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetInt_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", 123);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
int result = jDocument.getInt("key1");
|
||||
|
||||
assertEquals(123, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInt_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
int result = jDocument.getInt("key1");
|
||||
|
||||
assertEquals(0, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInt_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 456);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
int result = jDocument.getInt("key1.key2");
|
||||
|
||||
assertEquals(456, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInt_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 456);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
int result = jDocument.getInt("key1.key3");
|
||||
|
||||
assertEquals(0, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getLong method in the JDocument class is being tested here.
|
||||
* It retrieves a long value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetLong_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", 123L);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
long result = jDocument.getLong("key1");
|
||||
|
||||
assertEquals(123L, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLong_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
long result = jDocument.getLong("key1");
|
||||
|
||||
assertEquals(0L, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLong_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 456L);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
long result = jDocument.getLong("key1.key2");
|
||||
|
||||
assertEquals(456L, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLong_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 456L);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
long result = jDocument.getLong("key1.key3");
|
||||
|
||||
assertEquals(0L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getFloat method in the JDocument class is being tested here.
|
||||
* It retrieves a float value of the specified key from the JDocument's data.
|
||||
*/
|
||||
@Test
|
||||
public void testGetFloat_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", 1.23f);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
float result = jDocument.getFloat("key1");
|
||||
|
||||
assertEquals(1.23f, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloat_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
float result = jDocument.getFloat("key1");
|
||||
|
||||
assertEquals(0f, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloat_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 4.56f);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
float result = jDocument.getFloat("key1.key2");
|
||||
|
||||
assertEquals(4.56f, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloat_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 4.56f);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
float result = jDocument.getFloat("key1.key3");
|
||||
|
||||
assertEquals(0f, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getDouble method in the JDocument class is being tested here.
|
||||
* It retrieves a double value of the specified key from the JDocument's data.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGetDouble_Success_SingleKey() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", 1.23d);
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
double result = jDocument.getDouble("key1");
|
||||
|
||||
assertEquals(1.23d, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDouble_Failure_KeyNotFound() {
|
||||
JDocument jDocument = new JDocument(Collections.emptyMap());
|
||||
|
||||
double result = jDocument.getDouble("key1");
|
||||
|
||||
assertEquals(0d, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDouble_Success_NestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 4.56d);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
double result = jDocument.getDouble("key1.key2");
|
||||
|
||||
assertEquals(4.56d, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDouble_Failure_NonExistentNestedKey() {
|
||||
Map<String, Object> nestedData = new HashMap<>();
|
||||
nestedData.put("key2", 4.56d);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("key1", nestedData);
|
||||
|
||||
JDocument jDocument = new JDocument(data);
|
||||
|
||||
double result = jDocument.getDouble("key1.key3");
|
||||
|
||||
assertEquals(0d, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user