This commit is contained in:
Oliver
2025-03-03 21:57:54 +01:00
parent 1c8a53e778
commit ffc4c52c9a
197 changed files with 11722 additions and 13 deletions

122
libraries/common/.gitignore vendored Normal file
View 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

View 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>")
}
```

View 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()
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View 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();
}
}
}

View File

@@ -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);
}
}

View 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;
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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) {
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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");
}
}

View File

@@ -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() + ")";
}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,12 @@
package de.oliver.fancylib.translations;
public record TextConfig(
String primaryColor,
String secondaryColor,
String successColor,
String warningColor,
String errorColor,
String prefix
) {
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 "";
}
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}