The type of the target value
*/
-public abstract class ValueAdapter {
+public abstract class ValueAdapter
+ implements ValueSerializer
, ValueDeserializer
{
protected final Class super B> baseType;
protected final Class super V> valueType;
@@ -28,10 +28,6 @@ public abstract class ValueAdapter
{
return valueType;
}
- public abstract B serialize(@NotNull P provider, @NotNull V value) throws Exception;
-
- public abstract V deserialize(@NotNull P provider, @NotNull Class extends V> clazz, @NotNull B data) throws Exception;
-
public boolean isAdaptedFrom(Class> clazz) {
return clazz.isAssignableFrom(valueType);
}
@@ -41,7 +37,7 @@ public abstract class ValueAdapter
{
}
public boolean isAdapterOf(Class> clazz) {
- return valueType.isAssignableFrom(clazz);
+ return clazz == valueType;
}
@SuppressWarnings("unchecked")
diff --git a/core/src/main/java/cc/carm/lib/configuration/adapter/ValueAdapterRegistry.java b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueAdapterRegistry.java
index 607aa0b..460e4ca 100644
--- a/core/src/main/java/cc/carm/lib/configuration/adapter/ValueAdapterRegistry.java
+++ b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueAdapterRegistry.java
@@ -3,20 +3,17 @@ package cc.carm.lib.configuration.adapter;
import cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapters;
import cc.carm.lib.configuration.core.function.ConfigDataFunction;
import cc.carm.lib.configuration.source.ConfigurationProvider;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class ValueAdapterRegistry
{
- protected final @NotNull P provider;
protected final Map, ValueAdapter> adapters = new HashMap<>();
- public ValueAdapterRegistry(@NotNull P provider) {
- this.provider = provider;
- }
-
public void register(@NotNull ValueAdapter
adapter) {
adapters.put(adapter.getValueClass(), adapter);
}
@@ -46,30 +43,31 @@ public class ValueAdapterRegistry
{
}
@SuppressWarnings("unchecked")
- public T deserialize(Class type, Object value) throws Exception {
- if (value == null) return null;
- if (type == Object.class) return type.cast(value);
+ @Contract("_,_,null -> null")
+ public T deserialize(@NotNull P provider, @NotNull Class type, @Nullable Object source) throws Exception {
+ if (source == null) return null; // Null check
+ if (type.isInstance(source)) return type.cast(source); // Not required to deserialize
ValueAdapter adapter = getAdapter(type);
if (adapter == null) throw new RuntimeException("No adapter for type " + type.getName());
- // CHECK IF VALUE IS ADAPTED FROM GIVEN VALUE'S TYPE
- if (adapter.isAdaptedFrom(value)) {
- return (T) adapter.deserializeObject(provider, type, value);
+ // Check if value is adapted from given value's type
+ if (adapter.isAdaptedFrom(source)) {
+ return (T) adapter.deserializeObject(provider, type, source);
}
- // OTHERWISE, WE NEED TO DESERIALIZE ONE BY ONE
- Object baseValue = deserialize(adapter.getBaseClass(), value);
+ // Otherwise, we need to deserialize one by one.
+ Object baseValue = deserialize(provider, adapter.getBaseClass(), source);
if (baseValue == null) return null; // Null check
return (T) adapter.deserializeObject(provider, type, baseValue);
}
- public Object serialize(T value) throws Exception {
- if (value == null) return null;
+ @Contract("_,null -> null")
+ public Object serialize(@NotNull P provider, @Nullable T value) throws Exception {
+ if (value == null) return null; // Null check
- Class> valueClass = value.getClass();
- ValueAdapter adapter = adapters.get(valueClass);
+ ValueAdapter
adapter = getAdapter(value.getClass());
if (adapter == null) return value; // No adapters, try to return the original value
if (adapter instanceof PrimitiveAdapters) {
@@ -79,12 +77,14 @@ public class ValueAdapterRegistry
{
}
// Otherwise, we need to serialize one by one.
- return serialize(adapter.serializeObject(provider, value));
+ return serialize(provider, adapter.serializeObject(provider, value));
}
public ValueAdapter
getAdapter(Class> clazz) {
- ValueAdapter
byClass = adapters.get(clazz);
- if (byClass != null) return byClass;
+ return adapters.getOrDefault(clazz, findAdapter(clazz));
+ }
+
+ public ValueAdapter
findAdapter(Class> clazz) {
return adapters.values().stream().filter(adapter -> adapter.isAdapterOf(clazz)).findFirst().orElse(null);
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/adapter/ValueDeserializer.java b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueDeserializer.java
new file mode 100644
index 0000000..381d315
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueDeserializer.java
@@ -0,0 +1,18 @@
+package cc.carm.lib.configuration.adapter;
+
+import cc.carm.lib.configuration.source.ConfigurationProvider;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Value deserializer, convert base data to target value.
+ *
+ * @param
Configuration provider
+ * @param The type of base data
+ * @param The type of target value
+ */
+@FunctionalInterface
+public interface ValueDeserializer {
+
+ V deserialize(@NotNull P provider, @NotNull Class extends V> clazz, @NotNull B data) throws Exception;
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/adapter/ValueSerializer.java b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueSerializer.java
new file mode 100644
index 0000000..3161bf2
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/adapter/ValueSerializer.java
@@ -0,0 +1,18 @@
+package cc.carm.lib.configuration.adapter;
+
+import cc.carm.lib.configuration.source.ConfigurationProvider;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Value serializer, convert target value to base data.
+ *
+ * @param
Configuration provider
+ * @param The type of base data
+ * @param The type of value
+ */
+@FunctionalInterface
+public interface ValueSerializer {
+
+ B serialize(@NotNull P provider, @NotNull V value) throws Exception;
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/adapter/strandard/EnumAdapter.java b/core/src/main/java/cc/carm/lib/configuration/adapter/strandard/EnumAdapter.java
index 40bd3c4..6f53595 100644
--- a/core/src/main/java/cc/carm/lib/configuration/adapter/strandard/EnumAdapter.java
+++ b/core/src/main/java/cc/carm/lib/configuration/adapter/strandard/EnumAdapter.java
@@ -21,4 +21,9 @@ public class EnumAdapter
extends ValueAdapter
clazz) {
+ return clazz.isEnum();
+ }
+
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/annotation/ConfigPath.java b/core/src/main/java/cc/carm/lib/configuration/annotation/ConfigPath.java
index 01061e3..252e6a4 100644
--- a/core/src/main/java/cc/carm/lib/configuration/annotation/ConfigPath.java
+++ b/core/src/main/java/cc/carm/lib/configuration/annotation/ConfigPath.java
@@ -1,7 +1,5 @@
package cc.carm.lib.configuration.annotation;
-import cc.carm.lib.configuration.core.ConfigInitializer;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -16,7 +14,7 @@ public @interface ConfigPath {
/**
* The path value of the current configuration.
- * If not set,will generate the path by {@link ConfigInitializer#getPathFromName(String)}.
+ * If not set,will generate the path by {@link cc.carm.lib.configuration.source.path.PathGenerator}.
*
* @return The path value of the current configuration
*/
diff --git a/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java b/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java
deleted file mode 100644
index 286b35e..0000000
--- a/core/src/main/java/cc/carm/lib/configuration/core/ConfigInitializer.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package cc.carm.lib.configuration.core;
-
-import cc.carm.lib.configuration.annotation.ConfigPath;
-import cc.carm.lib.configuration.annotation.HeaderComment;
-import cc.carm.lib.configuration.annotation.InlineComment;
-import cc.carm.lib.configuration.core.source.ConfigurationProvider;
-import cc.carm.lib.configuration.value.ConfigValue;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * 配置文件类初始化方法
- * 用于初始化 {@link Configuration} 中的每个 {@link ConfigValue} 对象
- *
- * @param {@link ConfigurationProvider} 配置文件的数据来源
- * @author CarmJos
- */
-public class ConfigInitializer> {
-
- protected final @NotNull T provider;
-
- public ConfigInitializer(@NotNull T provider) {
- this.provider = provider;
- }
-
- /**
- * 初始化指定类以及其子类的所有 {@link ConfigValue} 对象。
- *
- * @param clazz 配置文件类,须继承于 {@link Configuration} 。
- * @param saveDefaults 是否写入默认值(默认为 true)。
- */
- public void initialize(@NotNull Class extends Configuration> clazz, boolean saveDefaults) {
- initialize(clazz, saveDefaults, true);
- }
-
- /**
- * 初始化指定类的所有 {@link ConfigValue} 对象。
- *
- * @param clazz 配置文件类,须继承于 {@link Configuration} 。
- * @param saveDefaults 是否写入默认值(默认为 true)。
- * @param loadSubClasses 是否加载内部子类(默认为 true)。
- */
- public void initialize(@NotNull Class extends Configuration> clazz,
- boolean saveDefaults, boolean loadSubClasses) {
- initializeStaticClass(
- clazz, null, null,
- null, null, null,
- saveDefaults, loadSubClasses
- );
- if (saveDefaults) {
- try {
- provider.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- /**
- * 初始化指定实例的所有 {@link ConfigValue} 与内部 {@link Configuration} 对象。
- *
- * @param config 配置文件实例类,须实现 {@link Configuration} 。
- */
- public void initialize(@NotNull Configuration config) {
- initialize(config, true);
- }
-
- /**
- * 初始化指定实例的所有 {@link ConfigValue} 与内部 {@link Configuration} 对象。
- *
- * @param config 配置文件实例类,须实现 {@link Configuration} 。
- * @param saveDefaults 是否写入默认值(默认为 true)。
- */
- public void initialize(@NotNull Configuration config, boolean saveDefaults) {
- initializeInstance(
- config, null, null,
- null, null, null,
- saveDefaults
- );
- if (saveDefaults) {
- try {
- provider.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
-
- // 针对实例类的初始化方法
- private void initializeInstance(@NotNull Configuration root,
- @Nullable String parentPath, @Nullable String fieldName,
- @Nullable ConfigPath fieldPath,
- @Nullable HeaderComment fieldHeaderComments,
- @Nullable InlineComment fieldInlineComments,
- boolean saveDefaults) {
- String path = getClassPath(root.getClass(), parentPath, fieldName, fieldPath);
- this.provider.setHeaderComment(path, getClassHeaderComments(root.getClass(), fieldHeaderComments));
- if (path != null) this.provider.setInlineComment(path, readInlineComments(fieldInlineComments));
-
- for (Field field : root.getClass().getDeclaredFields()) {
- initializeField(root, field, path, saveDefaults, false);
- }
- }
-
- // 针对静态类的初始化方法
- private void initializeStaticClass(@NotNull Class> clazz,
- @Nullable String parentPath, @Nullable String fieldName,
- @Nullable ConfigPath fieldPath,
- @Nullable HeaderComment fieldHeaderComments,
- @Nullable InlineComment fieldInlineComments,
- boolean saveDefaults, boolean loadSubClasses) {
- if (!Configuration.class.isAssignableFrom(clazz)) return; // 只解析继承了 ConfigurationRoot 的类
- String path = getClassPath(clazz, parentPath, fieldName, fieldPath);
- this.provider.setHeaderComment(path, getClassHeaderComments(clazz, fieldHeaderComments));
- if (path != null) this.provider.setInlineComment(path, readInlineComments(fieldInlineComments));
-
- for (Field field : clazz.getDeclaredFields()) {
- initializeField(clazz, field, path, saveDefaults, loadSubClasses);
- }
-
- if (!loadSubClasses) return;
- Class>[] classes = clazz.getDeclaredClasses();
- for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
- initializeStaticClass(
- classes[i], path, classes[i].getSimpleName(),
- null, null, null,
- saveDefaults, true
- );
- }
- }
-
- private void initializeField(@NotNull Object source, @NotNull Field field,
- @Nullable String parent, boolean saveDefaults, boolean loadSubClasses) {
- try {
- field.setAccessible(true);
- Object object = field.get(source);
-
- if (object instanceof ConfigValue>) {
- initializeValue(
- (ConfigValue>) object, getFieldPath(field, parent),
- field.getAnnotation(HeaderComment.class),
- field.getAnnotation(InlineComment.class),
- saveDefaults
- );
- } else if (source instanceof Configuration && object instanceof Configuration) {
- // 当且仅当 源字段与字段 均为ConfigurationRoot实例时,才对目标字段进行下一步初始化加载。
- initializeInstance(
- (Configuration) object, parent, field.getName(),
- field.getAnnotation(ConfigPath.class),
- field.getAnnotation(HeaderComment.class),
- field.getAnnotation(InlineComment.class),
- saveDefaults
- );
- } else if (source instanceof Class> && object instanceof Class>) {
- // 当且仅当 源字段与字段 均为静态类时,才对目标字段进行下一步初始化加载。
- initializeStaticClass(
- (Class>) object, parent, field.getName(),
- field.getAnnotation(ConfigPath.class),
- field.getAnnotation(HeaderComment.class),
- field.getAnnotation(InlineComment.class),
- saveDefaults, loadSubClasses
- );
- }
-
- // 以上判断实现以下规范:
- // - 实例类中仅加载 ConfigValue实例 与 ConfigurationRoot实例
- // - 静态类中仅加载 静态ConfigValue实例 与 静态ConfigurationRoot类
-
- } catch (IllegalAccessException ignored) {
- }
- }
-
- protected void initializeValue(@NotNull ConfigValue> value, @NotNull String path,
- @Nullable HeaderComment fieldHeaderComment,
- @Nullable InlineComment fieldInlineComment,
- boolean saveDefaults) {
- value.initialize(
- provider, saveDefaults, path,
- readHeaderComments(fieldHeaderComment),
- readInlineComments(fieldInlineComment)
- );
- }
-
- protected static @Nullable List getClassHeaderComments(@NotNull Class> clazz,
- @Nullable HeaderComment fieldAnnotation) {
- List classComments = readHeaderComments(clazz.getAnnotation(HeaderComment.class));
- if (classComments != null) return classComments;
- else return readHeaderComments(fieldAnnotation);
- }
-
-
- protected static List readHeaderComments(@Nullable HeaderComment annotation) {
- if (annotation == null) return null;
- String[] value = annotation.value();
- return value.length > 0 ? Arrays.asList(value) : null;
- }
-
-
- protected static @Nullable String readInlineComments(@Nullable InlineComment annotation) {
- if (annotation == null) return null;
- String value = annotation.value();
- return value.length() > 0 ? value : null;
- }
-
- protected static @Nullable String getClassPath(@Nullable Class> clazz,
- @Nullable String parentPath,
- @Nullable String filedName,
- @Nullable ConfigPath fieldAnnotation) {
- @NotNull String parent = parentPath != null ? parentPath + "." : "";
- boolean fromRoot = false;
-
- // 先获取 Class 对应的路径注解 其优先度最高。
- if (clazz != null) {
- ConfigPath clazzAnnotation = clazz.getAnnotation(ConfigPath.class);
- if (clazzAnnotation != null) {
- fromRoot = clazzAnnotation.root();
- if (clazzAnnotation.value().length() > 0) {
- return (fromRoot ? "" : parent) + clazzAnnotation.value();
- }
- }
- }
-
- if (fieldAnnotation != null) {
- fromRoot = fromRoot || fieldAnnotation.root();
- if (fieldAnnotation.value().length() > 0) {
- return (fromRoot ? "" : parent) + fieldAnnotation.value();
- }
- }
-
- // 再由 fieldName 获取路径
- if (filedName != null) return (fromRoot ? "" : parent) + getPathFromName(filedName);
- else return null; // 不满足上述条件 且 无 fieldName,则说明是根路径。
- }
-
- protected static @NotNull String getFieldPath(@NotNull Field field, @Nullable String parentPath) {
- @NotNull String parent = parentPath != null ? parentPath + "." : "";
- boolean fromRoot = false;
-
- // 先获取 Field 对应的路径注解 其优先度最高。
- ConfigPath pathAnnotation = field.getAnnotation(ConfigPath.class);
- if (pathAnnotation != null) {
- fromRoot = pathAnnotation.root();
- if (pathAnnotation.value().length() > 0) {
- return (fromRoot ? "" : parent) + pathAnnotation.value();
- }
- }
-
- // 最后再通过 fieldName 自动生成路径
- return (fromRoot ? "" : parent) + getPathFromName(field.getName());
- }
-
- /**
- * 得到指定元素的配置名称。
- * 采用 全小写,以“-”链接 的命名规则。
- *
- * @param name 源名称
- * @return 全小写,以“-”链接 的 路径名称
- */
- public static String getPathFromName(String name) {
- return name.replaceAll("[A-Z]", "-$0") // 将驼峰转换为蛇形;
- .replaceAll("-(.*)", "$1") // 若首字母也为大写,则也会被转换,需要去掉第一个横线
- .replaceAll("_-([A-Z])", "_$1") // 因为命名中可能包含 _,因此需要被特殊处理一下
- .replaceAll("([a-z])-([A-Z])", "$1_$2") // 然后将非全大写命名的内容进行转换
- .replace("-", "") // 移除掉多余的横线
- .replace("_", "-") // 将下划线替换为横线
- .toLowerCase(); // 最后转为全小写
- }
-
-
-}
diff --git a/core/src/main/java/cc/carm/lib/configuration/core/Configuration.java b/core/src/main/java/cc/carm/lib/configuration/core/Configuration.java
index 1ba41b9..f925a20 100644
--- a/core/src/main/java/cc/carm/lib/configuration/core/Configuration.java
+++ b/core/src/main/java/cc/carm/lib/configuration/core/Configuration.java
@@ -5,4 +5,5 @@ package cc.carm.lib.configuration.core;
* which is used to label and record the configuration information.
*/
public interface Configuration {
+
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java
index 0252983..09de47b 100644
--- a/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java
+++ b/core/src/main/java/cc/carm/lib/configuration/core/source/ConfigurationProvider.java
@@ -1,7 +1,7 @@
package cc.carm.lib.configuration.core.source;
-import cc.carm.lib.configuration.core.ConfigInitializer;
import cc.carm.lib.configuration.core.Configuration;
+import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.impl.CachedConfigValue;
import org.jetbrains.annotations.NotNull;
diff --git a/core/src/main/java/cc/carm/lib/configuration/manifest/ValueManifest.java b/core/src/main/java/cc/carm/lib/configuration/manifest/ValueManifest.java
new file mode 100644
index 0000000..8ef68e2
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/manifest/ValueManifest.java
@@ -0,0 +1,8 @@
+package cc.carm.lib.configuration.manifest;
+
+public class ValueManifest {
+
+
+
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationBuilder.java b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationBuilder.java
index 597bbcc..188dae2 100644
--- a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationBuilder.java
+++ b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationBuilder.java
@@ -1,15 +1,85 @@
package cc.carm.lib.configuration.source;
+import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
+import cc.carm.lib.configuration.core.function.ConfigDataFunction;
+import cc.carm.lib.configuration.source.path.PathGenerator;
+import cc.carm.lib.easyoptions.OptionHolder;
+import cc.carm.lib.easyoptions.OptionType;
import org.jetbrains.annotations.NotNull;
-public abstract class ConfigurationBuilder {
+import java.util.function.Consumer;
+import java.util.function.Function;
- protected ConfigurationLoader loader;
- protected ValueAdapterRegistry
processors;
+public abstract class ConfigurationBuilder
, C> {
+ protected Function
> loaderFunction = ConfigurationLoader::new;
+ protected Consumer> loaderConsumer = loader -> {
+ };
- public abstract @NotNull ConfigurationProvider build();
+ protected ValueAdapterRegistry adapters = new ValueAdapterRegistry<>();
+ protected OptionHolder options = new OptionHolder();
+
+ public abstract C getThis();
+
+ public C loader(Function
> loaderFunction) {
+ this.loaderFunction = loaderFunction;
+ return getThis();
+ }
+
+ public C loader(ConfigurationLoader
loader) {
+ return loader(p -> loader);
+ }
+
+ public C loader(Consumer> loaderConsumer) {
+ this.loaderConsumer = this.loaderConsumer.andThen(loaderConsumer);
+ return getThis();
+ }
+
+ public C pathGenerator(PathGenerator pathGenerator) {
+ return loader(loader -> {
+ loader.setPathGenerator(pathGenerator);
+ });
+ }
+
+ public C adapters(ValueAdapterRegistry
adapters) {
+ this.adapters = adapters;
+ return getThis();
+ }
+
+ public C adapter(Consumer> adapterRegistryConsumer) {
+ adapterRegistryConsumer.accept(adapters);
+ return getThis();
+ }
+
+ public C adapter(@NotNull ValueAdapter adapter) {
+ return adapter(a -> a.register(adapter));
+ }
+
+ public C adapter(Class clazz, @NotNull ValueAdapter adapter) {
+ return adapter(a -> a.register(clazz, adapter));
+ }
+
+ public C adapter(Class baseClass, Class valueClass,
+ ConfigDataFunction parser, ConfigDataFunction serializer) {
+ return adapter(a -> a.register(baseClass, valueClass, parser, serializer));
+ }
+
+ public C options(OptionHolder options) {
+ this.options = options;
+ return getThis();
+ }
+
+ public C option(Consumer optionsConsumer) {
+ optionsConsumer.accept(options);
+ return getThis();
+ }
+
+ public C option(OptionType option, O value) {
+ return option(o -> o.set(option, value));
+ }
+
+ public abstract @NotNull P build();
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationLoader.java b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationLoader.java
index 79f0a5e..a56eae9 100644
--- a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationLoader.java
+++ b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationLoader.java
@@ -1,4 +1,196 @@
package cc.carm.lib.configuration.source;
-public interface ConfigurationLoader {
+import cc.carm.lib.configuration.annotation.ConfigPath;
+import cc.carm.lib.configuration.core.Configuration;
+import cc.carm.lib.configuration.source.path.PathGenerator;
+import cc.carm.lib.configuration.source.path.StandardPathGenerator;
+import cc.carm.lib.configuration.value.ConfigValue;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+/**
+ * Configuration loader,
+ * used to load configuration values from {@link cc.carm.lib.configuration.core.Configuration} classes.
+ */
+public class ConfigurationLoader> {
+
+ protected final P provider;
+ protected PathGenerator
pathGenerator;
+
+ public ConfigurationLoader(P provider) {
+ this(provider, StandardPathGenerator.of(provider));
+ }
+
+ public ConfigurationLoader(P provider, PathGenerator
pathGenerator) {
+ this.provider = provider;
+ this.pathGenerator = pathGenerator;
+ }
+
+ public void setPathGenerator(PathGenerator
pathGenerator) {
+ this.pathGenerator = pathGenerator;
+ }
+
+ public PathGenerator
getPathGenerator() {
+ return pathGenerator;
+ }
+
+ /**
+ * 初始化指定类以及其子类的所有 {@link ConfigValue} 对象。
+ *
+ * @param clazz 配置文件类,须继承于 {@link Configuration} 。
+ */
+ public void initialize(@NotNull P provider, @NotNull Class extends Configuration> clazz) {
+ initialize(clazz, saveDefaults, true);
+ }
+
+ /**
+ * 初始化指定类的所有 {@link ConfigValue} 对象。
+ *
+ * @param clazz 配置文件类,须继承于 {@link Configuration} 。
+ * @param saveDefaults 是否写入默认值(默认为 true)。
+ * @param loadSubClasses 是否加载内部子类(默认为 true)。
+ */
+ public void initialize(@NotNull Class extends Configuration> clazz) {
+ initializeStaticClass(
+ clazz, null, null,
+ null, null, null,
+ saveDefaults, loadSubClasses
+ );
+ if (saveDefaults) {
+ try {
+ provider.save();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 初始化指定实例的所有 {@link ConfigValue} 与内部 {@link Configuration} 对象。
+ *
+ * @param config 配置文件实例类,须实现 {@link Configuration} 。
+ */
+ public void initialize(@NotNull Configuration config) {
+ initialize(config, true);
+ }
+
+ /**
+ * 初始化指定实例的所有 {@link ConfigValue} 与内部 {@link Configuration} 对象。
+ *
+ * @param config 配置文件实例类,须实现 {@link Configuration} 。
+ * @param saveDefaults 是否写入默认值(默认为 true)。
+ */
+ public void initialize(@NotNull Configuration config, boolean saveDefaults) {
+ initializeInstance(
+ config, null, null,
+ null, null, null,
+ saveDefaults
+ );
+ if (saveDefaults) {
+ try {
+ provider.save();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ // 针对实例类的初始化方法
+ private void initializeInstance(@NotNull Configuration root,
+ @Nullable String parentPath, @Nullable String fieldName,
+ @Nullable ConfigPath fieldPath,
+ @Nullable HeaderComment fieldHeaderComments,
+ @Nullable InlineComment fieldInlineComments,
+ boolean saveDefaults) {
+ String path = getClassPath(root.getClass(), parentPath, fieldName, fieldPath);
+ this.provider.setHeaderComment(path, getClassHeaderComments(root.getClass(), fieldHeaderComments));
+ if (path != null) this.provider.setInlineComment(path, readInlineComments(fieldInlineComments));
+
+ for (Field field : root.getClass().getDeclaredFields()) {
+ initializeField(root, field, path, saveDefaults, false);
+ }
+ }
+
+ // 针对静态类的初始化方法
+ private void initializeStaticClass(@NotNull Class> clazz,
+ @Nullable String parentPath, @Nullable String fieldName,
+ @Nullable ConfigPath fieldPath,
+ @Nullable HeaderComment fieldHeaderComments,
+ @Nullable InlineComment fieldInlineComments,
+ boolean saveDefaults, boolean loadSubClasses) {
+ if (!Configuration.class.isAssignableFrom(clazz)) return; // 只解析继承了 ConfigurationRoot 的类
+ String path = getClassPath(clazz, parentPath, fieldName, fieldPath);
+ this.provider.setHeaderComment(path, getClassHeaderComments(clazz, fieldHeaderComments));
+ if (path != null) this.provider.setInlineComment(path, readInlineComments(fieldInlineComments));
+
+ for (Field field : clazz.getDeclaredFields()) {
+ initializeField(clazz, field, path, saveDefaults, loadSubClasses);
+ }
+
+ if (!loadSubClasses) return;
+ Class>[] classes = clazz.getDeclaredClasses();
+ for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
+ initializeStaticClass(
+ classes[i], path, classes[i].getSimpleName(),
+ null, null, null,
+ saveDefaults, true
+ );
+ }
+ }
+
+ private void initializeField(@NotNull Object source, @NotNull Field field,
+ @Nullable String parent, boolean saveDefaults, boolean loadSubClasses) {
+ try {
+ field.setAccessible(true);
+ Object object = field.get(source);
+
+ if (object instanceof ConfigValue>) {
+ initializeValue(
+ (ConfigValue>) object, getFieldPath(field, parent),
+ field.getAnnotation(HeaderComment.class),
+ field.getAnnotation(InlineComment.class),
+ saveDefaults
+ );
+ } else if (source instanceof Configuration && object instanceof Configuration) {
+ // 当且仅当 源字段与字段 均为ConfigurationRoot实例时,才对目标字段进行下一步初始化加载。
+ initializeInstance(
+ (Configuration) object, parent, field.getName(),
+ field.getAnnotation(ConfigPath.class),
+ field.getAnnotation(HeaderComment.class),
+ field.getAnnotation(InlineComment.class),
+ saveDefaults
+ );
+ } else if (source instanceof Class> && object instanceof Class>) {
+ // 当且仅当 源字段与字段 均为静态类时,才对目标字段进行下一步初始化加载。
+ initializeStaticClass(
+ (Class>) object, parent, field.getName(),
+ field.getAnnotation(ConfigPath.class),
+ field.getAnnotation(HeaderComment.class),
+ field.getAnnotation(InlineComment.class),
+ saveDefaults, loadSubClasses
+ );
+ }
+
+ // 以上判断实现以下规范:
+ // - 实例类中仅加载 ConfigValue实例 与 ConfigurationRoot实例
+ // - 静态类中仅加载 静态ConfigValue实例 与 静态ConfigurationRoot类
+
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+
+ protected void initializeValue(@NotNull ConfigValue> value, @NotNull String path,
+ @Nullable HeaderComment fieldHeaderComment,
+ @Nullable InlineComment fieldInlineComment,
+ boolean saveDefaults) {
+ value.initialize(
+ provider, saveDefaults, path,
+ readHeaderComments(fieldHeaderComment),
+ readInlineComments(fieldInlineComment)
+ );
+ }
+
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationProvider.java b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationProvider.java
index 89354f3..fb662ed 100644
--- a/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationProvider.java
+++ b/core/src/main/java/cc/carm/lib/configuration/source/ConfigurationProvider.java
@@ -1,6 +1,37 @@
package cc.carm.lib.configuration.source;
-public class ConfigurationProvider {
+import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
+import cc.carm.lib.configuration.core.Configuration;
+import cc.carm.lib.easyoptions.OptionHolder;
+import cc.carm.lib.easyoptions.OptionType;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class ConfigurationProvider
> {
+
+ protected @NotNull ConfigurationLoader
loader = new ConfigurationLoader<>();
+ protected @NotNull ValueAdapterRegistry
adapters = new ValueAdapterRegistry<>();
+ protected @NotNull OptionHolder options = new OptionHolder();
+
+
+ public OptionHolder options() {
+ return options;
+ }
+
+ public @NotNull T option(@NotNull OptionType option) {
+ return options.get(option);
+ }
+
+ public void option(@NotNull OptionType option, @NotNull T value) {
+ options.set(option, value);
+ }
+
+ public ConfigurationLoader loader() {
+ return loader;
+ }
+
+ public void load(Configuration configuration) {
+ loader().load(configuration);
+ }
}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/path/PathGenerator.java b/core/src/main/java/cc/carm/lib/configuration/source/path/PathGenerator.java
new file mode 100644
index 0000000..19d0a6d
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/source/path/PathGenerator.java
@@ -0,0 +1,33 @@
+package cc.carm.lib.configuration.source.path;
+
+import cc.carm.lib.configuration.source.ConfigurationProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+
+public interface PathGenerator
> {
+
+ @Nullable String getFieldPath(@Nullable String parentPath, @NotNull Field field);
+
+ @Nullable String getClassPath(@Nullable String parentPath,
+ @NotNull Class> clazz, @Nullable Field clazzField);
+
+ /**
+ * Get the configuration name of the specified element.
+ * Use the naming convention of all lowercase and "-" links.
+ *
+ * @param name source name
+ * @return the final path
+ */
+ static String covertPathName(String name) {
+ return name.replaceAll("[A-Z]", "-$0") // 将驼峰形转换为蛇形;
+ .replaceAll("-(.*)", "$1") // 若首字母也为大写,则也会被转换,需要去掉第一个横线
+ .replaceAll("_-([A-Z])", "_$1") // 因为命名中可能包含 _,因此需要被特殊处理一下
+ .replaceAll("([a-z])-([A-Z])", "$1_$2") // 然后将非全大写命名的内容进行转换
+ .replace("-", "") // 移除掉多余的横线
+ .replace("_", "-") // 将下划线替换为横线
+ .toLowerCase(); // 最后转为全小写
+ }
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/path/StandardPathGenerator.java b/core/src/main/java/cc/carm/lib/configuration/source/path/StandardPathGenerator.java
new file mode 100644
index 0000000..824db53
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/source/path/StandardPathGenerator.java
@@ -0,0 +1,74 @@
+package cc.carm.lib.configuration.source.path;
+
+import cc.carm.lib.configuration.annotation.ConfigPath;
+import cc.carm.lib.configuration.source.ConfigurationProvider;
+import cc.carm.lib.configuration.source.standard.ConfigurationOptions;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.function.UnaryOperator;
+
+public class StandardPathGenerator
> implements PathGenerator
{
+
+ public static > StandardPathGenerator of(T provider) {
+ return of(provider, PathGenerator::covertPathName);
+ }
+
+ public static > StandardPathGenerator of(T provider, UnaryOperator pathConverter) {
+ return new StandardPathGenerator<>(provider, pathConverter);
+ }
+
+ protected final P provider;
+ protected UnaryOperator pathConverter;
+
+ public StandardPathGenerator(P provider, UnaryOperator pathConverter) {
+ this.provider = provider;
+ this.pathConverter = pathConverter;
+ }
+
+ public @NotNull UnaryOperator getPathConverter() {
+ return pathConverter;
+ }
+
+ public void setPathConverter(UnaryOperator pathConverter) {
+ this.pathConverter = pathConverter;
+ }
+
+ public String covertPath(String name) {
+ return pathConverter.apply(name);
+ }
+
+ public char pathSeparator() {
+ return provider.option(ConfigurationOptions.PATH_SEPARATOR);
+ }
+
+ protected String link(@Nullable String parent, boolean root, @Nullable String path) {
+ if (path == null || path.isEmpty()) return root ? null : parent;
+ return root && parent != null ? covertPath(path) : parent + pathSeparator() + covertPath(path);
+ }
+
+ @Override
+ public @Nullable String getFieldPath(@Nullable String parentPath, @NotNull Field field) {
+ ConfigPath path = field.getAnnotation(ConfigPath.class);
+ if (path == null) return link(parentPath, false, field.getName()); // No annotation, use field name.
+ else return link(parentPath, path.root(), path.value().isEmpty() ? field.getName() : path.value());
+ }
+
+ @Override
+ public @Nullable String getClassPath(@Nullable String parentPath, @NotNull Class> clazz, @Nullable Field clazzField) {
+ // For standard path generator, we generate path following by:
+ // 1. Check if the class has a ConfigPath annotation, if so, use the root and value as the path.
+ // 2. If the class defined as a field, check if the field has a ConfigPath annotation,
+ // and use filed information.
+ ConfigPath clazzPath = clazz.getAnnotation(ConfigPath.class);
+
+ if (clazzPath != null) return link(parentPath, clazzPath.root(), clazzPath.value());
+ if (clazzField == null) return parentPath; // No field, return same as parent.
+
+ ConfigPath fieldPath = clazzField.getAnnotation(ConfigPath.class);
+ if (fieldPath == null) return link(parentPath, false, clazzField.getName());
+ else return getFieldPath(parentPath, clazzField);
+ }
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationMetaTypes.java b/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationMetaTypes.java
new file mode 100644
index 0000000..90e4499
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationMetaTypes.java
@@ -0,0 +1,12 @@
+package cc.carm.lib.configuration.source.standard;
+
+import cc.carm.lib.configuration.annotation.ConfigPath;
+import cc.carm.lib.easyannotation.AnnotatedMetaType;
+
+public interface ConfigurationMetaTypes {
+
+ AnnotatedMetaType PATH = AnnotatedMetaType.of(ConfigPath.class, ConfigPath::value);
+
+ AnnotatedMetaType ROOT = AnnotatedMetaType.of(ConfigPath.class, ConfigPath::root);
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationOptions.java b/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationOptions.java
new file mode 100644
index 0000000..88485b8
--- /dev/null
+++ b/core/src/main/java/cc/carm/lib/configuration/source/standard/ConfigurationOptions.java
@@ -0,0 +1,30 @@
+package cc.carm.lib.configuration.source.standard;
+
+
+import cc.carm.lib.easyoptions.OptionType;
+
+import static cc.carm.lib.easyoptions.OptionType.of;
+
+public interface ConfigurationOptions {
+
+ /**
+ * The configuration path separator.
+ */
+ OptionType PATH_SEPARATOR = of('.');
+
+ /**
+ * Whether to copy files from resource if exists.
+ */
+ OptionType COPY_DEFAULTS = of(true);
+
+ /**
+ * Whether to save default values if offered and not exists in configuration.
+ */
+ OptionType SAVE_DEFAULTS = of(true);
+
+ /**
+ * Whether to load subclasses of configuration class.
+ */
+ OptionType LOAD_SUB_CLASSES = of(true);
+
+}
diff --git a/core/src/main/java/cc/carm/lib/configuration/value/ValueManifest.java b/core/src/main/java/cc/carm/lib/configuration/value/ValueManifest.java
index 9dd4888..e9caa02 100644
--- a/core/src/main/java/cc/carm/lib/configuration/value/ValueManifest.java
+++ b/core/src/main/java/cc/carm/lib/configuration/value/ValueManifest.java
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.value;
-import cc.carm.lib.configuration.core.ConfigInitializer;
import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.configuration.core.source.ConfigurationWrapper;
import org.jetbrains.annotations.NotNull;
diff --git a/core/src/test/java/AdaptTest.java b/core/src/test/java/AdaptTest.java
index 35be94b..b0e8d0d 100644
--- a/core/src/test/java/AdaptTest.java
+++ b/core/src/test/java/AdaptTest.java
@@ -12,7 +12,7 @@ public class AdaptTest {
@Test
public void test() throws Exception {
- ValueAdapterRegistry> registry = new ValueAdapterRegistry<>(new ConfigurationProvider());
+ ValueAdapterRegistry registry = new ValueAdapterRegistry<>();
registry.register(Long.class, PrimitiveAdapters.ofLong());
registry.register(long.class, PrimitiveAdapters.ofLong());
registry.register(Integer.class, PrimitiveAdapters.ofInteger());
@@ -36,16 +36,19 @@ public class AdaptTest {
registry.register(
Duration.class, LocalTime.class,
duration -> LocalTime.now().plus(duration),
- data -> Duration.between(data, LocalTime.now())
+ data -> Duration.between(LocalTime.now(), data)
);
- LocalTime v = registry.deserialize(LocalTime.class, "600");
- Object d = registry.serialize(v);
+ ConfigurationProvider provider = new ConfigurationProvider();
+ LocalTime v = registry.deserialize(provider, LocalTime.class, "600");
+ Object d = registry.serialize(provider, v);
System.out.println(v);
System.out.println(d);
- System.out.println(registry.deserialize(TestEnum.class, "b"));
+ System.out.println(registry.deserialize(provider, TestEnum.class, "b"));
+ System.out.println(registry.serialize(provider, TestEnum.C).getClass());
+
}
enum TestEnum {
diff --git a/core/src/test/java/NameTest.java b/core/src/test/java/NameTest.java
index e3eaab8..7a45698 100644
--- a/core/src/test/java/NameTest.java
+++ b/core/src/test/java/NameTest.java
@@ -1,4 +1,3 @@
-import cc.carm.lib.configuration.core.ConfigInitializer;
import org.junit.Test;
public class NameTest {
diff --git a/demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java b/demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java
index feef0a4..6b3875e 100644
--- a/demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java
+++ b/demo/src/main/java/cc/carm/lib/configuration/demo/tests/conf/DemoConfiguration.java
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.demo.tests.conf;
-import cc.carm.lib.configuration.core.ConfigInitializer;
import cc.carm.lib.configuration.core.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.HeaderComment;
diff --git a/impl/hocon/src/main/java/cc/carm/lib/configuration/hocon/HOCONFileConfigProvider.java b/impl/hocon/src/main/java/cc/carm/lib/configuration/hocon/HOCONFileConfigProvider.java
index 9c44994..2f12e30 100644
--- a/impl/hocon/src/main/java/cc/carm/lib/configuration/hocon/HOCONFileConfigProvider.java
+++ b/impl/hocon/src/main/java/cc/carm/lib/configuration/hocon/HOCONFileConfigProvider.java
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.hocon;
-import cc.carm.lib.configuration.core.ConfigInitializer;
-import cc.carm.lib.configuration.core.source.ConfigurationComments;
+import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import cc.carm.lib.configuration.hocon.exception.HOCONGetValueException;
import cc.carm.lib.configuration.hocon.util.HOCONUtils;
diff --git a/impl/json/src/main/java/cc/carm/lib/configuration/json/JSONConfigProvider.java b/impl/json/src/main/java/cc/carm/lib/configuration/json/JSONConfigProvider.java
index f9df64e..bbf7c80 100644
--- a/impl/json/src/main/java/cc/carm/lib/configuration/json/JSONConfigProvider.java
+++ b/impl/json/src/main/java/cc/carm/lib/configuration/json/JSONConfigProvider.java
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.json;
-import cc.carm.lib.configuration.core.ConfigInitializer;
-import cc.carm.lib.configuration.core.source.ConfigurationComments;
+import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import com.google.gson.Gson;
diff --git a/impl/sql/src/main/java/cc/carm/lib/configuration/sql/SQLConfigProvider.java b/impl/sql/src/main/java/cc/carm/lib/configuration/sql/SQLConfigProvider.java
index 453f54a..e896060 100644
--- a/impl/sql/src/main/java/cc/carm/lib/configuration/sql/SQLConfigProvider.java
+++ b/impl/sql/src/main/java/cc/carm/lib/configuration/sql/SQLConfigProvider.java
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.sql;
-import cc.carm.lib.configuration.core.ConfigInitializer;
-import cc.carm.lib.configuration.core.source.ConfigurationComments;
+import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLQuery;
diff --git a/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YAMLConfigProvider.java b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YAMLConfigProvider.java
index d69b3aa..9815444 100644
--- a/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YAMLConfigProvider.java
+++ b/impl/yaml/src/main/java/cc/carm/lib/configuration/yaml/YAMLConfigProvider.java
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.yaml;
-import cc.carm.lib.configuration.core.ConfigInitializer;
-import cc.carm.lib.configuration.core.source.ConfigurationComments;
+import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import cc.carm.lib.yamlcommentupdater.CommentedYAML;
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
@@ -26,6 +25,7 @@ public class YAMLConfigProvider extends FileConfigProvider i
}
public void initializeConfig() {
+ ConfigurationOptions
this.configuration = YamlConfiguration.loadConfiguration(file);
this.initializer = new ConfigInitializer<>(this);
}
diff --git a/pom.xml b/pom.xml
index d3de07a..55f7c72 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
impl/json
impl/sql
impl/hocon
+ commentable
EasyConfiguration