1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 10:38:19 +08:00

feat(yaml): Finished YAML Provider

This commit is contained in:
2025-02-13 06:48:58 +08:00
parent 5b95824bb0
commit 47e2a4854c
27 changed files with 357 additions and 426 deletions
@@ -0,0 +1,18 @@
package cc.carm.lib.configuration.function;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface DataConsumer<T> {
void accept(@NotNull T data) throws Exception;
default DataConsumer<T> andThen(DataConsumer<? super T> after) {
return (T t) -> {
accept(t);
after.accept(t);
};
}
}
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>,
@@ -31,8 +32,7 @@ public abstract class ConfigurationFactory<
this.adapters.register(StandardAdapters.SECTION_ADAPTER);
}
public abstract SELF self();
protected abstract SELF self();
public SELF adapters(ValueAdapterRegistry adapters) {
this.adapters = adapters;
@@ -81,13 +81,25 @@ public abstract class ConfigurationFactory<
return self();
}
public SELF option(Consumer<ConfigurationOptionHolder> optionsConsumer) {
optionsConsumer.accept(options);
public SELF option(Consumer<ConfigurationOptionHolder> modifier) {
modifier.accept(options);
return self();
}
public <O> SELF option(ConfigurationOption<O> option, O value) {
return option(o -> o.set(option, value));
public <O> SELF option(ConfigurationOption<O> type, O value) {
return option(o -> o.set(type, value));
}
public <O> SELF option(ConfigurationOption<O> type, Supplier<O> value) {
return option(type, value.get());
}
public <O> SELF option(ConfigurationOption<O> type, Consumer<O> modifier) {
return option(holder -> {
O current = holder.get(type);
modifier.accept(current);
holder.set(type, current);
});
}
@@ -67,14 +67,9 @@ public class ConfigurationInitializer {
this.classInitializer = this.classInitializer.andThen(classInitializer);
}
public <T, A extends Annotation> void registerAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
});
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> {
A data = clazz.getAnnotation(annotation);
if (data == null) return;
@@ -82,6 +77,23 @@ public class ConfigurationInitializer {
});
}
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
});
}
public <T, A extends Annotation> void registerAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
registerClassAnnotation(annotation, metadata, extractor);
registerFieldAnnotation(annotation, metadata, extractor);
}
public @Nullable String getFieldPath(@NotNull ConfigurationHolder<?> holder, @Nullable String parentPath, @NotNull Field field) {
return pathGenerator.getFieldPath(holder, parentPath, field);
@@ -54,13 +54,14 @@ public class PathGenerator {
ConfigPath clazzPath = clazz.getAnnotation(ConfigPath.class);
if (clazzPath != null) return link(holder, parentPath, clazzPath.root(), clazzPath.value());
if (clazzField == null) {
return link(holder, parentPath, false, parentPath == null ? null : covertPath(clazz.getSimpleName())); // No field, use class name.
if (clazzField != null) {
ConfigPath fieldPath = clazzField.getAnnotation(ConfigPath.class);
if (fieldPath == null) return link(holder, parentPath, false, covertPath(clazzField.getName()));
else return getFieldPath(holder, parentPath, clazzField);
}
ConfigPath fieldPath = clazzField.getAnnotation(ConfigPath.class);
if (fieldPath == null) return link(holder, parentPath, false, covertPath(clazzField.getName()));
else return getFieldPath(holder, parentPath, clazzField);
return link(holder, parentPath, false, covertPath(clazz.getSimpleName())); // No field, use class name.
}
protected String select(String path, String defaultValue) {
@@ -71,7 +72,7 @@ public class PathGenerator {
protected @Nullable String link(@NotNull ConfigurationHolder<?> holder,
@Nullable String parent, boolean root, @Nullable String path) {
if (path == null || path.isEmpty()) return root ? null : parent;
return root || parent == null ? path : parent + pathSeparator(holder) + path;
return (root || parent == null) ? path : (parent + pathSeparator(holder) + path);
}
public static boolean isBlank(String path) {
@@ -2,6 +2,8 @@ package cc.carm.lib.configuration.source.option;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class ConfigurationOption<V> {
@SuppressWarnings("unchecked")
@@ -13,6 +15,10 @@ public class ConfigurationOption<V> {
return new ConfigurationOption<>(valueClazz, defaultValue);
}
public static <T> ConfigurationOption<T> of(@NotNull Supplier<T> defaultValue) {
return of(defaultValue.get());
}
private final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue;
@@ -59,6 +59,11 @@ public abstract class ConfigureSource<
return null; // Source also represents the root section, so it has no parent
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return self();
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return section().getValues(deep);
@@ -7,6 +7,32 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a configurable value with type safety and null-handling capabilities.
* <p>
* This abstract class provides core functionalities for managing configuration values,
* including value retrieval with fallback defaults, null safety enforcement, and value
* persistence controls. It serves as the foundation for type-specific configuration
* implementations.
* </p>
*
* <h3>Functions:</h3>
* <ul>
* <li>Type-safe value access through {@link #get()} and {@link #optional()}</li>
* <li>Default value fallback via {@link #getOrDefault()}</li>
* <li>Null-safety enforcement with {@link #resolve()} and {@link #getNotNull()}</li>
* <li>Default value initialization through {@link #setDefault()}</li>
* <li>Value comparison with {@link #isDefault()}</li>
* </ul>
*
* <h3>Persistence Behavior:</h3>
* Value modifications via {@link #set(Object)} or {@link #setDefault()} methods
* <b>do NOT automatically persist</b> to configuration sources. Explicit calls to
* {@link ConfigurationHolder#save()} are required for permanent storage.
*
* @see ValueManifest Base class providing metadata and default value handling
* @see ConfigurationHolder Responsible for configuration source persistence
*/
public abstract class ConfigValue<T> extends ValueManifest<T> {
protected ConfigValue(@NotNull ValueManifest<T> manifest) {
@@ -14,57 +40,77 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
}
/**
* 得到该配置的设定值(即读取到的值)。
* <br> 若初始化时未写入默认值,则可以通过 {@link #getOrDefault()} 方法在该设定值为空时获取默认值。
* Gets the configured value (i.e., the value read from the source).
* <br> If no default value was written during initialization, you can use
* the {@link #getOrDefault()} method to obtain the default value when this value is empty.
*
* @return 设定值
* @return Configured value
*/
public abstract @Nullable T get();
/**
* 得到该配置的设定值,若不存在,则返回默认值。
* Gets the configured value, or returns the default value if not present.
*
* @return 设定值或默认值
* @return Configured value or default value
*/
public T getOrDefault() {
return optional().orElse(defaults());
}
/**
* 得到该配置的非空值。
* Gets the non-null value of this configuration.
*
* @return 非空值
* @throws NullPointerException 对应数据为空时抛出
* @return Non-null value
* @throws NullPointerException Thrown when the corresponding data is null
*/
public @NotNull T getNotNull() {
public @NotNull T resolve() {
return Objects.requireNonNull(getOrDefault(), "Value(" + type() + ") @[" + path() + "] is null.");
}
/**
* Gets the non-null value of this configuration.
*
* @return Non-null value
* @throws NullPointerException Thrown when the corresponding data is null
* @see #resolve()
*/
public @NotNull T getNotNull() {
return resolve();
}
/**
* Gets the value of this configuration as an {@link Optional}.
*
* @return {@link Optional} value
*/
public @NotNull Optional<@Nullable T> optional() {
return Optional.ofNullable(get());
}
/**
* 设定该配置的值。
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。
* Sets the value of this configuration.
* <br> After setting, the configuration file will NOT be saved automatically.
* To save, call {@link ConfigurationHolder#save()}.
*
* @param value 配置的值
* @param value The value to set
*/
public abstract void set(@Nullable T value);
/**
* 初始化该配置的默认值。
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。
* Initializes the default value for this configuration.
* <br> After setting, the configuration file will NOT be saved automatically.
* To save, call {@link ConfigurationHolder#save()}.
*/
public void setDefault() {
setDefault(false);
}
/**
* 将该配置的值设置为默认值。
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。
* Sets the configuration value to its default.
* <br> After setting, the configuration file will NOT be saved automatically.
* To save, call {@link ConfigurationHolder#save()}.
*
* @param override 是否覆盖已设定的值
* @param override Whether to overwrite existing configured value
*/
public void setDefault(boolean override) {
if (!override && config().contains(path())) return;
@@ -72,9 +118,9 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
}
/**
* 判断加载的配置是否与默认值相同。
* Checks if the loaded configuration value matches the default value.
*
* @return 获取当前值是否为默认值。
* @return Whether the current value is the default value
*/
public boolean isDefault() {
return Objects.equals(defaults(), get());
@@ -86,10 +132,10 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* it is recommended to call {@link ConfigurationHolder#save()}
* after all modifications are completed instead of this.
*
* @throws Exception
* @throws Exception Thrown when an error occurs during saving
*/
public void save() throws Exception {
holder().save();
}
}
}
@@ -111,22 +111,22 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override
public V get(int index) {
return getNotNull().get(index);
return resolve().get(index);
}
public @NotNull List<V> copy() {
return new ArrayList<>(getNotNull());
return new ArrayList<>(resolve());
}
public <T> @NotNull T handle(Function<List<V>, T> function) {
List<V> list = getNotNull();
List<V> list = resolve();
T result = function.apply(list);
set(list);
return result;
}
public @NotNull ConfiguredList<V> modify(Consumer<List<V>> consumer) {
List<V> list = getNotNull();
List<V> list = resolve();
consumer.accept(list);
set(list);
return this;
@@ -139,40 +139,40 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override
public int size() {
return getNotNull().size();
return resolve().size();
}
@Override
public boolean isEmpty() {
return getNotNull().isEmpty();
return resolve().isEmpty();
}
@Override
public boolean contains(Object o) {
return getNotNull().contains(o);
return resolve().contains(o);
}
@NotNull
@Override
public Iterator<V> iterator() {
return getNotNull().iterator();
return resolve().iterator();
}
@NotNull
@Override
public Object @NotNull [] toArray() {
return getNotNull().toArray();
return resolve().toArray();
}
@NotNull
@Override
public <T> T @NotNull [] toArray(@NotNull T[] a) {
return getNotNull().toArray(a);
return resolve().toArray(a);
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
return new HashSet<>(getNotNull()).containsAll(c);
return new HashSet<>(resolve()).containsAll(c);
}
@Override
@@ -223,30 +223,30 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override
public int indexOf(Object o) {
return getNotNull().indexOf(o);
return resolve().indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return getNotNull().lastIndexOf(o);
return resolve().lastIndexOf(o);
}
@NotNull
@Override
public ListIterator<V> listIterator() {
return getNotNull().listIterator();
return resolve().listIterator();
}
@NotNull
@Override
public ListIterator<V> listIterator(int index) {
return getNotNull().listIterator(index);
return resolve().listIterator(index);
}
@NotNull
@Override
public List<V> subList(int fromIndex, int toIndex) {
return getNotNull().subList(fromIndex, toIndex);
return resolve().subList(fromIndex, toIndex);
}
}
-57
View File
@@ -1,57 +0,0 @@
import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapter;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.test.config.TestSource;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.time.Duration;
import java.time.LocalTime;
import java.util.concurrent.ConcurrentHashMap;
public class AdaptTest {
@Test
public void test() throws Exception {
ValueAdapterRegistry registry = new ValueAdapterRegistry();
registry.register(PrimitiveAdapter.ADAPTERS);
registry.register(PrimitiveAdapter.ofEnum());
registry.register(ValueType.of(Long.class), ValueType.of(Duration.class), Duration::ofMillis, Duration::toMillis);
registry.register(
ValueType.of(Duration.class), ValueType.of(LocalTime.class),
duration -> LocalTime.now().plus(duration),
data -> Duration.between(LocalTime.now(), data)
);
ConfigurationHolder<TestSource> provider = new ConfigurationHolder<TestSource>(
registry, new ConfigurationOptionHolder(),
new ConcurrentHashMap<>(), new ConfigurationInitializer()
) {
final TestSource source = new TestSource(this, System.currentTimeMillis());
@Override
public @NotNull TestSource config() {
return source;
}
};
LocalTime v = registry.deserialize(provider, LocalTime.class, 600000L);
Object d = registry.serialize(provider, v);
System.out.println(v);
System.out.println(d);
System.out.println(registry.deserialize(provider, TestEnum.class, "C"));
System.out.println(registry.serialize(provider, TestEnum.C).getClass());
}
enum TestEnum {
A, b, C
}
}
@@ -1,55 +0,0 @@
package cc.carm.test.config;
import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.concurrent.ConcurrentHashMap;
public class LoaderTest {
@Test
public void test() throws Exception {
ConfigurationHolder<TestSource> provider = new ConfigurationHolder<TestSource>(
new ValueAdapterRegistry(), new ConfigurationOptionHolder(),
new ConcurrentHashMap<>(), new ConfigurationInitializer()
) {
final TestSource source = new TestSource(this, System.currentTimeMillis());
@Override
public @NotNull TestSource config() {
return source;
}
};
ConfigurationInitializer loader = new ConfigurationInitializer();
loader.initialize(provider, ROOT.class);
}
interface ROOT extends Configuration {
interface SUB extends Configuration {
}
@ConfigPath(root = true)
interface EXTERNAL extends Configuration {
}
@ConfigPath("NO")
interface YES extends Configuration {
}
}
}
@@ -1,57 +0,0 @@
package cc.carm.test.config;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class TestSection implements ConfigureSection {
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return null;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.emptyMap();
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
}
@Override
public boolean contains(@NotNull String path) {
return false;
}
@Override
public boolean isList(@NotNull String path) {
return false;
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return Collections.emptyList();
}
@Override
public boolean isSection(@NotNull String path) {
return false;
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
}
@@ -1,45 +0,0 @@
package cc.carm.test.config;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
public class TestSource extends ConfigureSource<TestSection, Map<String, String>, TestSource> {
public TestSource(@NotNull ConfigurationHolder<? extends TestSource> holder, long lastUpdateMillis) {
super(holder, lastUpdateMillis);
}
@Override
protected TestSource self() {
return this;
}
@Override
public void save() throws Exception {
}
@Override
protected void onReload() throws Exception {
}
@Override
public @NotNull Map<String, String> original() {
return null;
}
@Override
public @NotNull TestSection section() {
return null;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return null;
}
}