mirror of
https://github.com/AXOLOTsh/RegistryLib.git
synced 2026-06-02 09:46:33 +03:00
feature: add project files
This commit is contained in:
28
src/main/java/io/github/axolotsh/registrylib/KeyAdapter.java
Normal file
28
src/main/java/io/github/axolotsh/registrylib/KeyAdapter.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package io.github.axolotsh.registrylib;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the serialization logic of {@link Key}, causing it to be serialized
|
||||||
|
* and deserialized as {@link String}.
|
||||||
|
*/
|
||||||
|
public class KeyAdapter implements JsonSerializer<Key>, JsonDeserializer<Key> {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Key src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
|
||||||
|
return Key.key(json.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
251
src/main/java/io/github/axolotsh/registrylib/Registry.java
Normal file
251
src/main/java/io/github/axolotsh/registrylib/Registry.java
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
package io.github.axolotsh.registrylib;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
|
||||||
|
import net.kyori.adventure.key.Keyed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic registry for managing objects that implement the {@link Keyed}
|
||||||
|
* interface.
|
||||||
|
*
|
||||||
|
* @param <T> The type extending {@link Keyed} of objects stored in this
|
||||||
|
* registry.
|
||||||
|
*/
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
public class Registry<T extends Keyed> {
|
||||||
|
private final Map<Key, T> map = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The registry type category (e.g., "items", "quests").
|
||||||
|
*
|
||||||
|
* @return The registry type category.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Nullable
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class type of the elements handled by this registry.
|
||||||
|
*
|
||||||
|
* @return The {@link Class} object for type {@code T}.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private Class<T> typeClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @param typeClass The class of the type to be stored.
|
||||||
|
*/
|
||||||
|
public Registry(String type, Class<T> typeClass) {
|
||||||
|
this.type = type;
|
||||||
|
this.typeClass = typeClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a value to the registry.
|
||||||
|
*
|
||||||
|
* @param value The value to add.
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> add(T value) {
|
||||||
|
map.put(value.key(), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a collection of values to the registry.
|
||||||
|
*
|
||||||
|
* @param values The collection of values to add.
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> addAll(Collection<T> values) {
|
||||||
|
values.forEach(x -> {
|
||||||
|
map.put(x.key(), x);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a value by its string representation of a {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The string representation of a {@link Key}.
|
||||||
|
* @return The value associated with the key, or {@code null} if not found.
|
||||||
|
*/
|
||||||
|
public T get(String key) {
|
||||||
|
return get(Key.key(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a value by its {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @return The value associated with the key, or {@code null} if not found.
|
||||||
|
*/
|
||||||
|
public T get(Key key) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a collection of values corresponding to the provided string
|
||||||
|
* representation of {@link Key}s.
|
||||||
|
*
|
||||||
|
* @param keys A collection of string keys.
|
||||||
|
* @return A collection of found values.
|
||||||
|
*/
|
||||||
|
public Collection<T> getAllByStrings(Collection<String> keys) {
|
||||||
|
return getAll(keys.stream().map(Key::key).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a collection of values corresponding to the provided {@link Key}s.
|
||||||
|
*
|
||||||
|
* @param keys A collection of keys.
|
||||||
|
* @return A collection of found values.
|
||||||
|
*/
|
||||||
|
public Collection<T> getAll(Collection<Key> keys) {
|
||||||
|
Set<T> result = new HashSet<>();
|
||||||
|
for (var key : keys)
|
||||||
|
result.add(get(key));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all values currently stored in the registry.
|
||||||
|
*
|
||||||
|
* @return A collection of all values.
|
||||||
|
*/
|
||||||
|
public Collection<T> getAll() {
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a specific value from the registry.
|
||||||
|
*
|
||||||
|
* @param value The value to remove.
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> remove(T value) {
|
||||||
|
return remove(value.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a value from the registry by its string representation of a
|
||||||
|
* {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The string representation of a {@link Key} to remove.
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> remove(String key) {
|
||||||
|
return remove(Key.key(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a value from the registry by its {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The key to remove.
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> remove(Key key) {
|
||||||
|
map.remove(key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all entries from the registry.
|
||||||
|
*
|
||||||
|
* @return This registry instance for chaining.
|
||||||
|
*/
|
||||||
|
public Registry<T> clear() {
|
||||||
|
map.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the registry contains a value with the same key as the provided
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* @param value The value to check for.
|
||||||
|
* @return {@code true} if present, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean contains(T value) {
|
||||||
|
return contains(value.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the registry contains a value associated with the given string
|
||||||
|
* representation of a {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The string representation of a {@link Key} to check.
|
||||||
|
* @return {@code true} if present, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean contains(String key) {
|
||||||
|
return contains(Key.key(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the registry contains a value associated with the given
|
||||||
|
* {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The key to check.
|
||||||
|
* @return {@code true} if present, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean contains(Key key) {
|
||||||
|
return map.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve a value, or creates and adds a new one if it does not
|
||||||
|
* exist.
|
||||||
|
*
|
||||||
|
* @param key The string representation of a {@link Key}.
|
||||||
|
* @param fallback A supplier that provides the value if not found.
|
||||||
|
* @return The existing value or the newly created one.
|
||||||
|
*/
|
||||||
|
public T getOrAdd(String key, Supplier<T> fallback) {
|
||||||
|
return getOrAdd(Key.key(key), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve a value, or creates and adds a new one if it does not
|
||||||
|
* exist.
|
||||||
|
*
|
||||||
|
* @param key The {@link Key}.
|
||||||
|
* @param fallback A supplier that provides the value if not found.
|
||||||
|
* @return The existing value or the newly created one.
|
||||||
|
*/
|
||||||
|
public T getOrAdd(Key key, Supplier<T> fallback) {
|
||||||
|
var result = map.get(key);
|
||||||
|
if (result == null) {
|
||||||
|
result = fallback.get();
|
||||||
|
add(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interception point called before a value is saved to the file system via
|
||||||
|
* {@link RegistryLoader}.
|
||||||
|
* If the return value is {@code null}, {@link RegistryLoader} does not write it
|
||||||
|
* to the file system.
|
||||||
|
* By default, returns the value as-is.
|
||||||
|
*
|
||||||
|
* @param value The value to process.
|
||||||
|
* @return The modified value or {@code null}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public T onBeforeSave(T value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
213
src/main/java/io/github/axolotsh/registrylib/RegistryLoader.java
Normal file
213
src/main/java/io/github/axolotsh/registrylib/RegistryLoader.java
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package io.github.axolotsh.registrylib;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import net.kyori.adventure.key.Keyed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides utility methods for loading and saving {@link Keyed} registry
|
||||||
|
* objects from the file system.
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@Accessors(chain = true, fluent = true)
|
||||||
|
public class RegistryLoader {
|
||||||
|
/**
|
||||||
|
* The logger used to report IO errors and serialization failures.
|
||||||
|
*
|
||||||
|
* @return The current logger instance.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base directory where registry files are stored.
|
||||||
|
*
|
||||||
|
* @return The current base directory.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private Path rootPath;
|
||||||
|
|
||||||
|
private static final Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Key.class, new KeyAdapter())
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a single JSON file and converts it into a keyed object.
|
||||||
|
*
|
||||||
|
* @param filePath The path to the JSON file.
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @param classType The class to deserialize into.
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
* @return The loaded object, or {@code null} if loading
|
||||||
|
* fails.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public <T extends Keyed> T loadFile(Path filePath, String type, Class<T> classType) {
|
||||||
|
try (var reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
|
||||||
|
var result = gson.fromJson(reader, classType);
|
||||||
|
injectKey(result, getFileKey(filePath, type));
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to load registry file {}: {}", filePath, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectKey(Object target, Key key) throws Exception {
|
||||||
|
var current = target.getClass();
|
||||||
|
while (current != null) {
|
||||||
|
try {
|
||||||
|
var field = current.getDeclaredField("key");
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(target, key);
|
||||||
|
return;
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
current = current.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException("Field 'key' not found in class hierarchy");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a keyed object to its corresponding JSON file location.
|
||||||
|
*
|
||||||
|
* @param value The object to save.
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
*/
|
||||||
|
public <T extends Keyed> void saveFile(T value, String type) {
|
||||||
|
var filePath = getKeyFile(value.key(), type);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(filePath.getParent());
|
||||||
|
try (var writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) {
|
||||||
|
gson.toJson(value, writer);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to save registry file {}: {}", filePath, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines a {@link Key} based on a file's location relative to the
|
||||||
|
* {@code rootPath}.
|
||||||
|
*
|
||||||
|
* @param filePath The path to the JSON file.
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @return A {@link Key} representing the namespace and internal path.
|
||||||
|
*/
|
||||||
|
public Key getFileKey(Path filePath, String type) {
|
||||||
|
var relative = rootPath.relativize(filePath);
|
||||||
|
var namespace = relative.getName(0).toString();
|
||||||
|
|
||||||
|
var valuePath = relative.subpath(2, relative.getNameCount()).toString()
|
||||||
|
.replace('\\', '/')
|
||||||
|
.replace(".json", "");
|
||||||
|
|
||||||
|
return Key.key(namespace, valuePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines a {@link Path} based on a provided key relative to the
|
||||||
|
* {@code rootPath}.
|
||||||
|
*
|
||||||
|
* @param key The key identifier.
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @return A {@link Path} representing the key.
|
||||||
|
*/
|
||||||
|
public Path getKeyFile(Key key, String type) {
|
||||||
|
return rootPath.resolve(key.namespace())
|
||||||
|
.resolve(type)
|
||||||
|
.resolve(key.value() + ".json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all objects of a specific type category.
|
||||||
|
*
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @param classType The class to deserialize into.
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
* @return A collection of loaded objects.
|
||||||
|
*/
|
||||||
|
public <T extends Keyed> Collection<T> loadAll(String type, Class<T> classType) {
|
||||||
|
if (!Files.exists(rootPath))
|
||||||
|
return Collections.emptyList();
|
||||||
|
|
||||||
|
List<T> loaded = new ArrayList<>();
|
||||||
|
try (var namespaces = Files.list(rootPath)) {
|
||||||
|
for (var nsPath : namespaces.filter(Files::isDirectory).toList()) {
|
||||||
|
var typePath = nsPath.resolve(type);
|
||||||
|
if (!Files.exists(typePath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try (var files = Files.walk(typePath)) {
|
||||||
|
var results = files
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(path -> path.toString().endsWith(".json"))
|
||||||
|
.map(path -> loadFile(path, type, classType))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
loaded.addAll(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error while walking through registry: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all objects of a specific type category and add them into provided
|
||||||
|
* registry.
|
||||||
|
*
|
||||||
|
* @param registry The registry to add loaded object into.
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
*/
|
||||||
|
public <T extends Keyed> void loadAll(Registry<T> registry) {
|
||||||
|
registry.addAll(loadAll(registry.type(), registry.typeClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a collection of objects to the filesystem.
|
||||||
|
*
|
||||||
|
* @param values The values collection to save.
|
||||||
|
* @param type The registry type category (e.g., "items", "quests").
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
*/
|
||||||
|
public <T extends Keyed> void saveAll(Collection<T> values, String type) {
|
||||||
|
values.forEach(x -> saveFile(x, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves all objects from a registry, invoking
|
||||||
|
* {@link Registry#onBeforeSave(Object)}
|
||||||
|
* on each item before writing to disk.
|
||||||
|
*
|
||||||
|
* @param registry The registry to save.
|
||||||
|
* @param <T> A type extending {@link Keyed}.
|
||||||
|
*/
|
||||||
|
public <T extends Keyed> void saveAll(Registry<T> registry) {
|
||||||
|
registry.getAll().stream()
|
||||||
|
.map(registry::onBeforeSave)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(x -> saveFile(x, registry.type()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user