1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 18:48:20 +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.lang.annotation.Annotation;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
public abstract class ConfigurationFactory< public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>, SOURCE extends ConfigureSource<?, ?, SOURCE>,
@@ -31,8 +32,7 @@ public abstract class ConfigurationFactory<
this.adapters.register(StandardAdapters.SECTION_ADAPTER); this.adapters.register(StandardAdapters.SECTION_ADAPTER);
} }
public abstract SELF self(); protected abstract SELF self();
public SELF adapters(ValueAdapterRegistry adapters) { public SELF adapters(ValueAdapterRegistry adapters) {
this.adapters = adapters; this.adapters = adapters;
@@ -81,13 +81,25 @@ public abstract class ConfigurationFactory<
return self(); return self();
} }
public SELF option(Consumer<ConfigurationOptionHolder> optionsConsumer) { public SELF option(Consumer<ConfigurationOptionHolder> modifier) {
optionsConsumer.accept(options); modifier.accept(options);
return self(); return self();
} }
public <O> SELF option(ConfigurationOption<O> option, O value) { public <O> SELF option(ConfigurationOption<O> type, O value) {
return option(o -> o.set(option, 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); this.classInitializer = this.classInitializer.andThen(classInitializer);
} }
public <T, A extends Annotation> void registerAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @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));
});
appendClassInitializer((holder, path, clazz) -> { appendClassInitializer((holder, path, clazz) -> {
A data = clazz.getAnnotation(annotation); A data = clazz.getAnnotation(annotation);
if (data == null) return; 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) { public @Nullable String getFieldPath(@NotNull ConfigurationHolder<?> holder, @Nullable String parentPath, @NotNull Field field) {
return pathGenerator.getFieldPath(holder, parentPath, field); return pathGenerator.getFieldPath(holder, parentPath, field);
@@ -54,13 +54,14 @@ public class PathGenerator {
ConfigPath clazzPath = clazz.getAnnotation(ConfigPath.class); ConfigPath clazzPath = clazz.getAnnotation(ConfigPath.class);
if (clazzPath != null) return link(holder, parentPath, clazzPath.root(), clazzPath.value()); if (clazzPath != null) return link(holder, parentPath, clazzPath.root(), clazzPath.value());
if (clazzField == null) { if (clazzField != null) {
return link(holder, parentPath, false, parentPath == null ? null : covertPath(clazz.getSimpleName())); // No field, use class name. 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); return link(holder, parentPath, false, covertPath(clazz.getSimpleName())); // No field, use class name.
if (fieldPath == null) return link(holder, parentPath, false, covertPath(clazzField.getName()));
else return getFieldPath(holder, parentPath, clazzField);
} }
protected String select(String path, String defaultValue) { protected String select(String path, String defaultValue) {
@@ -71,7 +72,7 @@ public class PathGenerator {
protected @Nullable String link(@NotNull ConfigurationHolder<?> holder, protected @Nullable String link(@NotNull ConfigurationHolder<?> holder,
@Nullable String parent, boolean root, @Nullable String path) { @Nullable String parent, boolean root, @Nullable String path) {
if (path == null || path.isEmpty()) return root ? null : parent; 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) { public static boolean isBlank(String path) {
@@ -2,6 +2,8 @@ package cc.carm.lib.configuration.source.option;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class ConfigurationOption<V> { public class ConfigurationOption<V> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -13,6 +15,10 @@ public class ConfigurationOption<V> {
return new ConfigurationOption<>(valueClazz, defaultValue); 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 final @NotNull Class<V> valueClazz;
private @NotNull V defaultValue; 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 return null; // Source also represents the root section, so it has no parent
} }
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return self();
}
@Override @Override
public @NotNull Map<String, Object> getValues(boolean deep) { public @NotNull Map<String, Object> getValues(boolean deep) {
return section().getValues(deep); return section().getValues(deep);
@@ -7,6 +7,32 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; 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> { public abstract class ConfigValue<T> extends ValueManifest<T> {
protected ConfigValue(@NotNull ValueManifest<T> manifest) { protected ConfigValue(@NotNull ValueManifest<T> manifest) {
@@ -14,57 +40,77 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
} }
/** /**
* 得到该配置的设定值(即读取到的值)。 * Gets the configured value (i.e., the value read from the source).
* <br> 若初始化时未写入默认值,则可以通过 {@link #getOrDefault()} 方法在该设定值为空时获取默认值。 * <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(); 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() { public T getOrDefault() {
return optional().orElse(defaults()); return optional().orElse(defaults());
} }
/** /**
* 得到该配置的非空值。 * Gets the non-null value of this configuration.
* *
* @return 非空值 * @return Non-null value
* @throws NullPointerException 对应数据为空时抛出 * @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."); 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() { public @NotNull Optional<@Nullable T> optional() {
return Optional.ofNullable(get()); return Optional.ofNullable(get());
} }
/** /**
* 设定该配置的值。 * Sets the value of this configuration.
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。 * <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); public abstract void set(@Nullable T value);
/** /**
* 初始化该配置的默认值。 * Initializes the default value for this configuration.
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。 * <br> After setting, the configuration file will NOT be saved automatically.
* To save, call {@link ConfigurationHolder#save()}.
*/ */
public void setDefault() { public void setDefault() {
setDefault(false); setDefault(false);
} }
/** /**
* 将该配置的值设置为默认值。 * Sets the configuration value to its default.
* <br> 设定后,不会自动保存配置文件;若需要保存,请调用 {@link ConfigurationHolder#save()} 方法。 * <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) { public void setDefault(boolean override) {
if (!override && config().contains(path())) return; 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() { public boolean isDefault() {
return Objects.equals(defaults(), get()); return Objects.equals(defaults(), get());
@@ -86,7 +132,7 @@ public abstract class ConfigValue<T> extends ValueManifest<T> {
* it is recommended to call {@link ConfigurationHolder#save()} * it is recommended to call {@link ConfigurationHolder#save()}
* after all modifications are completed instead of this. * after all modifications are completed instead of this.
* *
* @throws Exception * @throws Exception Thrown when an error occurs during saving
*/ */
public void save() throws Exception { public void save() throws Exception {
holder().save(); holder().save();
@@ -111,22 +111,22 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override @Override
public V get(int index) { public V get(int index) {
return getNotNull().get(index); return resolve().get(index);
} }
public @NotNull List<V> copy() { public @NotNull List<V> copy() {
return new ArrayList<>(getNotNull()); return new ArrayList<>(resolve());
} }
public <T> @NotNull T handle(Function<List<V>, T> function) { public <T> @NotNull T handle(Function<List<V>, T> function) {
List<V> list = getNotNull(); List<V> list = resolve();
T result = function.apply(list); T result = function.apply(list);
set(list); set(list);
return result; return result;
} }
public @NotNull ConfiguredList<V> modify(Consumer<List<V>> consumer) { public @NotNull ConfiguredList<V> modify(Consumer<List<V>> consumer) {
List<V> list = getNotNull(); List<V> list = resolve();
consumer.accept(list); consumer.accept(list);
set(list); set(list);
return this; return this;
@@ -139,40 +139,40 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override @Override
public int size() { public int size() {
return getNotNull().size(); return resolve().size();
} }
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return getNotNull().isEmpty(); return resolve().isEmpty();
} }
@Override @Override
public boolean contains(Object o) { public boolean contains(Object o) {
return getNotNull().contains(o); return resolve().contains(o);
} }
@NotNull @NotNull
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return getNotNull().iterator(); return resolve().iterator();
} }
@NotNull @NotNull
@Override @Override
public Object @NotNull [] toArray() { public Object @NotNull [] toArray() {
return getNotNull().toArray(); return resolve().toArray();
} }
@NotNull @NotNull
@Override @Override
public <T> T @NotNull [] toArray(@NotNull T[] a) { public <T> T @NotNull [] toArray(@NotNull T[] a) {
return getNotNull().toArray(a); return resolve().toArray(a);
} }
@Override @Override
public boolean containsAll(@NotNull Collection<?> c) { public boolean containsAll(@NotNull Collection<?> c) {
return new HashSet<>(getNotNull()).containsAll(c); return new HashSet<>(resolve()).containsAll(c);
} }
@Override @Override
@@ -223,30 +223,30 @@ public class ConfiguredList<V> extends CachedConfigValue<List<V>> implements Lis
@Override @Override
public int indexOf(Object o) { public int indexOf(Object o) {
return getNotNull().indexOf(o); return resolve().indexOf(o);
} }
@Override @Override
public int lastIndexOf(Object o) { public int lastIndexOf(Object o) {
return getNotNull().lastIndexOf(o); return resolve().lastIndexOf(o);
} }
@NotNull @NotNull
@Override @Override
public ListIterator<V> listIterator() { public ListIterator<V> listIterator() {
return getNotNull().listIterator(); return resolve().listIterator();
} }
@NotNull @NotNull
@Override @Override
public ListIterator<V> listIterator(int index) { public ListIterator<V> listIterator(int index) {
return getNotNull().listIterator(index); return resolve().listIterator(index);
} }
@NotNull @NotNull
@Override @Override
public List<V> subList(int fromIndex, int toIndex) { 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;
}
}
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST); provider.initialize(TEST);
System.out.println("> Test Inner value before:"); System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.getNotNull()); System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
double after = Math.random() * 200D; double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after); System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after); TEST.INSTANCE.INNER_VALUE.set(after);
System.out.println("> Test Inner value after:"); System.out.println("> Test Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.getNotNull()); System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
} }
@@ -1,5 +1,6 @@
package cc.carm.lib.configuration.source.file; package cc.carm.lib.configuration.source.file;
import cc.carm.lib.configuration.function.DataConsumer;
import cc.carm.lib.configuration.function.DataFunction; import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.option.FileConfigOptions; import cc.carm.lib.configuration.source.option.FileConfigOptions;
@@ -14,7 +15,6 @@ import java.net.URLConnection;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
public abstract class FileConfigSource<SECTION extends ConfigureSection, ORIGINAL, SELF extends FileConfigSource<SECTION, ORIGINAL, SELF>> public abstract class FileConfigSource<SECTION extends ConfigureSection, ORIGINAL, SELF extends FileConfigSource<SECTION, ORIGINAL, SELF>>
extends ConfigureSource<SECTION, ORIGINAL, SELF> { extends ConfigureSource<SECTION, ORIGINAL, SELF> {
@@ -38,7 +38,6 @@ public abstract class FileConfigSource<SECTION extends ConfigureSection, ORIGINA
return holder().options().get(FileConfigOptions.COPY_DEFAULTS); return holder().options().get(FileConfigOptions.COPY_DEFAULTS);
} }
public void initializeFile() throws IOException { public void initializeFile() throws IOException {
if (this.file.exists()) return; if (this.file.exists()) return;
@@ -74,13 +73,41 @@ public abstract class FileConfigSource<SECTION extends ConfigureSection, ORIGINA
} }
} }
protected void fileOutputStream(@NotNull Consumer<OutputStream> stream) throws Exception { protected <R> R fileReadString(@NotNull DataFunction<String, R> loader) throws Exception {
try (InputStream is = Files.newInputStream(file.toPath())) {
try (Reader r = new InputStreamReader(is, charset())) {
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
int len;
while ((len = r.read(buf)) > 0) {
sb.append(buf, 0, len);
}
return loader.handle(sb.toString());
}
}
}
protected void fileReadString(@NotNull DataConsumer<String> loader) throws Exception {
try (InputStream is = Files.newInputStream(file.toPath())) {
try (Reader r = new InputStreamReader(is, charset())) {
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
int len;
while ((len = r.read(buf)) > 0) {
sb.append(buf, 0, len);
}
loader.accept(sb.toString());
}
}
}
protected void fileOutputStream(@NotNull DataConsumer<OutputStream> stream) throws Exception {
try (OutputStream os = Files.newOutputStream(file.toPath())) { try (OutputStream os = Files.newOutputStream(file.toPath())) {
stream.accept(os); stream.accept(os);
} }
} }
protected void fileWriter(@NotNull Consumer<Writer> writer) throws Exception { protected void fileWriter(@NotNull DataConsumer<Writer> writer) throws Exception {
try (OutputStream os = Files.newOutputStream(file.toPath())) { try (OutputStream os = Files.newOutputStream(file.toPath())) {
try (Writer w = new OutputStreamWriter(os, charset())) { try (Writer w = new OutputStreamWriter(os, charset())) {
writer.accept(w); writer.accept(w);
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId>
<version>3.9.1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<properties>
<maven.compiler.source>${project.jdk.version}</maven.compiler.source>
<maven.compiler.target>${project.jdk.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<artifactId>easyconfiguration-feature-section</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,34 +1,39 @@
package cc.carm.lib.configuration.source.json; package cc.carm.lib.configuration.source.section;
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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
public class JSONSection implements ConfigureSection { public class MemorySection implements ConfigureSection {
protected final @NotNull ConfigureSource<? extends JSONSection, ?, ?> source; public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source) {
return new MemorySection(source, new LinkedHashMap<>(), null);
}
public static @NotNull MemorySection root(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@Nullable Map<?, ?> data) {
return new MemorySection(source, data == null ? new LinkedHashMap<>() : data, null);
}
protected final @NotNull ConfigureSource<? extends MemorySection, ?, ?> source;
protected final @NotNull Map<String, Object> data; protected final @NotNull Map<String, Object> data;
protected final @Nullable JSONSection parent; protected final @Nullable MemorySection parent;
public JSONSection(@NotNull ConfigureSource<? extends JSONSection, ?, ?> source, public MemorySection(@NotNull ConfigureSource<? extends MemorySection, ?, ?> source,
@NotNull Map<?, ?> data, @Nullable JSONSection parent) { @NotNull Map<?, ?> data, @Nullable MemorySection parent) {
this.source = source; this.source = source;
this.parent = parent; this.parent = parent;
this.data = new LinkedHashMap<>(); this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : data.entrySet()) { for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString(); String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) { if (entry.getValue() instanceof Map) {
this.data.put(key, new JSONSection(source, (Map<?, ?>) entry.getValue(), this)); this.data.put(key, createChild((Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) { } else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) { for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) { if (obj instanceof Map) {
list.add(new JSONSection(source, (Map<?, ?>) obj, this)); list.add(createChild((Map<?, ?>) obj));
} else { } else {
list.add(obj); list.add(obj);
} }
@@ -40,8 +45,16 @@ public class JSONSection implements ConfigureSection {
} }
} }
protected @NotNull MemorySection createChild(@NotNull Map<?, ?> data) {
return new MemorySection(source(), data, this);
}
protected @NotNull MemorySection createChild() {
return createChild(new LinkedHashMap<>());
}
@Override @Override
public @NotNull ConfigureSource<? extends JSONSection, ?, ?> source() { public @NotNull ConfigureSource<? extends MemorySection, ?, ?> source() {
return this.source; return this.source;
} }
@@ -49,7 +62,7 @@ public class JSONSection implements ConfigureSection {
return this.data; return this.data;
} }
public @Nullable JSONSection parent() { public @Nullable MemorySection parent() {
return this.parent; return this.parent;
} }
@@ -64,11 +77,9 @@ public class JSONSection implements ConfigureSection {
@Override @Override
public void set(@NotNull String path, @Nullable Object value) { public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) { if (value instanceof Map) value = createChild((Map<?, ?>) value);
value = new JSONSection(source(), (Map<?, ?>) value, this);
}
JSONSection section = getSectionFor(path); MemorySection section = getSectionFor(path);
if (section == this) { if (section == this) {
if (value == null) { if (value == null) {
this.data.remove(path); this.data.remove(path);
@@ -87,7 +98,7 @@ public class JSONSection implements ConfigureSection {
@Override @Override
public @Nullable Object get(@NotNull String path) { public @Nullable Object get(@NotNull String path) {
JSONSection section = getSectionFor(path); MemorySection section = getSectionFor(path);
return section == this ? data.get(path) : section.get(getChild(path)); return section == this ? data.get(path) : section.get(getChild(path));
} }
@@ -104,7 +115,7 @@ public class JSONSection implements ConfigureSection {
@Override @Override
public boolean isSection(@NotNull String path) { public boolean isSection(@NotNull String path) {
return get(path) instanceof JSONSection; return get(path) instanceof ConfigureSection;
} }
@Override @Override
@@ -113,18 +124,18 @@ public class JSONSection implements ConfigureSection {
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null; return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
} }
private JSONSection getSectionFor(String path) { private MemorySection getSectionFor(String path) {
int index = path.indexOf(separator()); int index = path.indexOf(separator());
if (index == -1) return this; if (index == -1) return this;
String root = path.substring(0, index); String root = path.substring(0, index);
Object section = this.data.get(root); Object section = this.data.get(root);
if (section == null) { if (section == null) {
section = new JSONSection(source(), new LinkedHashMap<>(), this); section = createChild();
this.data.put(root, section); this.data.put(root, section);
} }
return (JSONSection) section; return (MemorySection) section;
} }
private String getChild(String path) { private String getChild(String path) {
@@ -140,16 +151,15 @@ public class JSONSection implements ConfigureSection {
* @param section The section to map the values from * @param section The section to map the values from
* @param parent The parent path * @param parent The parent path
* @param deep If the mapping should be deep * @param deep If the mapping should be deep
* @author md_5, sk89q
*/ */
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull JSONSection section, protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull MemorySection section,
@Nullable String parent, boolean deep) { @Nullable String parent, boolean deep) {
for (Map.Entry<String, Object> entry : section.data().entrySet()) { for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + separator()) + entry.getKey(); String path = (parent == null ? "" : parent + separator()) + entry.getKey();
output.remove(path); output.remove(path);
output.put(path, entry.getValue()); output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof JSONSection) { if (deep && entry.getValue() instanceof MemorySection) {
this.mapChildrenValues(output, (JSONSection) entry.getValue(), path, true); this.mapChildrenValues(output, (MemorySection) entry.getValue(), path, true);
} }
} }
} }
@@ -19,7 +19,7 @@ public interface VersionedMetaTypes {
} }
static void register(@NotNull ConfigurationInitializer initializer) { static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value); initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value);
} }
} }
+2 -1
View File
@@ -18,8 +18,9 @@
<version>3.9.1</version> <version>3.9.1</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>features/commentable</module> <module>features/section</module>
<module>features/file</module> <module>features/file</module>
<module>features/commentable</module>
<module>features/versioned</module> <module>features/versioned</module>
<module>providers/yaml</module> <module>providers/yaml</module>
+7
View File
@@ -34,6 +34,13 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId> <artifactId>easyconfiguration-demo</artifactId>
@@ -28,7 +28,7 @@ public class JSONConfigFactory extends FileConfigFactory<JSONSource, Configurati
} }
@Override @Override
public JSONConfigFactory self() { protected JSONConfigFactory self() {
return this; return this;
} }
@@ -37,10 +37,6 @@ public class JSONConfigFactory extends FileConfigFactory<JSONSource, Configurati
return self(); return self();
} }
public JSONConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public JSONConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) { public JSONConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> { return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
@@ -49,6 +45,9 @@ public class JSONConfigFactory extends FileConfigFactory<JSONSource, Configurati
}); });
} }
public JSONConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
@Override @Override
public @NotNull ConfigurationHolder<JSONSource> build() { public @NotNull ConfigurationHolder<JSONSource> build() {
@@ -59,7 +58,7 @@ public class JSONConfigFactory extends FileConfigFactory<JSONSource, Configurati
String sourcePath = this.resourcePath; String sourcePath = this.resourcePath;
return new ConfigurationHolder<JSONSource>(this.adapters, this.options, new ConcurrentHashMap<>(), this.initializer) { return new ConfigurationHolder<JSONSource>(this.adapters, this.options, new ConcurrentHashMap<>(), this.initializer) {
final JSONSource source = new JSONSource(this, 0, configFile, sourcePath, gson); final JSONSource source = new JSONSource(this, configFile, sourcePath, gson);
@Override @Override
public @NotNull JSONSource config() { public @NotNull JSONSource config() {
@@ -2,36 +2,38 @@ package cc.carm.lib.configuration.source.json;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.file.FileConfigSource; import cc.carm.lib.configuration.source.file.FileConfigSource;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.MemorySection;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.File;
import java.util.*; import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class JSONSource extends FileConfigSource<JSONSection, Map<?, ?>, JSONSource> { public class JSONSource extends FileConfigSource<MemorySection, Map<?, ?>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder() public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting() .serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter( .registerTypeAdapter(
JSONSection.class, MemorySection.class,
(JsonSerializer<JSONSection>) (src, typeOfSrc, context) -> context.serialize(src.data) (JsonSerializer<MemorySection>) (src, t, c) -> c.serialize(src.data())
).create(); ).create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected @Nullable JSONSection rootSection; protected @Nullable MemorySection rootSection;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis, protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath) { @NotNull File file, @Nullable String resourcePath) {
this(holder, lastUpdateMillis, file, resourcePath, DEFAULT_GSON); this(holder, file, resourcePath, DEFAULT_GSON);
} }
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis, protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder,
@NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) { @NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) {
super(holder, lastUpdateMillis, file, resourcePath); super(holder, 0, file, resourcePath);
this.gson = gson; this.gson = gson;
initialize(); initialize();
} }
@@ -50,19 +52,14 @@ public class JSONSource extends FileConfigSource<JSONSection, Map<?, ?>, JSONSou
return this; return this;
} }
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return this;
}
@Override @Override
public @NotNull Map<?, ?> original() { public @NotNull Map<?, ?> original() {
return section().data(); return section().data();
} }
@Override @Override
public @NotNull JSONSection section() { public @NotNull MemorySection section() {
return Objects.requireNonNull(this.rootSection, "Section is not initialized"); return Objects.requireNonNull(this.rootSection, "Root section is not initialized");
} }
@Override @Override
@@ -70,11 +67,15 @@ public class JSONSource extends FileConfigSource<JSONSection, Map<?, ?>, JSONSou
fileWriter(writer -> gson.toJson(original(), writer)); fileWriter(writer -> gson.toJson(original(), writer));
} }
public @NotNull String saveToString() {
return gson.toJson(original());
}
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class)); this.rootSection = MemorySection.root(
if (data == null) data = new LinkedHashMap<>(); this, fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class))
this.rootSection = new JSONSection(this, data, null); );
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间 this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
} }
} }
+14
View File
@@ -42,6 +42,20 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
@@ -1,76 +0,0 @@
package cc.carm.lib.configuration.source.yaml;
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 org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class YAMLSection implements ConfigureSection {
protected final @NotNull ConfigureSource<? extends YAMLSection, ?, ?> source;
protected final @NotNull Map<String, Object> data;
protected final @Nullable YAMLSection parent;
public YAMLSection(@NotNull ConfigureSource<? extends YAMLSection, ?, ?> source,
@NotNull Map<String, Object> data, @Nullable YAMLSection parent) {
this.source = source;
this.data = data;
this.parent = parent;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return this.source;
}
@Override
public @Nullable YAMLSection parent() {
return this.parent;
}
@Override
public @NotNull @UnmodifiableView 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 YAMLSection getSection(@NotNull String path) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
}
@@ -1,15 +0,0 @@
package sample;
public class Sample {
public static void main(String[] args) {
// 1. Make a configuration provider from a file.
ConfigurationProvider<?> provider = EasyConfiguration.from("config.yml");
// 2. Initialize the configuration classes or instances.
provider.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.getNotNull() + " !");
}
}
@@ -1,6 +1,6 @@
package sample; package sample;
import cc.carm.lib.configuration.source.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.HeaderComment; import cc.carm.lib.configuration.annotation.HeaderComment;
import cc.carm.lib.configuration.annotation.InlineComment; import cc.carm.lib.configuration.annotation.InlineComment;
@@ -9,6 +9,7 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import java.util.UUID; import java.util.UUID;
@ConfigPath(root = true)
@HeaderComment("Configurations for sample") @HeaderComment("Configurations for sample")
public interface SampleConfig extends Configuration { public interface SampleConfig extends Configuration {
@@ -16,12 +17,13 @@ public interface SampleConfig extends Configuration {
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true); ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString() ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
.parseValue(UUID::fromString).serializeValue(UUID::toString) .parse(UUID::fromString).serialize(UUID::toString)
.defaults( .defaults(
UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("00000000-0000-0000-0000-000000000000"),
UUID.fromString("00000000-0000-0000-0000-000000000001") UUID.fromString("00000000-0000-0000-0000-000000000001")
).build(); ).build();
@ConfigPath("info") // Custom path
interface INFO extends Configuration { interface INFO extends Configuration {
@HeaderComment("Configure your name!") // Header comment @HeaderComment("Configure your name!") // Header comment
@@ -0,0 +1,24 @@
package sample;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
import org.junit.Test;
public class SampleTest {
@Test
public void test() {
// 1. Make a configuration provider from a file.
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/config.yml")
.resourcePath("configs/sample.yml")
.indent(4) // Optional: Set the indentation of the configuration file.
.build();
// 2. Initialize the configuration classes or instances.
holder.initialize(SampleConfig.class);
// 3. Enjoy using the configuration!
SampleConfig.ENABLED.set(false);
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
}
}