mirror of
https://github.com/AXOLOTsh/RegistryLib.git
synced 2026-06-02 01:36:31 +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