mirror of
https://github.com/FancyInnovations/FancyPlugins.git
synced 2025-12-06 07:43:36 +00:00
Compare commits
6 Commits
6fe7cbabbd
...
c0998aabdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0998aabdb | ||
|
|
09363fe010 | ||
|
|
6c1ff8a77a | ||
|
|
0d8665e5e5 | ||
|
|
b2416a14b2 | ||
|
|
ec842f7287 |
@@ -1,6 +1,5 @@
|
||||
fancylibVersion=37
|
||||
fancysitulaVersion=0.0.13
|
||||
jdbVersion=1.0.1
|
||||
plugintestsVersion=1.0.0
|
||||
org.gradle.parallel=false
|
||||
org.gradle.caching=true
|
||||
1
libraries/config/VERSION
Normal file
1
libraries/config/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -24,6 +24,38 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "fancyinnovationsReleases"
|
||||
url = uri("https://repo.fancyinnovations.com/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "fancyinnovationsSnapshots"
|
||||
url = uri("https://repo.fancyinnovations.com/snapshots")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
groupId = "de.oliver"
|
||||
artifactId = "config"
|
||||
version = getCFGVersion()
|
||||
from(project.components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release.set(17) //TODO change to 21, once 1.19.4 support is dropped
|
||||
@@ -49,3 +81,7 @@ tasks {
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17)) //TODO change to 21, once 1.19.4 support is dropped
|
||||
}
|
||||
|
||||
fun getCFGVersion(): String {
|
||||
return file("VERSION").readText()
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.fancyinnovations.config;
|
||||
|
||||
import com.google.gson.*;
|
||||
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
|
||||
import de.oliver.fancyanalytics.logger.properties.ThrowableProperty;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConfigJSON {
|
||||
|
||||
private final ExtendedFancyLogger logger;
|
||||
private final File configFile;
|
||||
private final Map<String, ConfigField<?>> fields;
|
||||
private final Map<String, Object> values;
|
||||
private final Gson gson;
|
||||
|
||||
public ConfigJSON(ExtendedFancyLogger logger, String configFilePath) {
|
||||
this.logger = logger;
|
||||
this.configFile = new File(configFilePath);
|
||||
this.fields = new ConcurrentHashMap<>();
|
||||
this.values = new ConcurrentHashMap<>();
|
||||
this.gson = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
}
|
||||
|
||||
public void addField(ConfigField<?> field) {
|
||||
fields.put(field.path(), field);
|
||||
}
|
||||
|
||||
public Map<String, ConfigField<?>> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public <T> T get(String path) {
|
||||
ConfigField<?> field = fields.get(path);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (field.forceDefault()) {
|
||||
return (T) field.defaultValue();
|
||||
}
|
||||
|
||||
Object value = values.computeIfAbsent(path, k -> field.defaultValue());
|
||||
return (T) field.type().cast(value);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
if (!configFile.exists()) {
|
||||
try {
|
||||
File parent = configFile.getParentFile();
|
||||
if (parent != null && !parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
if (!configFile.createNewFile()) {
|
||||
logger.error("Failed to create config file: " + configFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Error creating config file: " + configFile.getAbsolutePath(), ThrowableProperty.of(e));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject root = new JsonObject();
|
||||
for (ConfigField<?> field : fields.values()) {
|
||||
setDefault(root, field);
|
||||
}
|
||||
|
||||
saveJson(root);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject root;
|
||||
try (FileReader reader = new FileReader(configFile)) {
|
||||
JsonElement parsed = JsonParser.parseReader(reader);
|
||||
if (parsed == null || !parsed.isJsonObject()) {
|
||||
root = new JsonObject();
|
||||
} else {
|
||||
root = parsed.getAsJsonObject();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error reading config file: " + configFile.getAbsolutePath(), ThrowableProperty.of(e));
|
||||
root = new JsonObject();
|
||||
}
|
||||
|
||||
boolean dirty = false;
|
||||
|
||||
for (Map.Entry<String, ConfigField<?>> entry : fields.entrySet()) {
|
||||
String path = entry.getKey();
|
||||
ConfigField<?> field = entry.getValue();
|
||||
|
||||
if (field.forRemoval()) {
|
||||
if (isSet(root, path)) {
|
||||
logger.debug("Removing path '" + path + "' from config");
|
||||
removePath(root, path);
|
||||
dirty = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonElement elem = getElement(root, path);
|
||||
if (elem != null && !elem.isJsonNull()) {
|
||||
try {
|
||||
Object deserialized = gson.fromJson(elem, (Type) field.type());
|
||||
if (deserialized != null && field.type().isInstance(deserialized)) {
|
||||
values.put(path, deserialized);
|
||||
} else {
|
||||
// Attempt numeric conversions: gson may deserialize numbers as Double
|
||||
Object converted = tryConvertNumber(deserialized, field.type());
|
||||
if (converted != null) {
|
||||
values.put(path, converted);
|
||||
} else {
|
||||
logger.warn("Value for path '" + path + "' is not of type '" + field.type().getSimpleName() + "'");
|
||||
setDefault(root, field);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException | ClassCastException ex) {
|
||||
logger.warn("Failed to parse value for path '" + path + "': " + ex.getMessage());
|
||||
setDefault(root, field);
|
||||
dirty = true;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Path '" + path + "' not found in config");
|
||||
setDefault(root, field);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
saveJson(root);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefault(JsonObject root, ConfigField<?> field) {
|
||||
logger.debug("Setting default value for path '" + field.path() + "': " + field.defaultValue());
|
||||
JsonElement elem = gson.toJsonTree(field.defaultValue(), (Type) field.type());
|
||||
setElement(root, field.path(), elem);
|
||||
// JSON does not support inline comments; descriptions are not stored.
|
||||
}
|
||||
|
||||
private void saveJson(JsonObject root) {
|
||||
try (FileWriter writer = new FileWriter(configFile)) {
|
||||
gson.toJson(root, writer);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error saving config file: " + configFile.getAbsolutePath(), ThrowableProperty.of(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: get JsonElement at dot-separated path, or null if absent
|
||||
*/
|
||||
private JsonElement getElement(JsonObject root, String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
JsonElement current = root;
|
||||
for (String p : parts) {
|
||||
if (!current.isJsonObject()) return null;
|
||||
JsonObject obj = current.getAsJsonObject();
|
||||
if (!obj.has(p)) return null;
|
||||
current = obj.get(p);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: set JsonElement at dot-separated path, creating intermediate objects
|
||||
*/
|
||||
private void setElement(JsonObject root, String path, JsonElement value) {
|
||||
String[] parts = path.split("\\.");
|
||||
JsonObject current = root;
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
String p = parts[i];
|
||||
if (!current.has(p) || !current.get(p).isJsonObject()) {
|
||||
JsonObject child = new JsonObject();
|
||||
current.add(p, child);
|
||||
current = child;
|
||||
} else {
|
||||
current = current.getAsJsonObject(p);
|
||||
}
|
||||
}
|
||||
current.add(parts[parts.length - 1], value);
|
||||
}
|
||||
|
||||
private boolean isSet(JsonObject root, String path) {
|
||||
return getElement(root, path) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a path (dot-separated) from the JSON object
|
||||
*/
|
||||
private void removePath(JsonObject root, String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
JsonObject current = root;
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
String p = parts[i];
|
||||
if (!current.has(p) || !current.get(p).isJsonObject()) {
|
||||
return;
|
||||
}
|
||||
current = current.getAsJsonObject(p);
|
||||
}
|
||||
current.remove(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to convert numeric values (e.g., Double) to the requested numeric target type
|
||||
*/
|
||||
private Object tryConvertNumber(Object value, Class<?> target) {
|
||||
if (!(value instanceof Number)) return null;
|
||||
Number num = (Number) value;
|
||||
if (target == Integer.class || target == int.class) {
|
||||
return num.intValue();
|
||||
} else if (target == Long.class || target == long.class) {
|
||||
return num.longValue();
|
||||
} else if (target == Double.class || target == double.class) {
|
||||
return num.doubleValue();
|
||||
} else if (target == Float.class || target == float.class) {
|
||||
return num.floatValue();
|
||||
} else if (target == Short.class || target == short.class) {
|
||||
return num.shortValue();
|
||||
} else if (target == Byte.class || target == byte.class) {
|
||||
return num.byteValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
1
libraries/jdb/VERSION
Normal file
1
libraries/jdb/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.4
|
||||
@@ -1,11 +1,11 @@
|
||||
plugins {
|
||||
id("java")
|
||||
id("maven-publish")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
group = "de.oliver"
|
||||
version = findProperty("jdbVersion") as String
|
||||
version = getJDBVersion()
|
||||
description = "Library for storing JSON data locally"
|
||||
|
||||
java {
|
||||
@@ -55,7 +55,7 @@ tasks {
|
||||
create<MavenPublication>("maven") {
|
||||
groupId = "de.oliver"
|
||||
artifactId = "JDB"
|
||||
version = findProperty("jdbVersion") as String
|
||||
version = getJDBVersion()
|
||||
from(project.components["java"])
|
||||
}
|
||||
}
|
||||
@@ -85,3 +85,7 @@ tasks {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
fun getJDBVersion(): String {
|
||||
return file("VERSION").readText()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -16,7 +17,7 @@ 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()
|
||||
public final static Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
@@ -24,6 +25,7 @@ public class JDB {
|
||||
private static final String FILE_EXTENSION = ".json";
|
||||
private final @NotNull String basePath;
|
||||
private final @NotNull File baseDirectory;
|
||||
private final JIndex index;
|
||||
|
||||
/**
|
||||
* Constructs a new JDB instance with the specified base path.
|
||||
@@ -33,6 +35,8 @@ public class JDB {
|
||||
public JDB(@NotNull String basePath) {
|
||||
this.basePath = basePath;
|
||||
this.baseDirectory = new File(basePath);
|
||||
|
||||
this.index = JIndex.load("jdb_index", basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +51,13 @@ public class JDB {
|
||||
public <T> T get(@NotNull String path, @NotNull Class<T> clazz) throws IOException {
|
||||
File documentFile = new File(baseDirectory, createFilePath(path));
|
||||
if (!documentFile.exists()) {
|
||||
|
||||
// Check index for alternative path
|
||||
if (index.indexMap().containsKey(path)) {
|
||||
String indexPath = index.indexMap().get(path);
|
||||
return get(indexPath, clazz);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
BufferedReader bufferedReader = Files.newBufferedReader(documentFile.toPath());
|
||||
@@ -95,6 +106,26 @@ public class JDB {
|
||||
return documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of documents in the specified directory path.
|
||||
*
|
||||
* @param path the relative directory path
|
||||
* @return the number of documents in the directory
|
||||
*/
|
||||
public int countDocuments(@NotNull String path) {
|
||||
File directory = new File(baseDirectory, path);
|
||||
if (!directory.exists()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return files.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given value as a document at the specified path.
|
||||
*
|
||||
@@ -113,12 +144,40 @@ public class JDB {
|
||||
Files.write(documentFile.toPath(), json.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given value as a document at the specified path and indexes it under additional paths.
|
||||
*/
|
||||
public <T> void set(@NotNull String path, @NotNull T value, String... indexPaths) throws IOException {
|
||||
set(path, value);
|
||||
for (String indexPath : indexPaths) {
|
||||
indexDocument(indexPath, path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes a document by mapping the original path to the index path.
|
||||
*
|
||||
* @param originalPath the original relative path (excluding .json extension) of the document
|
||||
* @param indexPath the index relative path (excluding .json extension) to map to the original document
|
||||
*/
|
||||
public void indexDocument(@NotNull String originalPath, @NotNull String indexPath) {
|
||||
index.indexMap().put(originalPath, indexPath);
|
||||
index.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the document(s) at the specified path.
|
||||
*
|
||||
* @param path the relative path (excluding .json extension) of the document(s) to be deleted
|
||||
*/
|
||||
public void delete(@NotNull String path) {
|
||||
for (Map.Entry<String, String> entry : new HashSet<>(index.indexMap().entrySet())) {
|
||||
if (entry.getKey().equals(path) || entry.getValue().equals(path)) {
|
||||
index.indexMap().remove(entry.getKey());
|
||||
index.save();
|
||||
}
|
||||
}
|
||||
|
||||
File file = new File(baseDirectory, path);
|
||||
if (file.isDirectory()) {
|
||||
deleteDirectory(file);
|
||||
|
||||
41
libraries/jdb/src/main/java/de/oliver/jdb/JIndex.java
Normal file
41
libraries/jdb/src/main/java/de/oliver/jdb/JIndex.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package de.oliver.jdb;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public record JIndex(
|
||||
String name,
|
||||
@SerializedName("base_path") String basePath,
|
||||
@SerializedName("index_map") Map<String, String> indexMap // key -> original path
|
||||
) {
|
||||
|
||||
public static JIndex load(String name, String basePath) {
|
||||
File indexFile = new File(basePath, name + ".json");
|
||||
if (!indexFile.exists()) {
|
||||
return new JIndex(name, basePath, new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
try (var reader = Files.newBufferedReader(indexFile.toPath())) {
|
||||
return JDB.GSON.fromJson(reader, JIndex.class);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new JIndex(name, basePath, new ConcurrentHashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
File indexFile = new File(basePath, name + ".json");
|
||||
|
||||
String json = JDB.GSON.toJson(this);
|
||||
try {
|
||||
Files.write(indexFile.toPath(), json.getBytes());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,6 +110,23 @@ public class JDBTest {
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountDocuments() 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
|
||||
int count = jdb.countDocuments(path);
|
||||
|
||||
// Assert
|
||||
assertEquals(3, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetNewObject() throws IOException {
|
||||
// Prepare
|
||||
|
||||
Reference in New Issue
Block a user