1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-05 02:58:20 +08:00

feat(loader): Refactor loaders and metadata.

This commit is contained in:
2024-01-16 23:25:21 +08:00
parent 2df33e3458
commit b912ea369c
33 changed files with 632 additions and 332 deletions
+29
View File
@@ -0,0 +1,29 @@
<?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>
</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-commentable</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>easyconfiguration-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,26 @@
package cc.carm.lib.configuration.commentable;
import cc.carm.lib.configuration.annotation.HeaderComment;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.easyannotation.AnnotatedMetaType;
import java.util.Arrays;
import java.util.List;
public interface CommentableMetaTypes {
/**
* Configuration's {@link HeaderComment}
*/
AnnotatedMetaType<HeaderComment, List<String>> HEADER_COMMENT = AnnotatedMetaType.of(
HeaderComment.class, h -> h.value().length == 0 ? null : Arrays.asList(h.value())
);
/**
* Configuration's {@link InlineComment}
*/
AnnotatedMetaType<InlineComment, String> INLINE_COMMENT = AnnotatedMetaType.of(
InlineComment.class, c -> c.value().isEmpty() ? null : c.value()
);
}
@@ -0,0 +1,26 @@
package cc.carm.lib.configuration.commentable;
import cc.carm.lib.easyoptions.OptionType;
import static cc.carm.lib.easyoptions.OptionType.of;
public interface CommentableOptions {
/**
* Whether to keep modified comments in configuration,
* that means we only set comments for values that are not exists in configuration.
*/
OptionType<Boolean> KEEP_COMMENTS = OptionType.of(true);
/**
* Whether to comment values name that are not exists in configuration and no default value offered.
* <br>If true, a value without default value is not exists in configuration, we will comment its name,
* <p>e.g. a value named "foo" without default value will be put as:
* <blockquote><pre>
* # Value comments
* # foo:
* </pre></blockquote>
*/
OptionType<Boolean> COMMENT_NO_DEFAULT = OptionType.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.core.source; package cc.carm.lib.configuration.commentable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -20,7 +20,6 @@ public class ConfigurationComments {
} }
public void setHeaderComments(@Nullable String path, @Nullable List<String> comments) { public void setHeaderComments(@Nullable String path, @Nullable List<String> comments) {
if (comments == null) { if (comments == null) {
getHeaderComments().remove(path); getHeaderComments().remove(path);
} else { } else {
@@ -28,7 +27,6 @@ public class ConfigurationComments {
} }
} }
public void setInlineComment(@NotNull String path, @Nullable String comment) { public void setInlineComment(@NotNull String path, @Nullable String comment) {
if (comment == null) { if (comment == null) {
getInlineComments().remove(path); getInlineComments().remove(path);
@@ -47,5 +45,4 @@ public class ConfigurationComments {
return getInlineComments().get(path); return getInlineComments().get(path);
} }
} }
+13
View File
@@ -18,6 +18,19 @@
<artifactId>easyconfiguration-core</artifactId> <artifactId>easyconfiguration-core</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyoptions</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyannotation</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.adapter; package cc.carm.lib.configuration.adapter;
import cc.carm.lib.configuration.source.ConfigurationProvider; import cc.carm.lib.configuration.source.ConfigurationProvider;
import org.jetbrains.annotations.NotNull;
/** /**
* Value adapter, used to convert the value of the configuration file into the objects. * Value adapter, used to convert the value of the configuration file into the objects.
@@ -10,7 +9,8 @@ import org.jetbrains.annotations.NotNull;
* @param <B> The type of the base data * @param <B> The type of the base data
* @param <V> The type of the target value * @param <V> The type of the target value
*/ */
public abstract class ValueAdapter<P extends ConfigurationProvider, B, V> { public abstract class ValueAdapter<P extends ConfigurationProvider, B, V>
implements ValueSerializer<P, B, V>, ValueDeserializer<P, B, V> {
protected final Class<? super B> baseType; protected final Class<? super B> baseType;
protected final Class<? super V> valueType; protected final Class<? super V> valueType;
@@ -28,10 +28,6 @@ public abstract class ValueAdapter<P extends ConfigurationProvider, B, V> {
return valueType; 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) { public boolean isAdaptedFrom(Class<?> clazz) {
return clazz.isAssignableFrom(valueType); return clazz.isAssignableFrom(valueType);
} }
@@ -41,7 +37,7 @@ public abstract class ValueAdapter<P extends ConfigurationProvider, B, V> {
} }
public boolean isAdapterOf(Class<?> clazz) { public boolean isAdapterOf(Class<?> clazz) {
return valueType.isAssignableFrom(clazz); return clazz == valueType;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -3,20 +3,17 @@ package cc.carm.lib.configuration.adapter;
import cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapters; import cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapters;
import cc.carm.lib.configuration.core.function.ConfigDataFunction; import cc.carm.lib.configuration.core.function.ConfigDataFunction;
import cc.carm.lib.configuration.source.ConfigurationProvider; import cc.carm.lib.configuration.source.ConfigurationProvider;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class ValueAdapterRegistry<P extends ConfigurationProvider> { public class ValueAdapterRegistry<P extends ConfigurationProvider> {
protected final @NotNull P provider;
protected final Map<Class<?>, ValueAdapter<P, ?, ?>> adapters = new HashMap<>(); protected final Map<Class<?>, ValueAdapter<P, ?, ?>> adapters = new HashMap<>();
public ValueAdapterRegistry(@NotNull P provider) {
this.provider = provider;
}
public void register(@NotNull ValueAdapter<P, ?, ?> adapter) { public void register(@NotNull ValueAdapter<P, ?, ?> adapter) {
adapters.put(adapter.getValueClass(), adapter); adapters.put(adapter.getValueClass(), adapter);
} }
@@ -46,30 +43,31 @@ public class ValueAdapterRegistry<P extends ConfigurationProvider> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T deserialize(Class<T> type, Object value) throws Exception { @Contract("_,_,null -> null")
if (value == null) return null; public <T> T deserialize(@NotNull P provider, @NotNull Class<T> type, @Nullable Object source) throws Exception {
if (type == Object.class) return type.cast(value); if (source == null) return null; // Null check
if (type.isInstance(source)) return type.cast(source); // Not required to deserialize
ValueAdapter<P, ?, ?> adapter = getAdapter(type); ValueAdapter<P, ?, ?> adapter = getAdapter(type);
if (adapter == null) throw new RuntimeException("No adapter for type " + type.getName()); if (adapter == null) throw new RuntimeException("No adapter for type " + type.getName());
// CHECK IF VALUE IS ADAPTED FROM GIVEN VALUE'S TYPE // Check if value is adapted from given value's type
if (adapter.isAdaptedFrom(value)) { if (adapter.isAdaptedFrom(source)) {
return (T) adapter.deserializeObject(provider, type, value); return (T) adapter.deserializeObject(provider, type, source);
} }
// OTHERWISE, WE NEED TO DESERIALIZE ONE BY ONE // Otherwise, we need to deserialize one by one.
Object baseValue = deserialize(adapter.getBaseClass(), value); Object baseValue = deserialize(provider, adapter.getBaseClass(), source);
if (baseValue == null) return null; // Null check if (baseValue == null) return null; // Null check
return (T) adapter.deserializeObject(provider, type, baseValue); return (T) adapter.deserializeObject(provider, type, baseValue);
} }
public <T> Object serialize(T value) throws Exception { @Contract("_,null -> null")
if (value == null) return null; public <T> Object serialize(@NotNull P provider, @Nullable T value) throws Exception {
if (value == null) return null; // Null check
Class<?> valueClass = value.getClass(); ValueAdapter<P, ?, ?> adapter = getAdapter(value.getClass());
ValueAdapter<P, ?, ?> adapter = adapters.get(valueClass);
if (adapter == null) return value; // No adapters, try to return the original value if (adapter == null) return value; // No adapters, try to return the original value
if (adapter instanceof PrimitiveAdapters) { if (adapter instanceof PrimitiveAdapters) {
@@ -79,12 +77,14 @@ public class ValueAdapterRegistry<P extends ConfigurationProvider> {
} }
// Otherwise, we need to serialize one by one. // Otherwise, we need to serialize one by one.
return serialize(adapter.serializeObject(provider, value)); return serialize(provider, adapter.serializeObject(provider, value));
} }
public ValueAdapter<P, ?, ?> getAdapter(Class<?> clazz) { public ValueAdapter<P, ?, ?> getAdapter(Class<?> clazz) {
ValueAdapter<P, ?, ?> byClass = adapters.get(clazz); return adapters.getOrDefault(clazz, findAdapter(clazz));
if (byClass != null) return byClass; }
public ValueAdapter<P, ?, ?> findAdapter(Class<?> clazz) {
return adapters.values().stream().filter(adapter -> adapter.isAdapterOf(clazz)).findFirst().orElse(null); return adapters.values().stream().filter(adapter -> adapter.isAdapterOf(clazz)).findFirst().orElse(null);
} }
@@ -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 <P> Configuration provider
* @param <B> The type of base data
* @param <V> The type of target value
*/
@FunctionalInterface
public interface ValueDeserializer<P extends ConfigurationProvider, B, V> {
V deserialize(@NotNull P provider, @NotNull Class<? extends V> clazz, @NotNull B data) throws Exception;
}
@@ -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 <P> Configuration provider
* @param <B> The type of base data
* @param <V> The type of value
*/
@FunctionalInterface
public interface ValueSerializer<P extends ConfigurationProvider, B, V> {
B serialize(@NotNull P provider, @NotNull V value) throws Exception;
}
@@ -21,4 +21,9 @@ public class EnumAdapter<P extends ConfigurationProvider> extends ValueAdapter<P
return Enum.valueOf(clazz, data); return Enum.valueOf(clazz, data);
} }
@Override
public boolean isAdapterOf(Class<?> clazz) {
return clazz.isEnum();
}
} }
@@ -1,7 +1,5 @@
package cc.carm.lib.configuration.annotation; package cc.carm.lib.configuration.annotation;
import cc.carm.lib.configuration.core.ConfigInitializer;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -16,7 +14,7 @@ public @interface ConfigPath {
/** /**
* The path value of the current configuration. * 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 * @return The path value of the current configuration
*/ */
@@ -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 <T> {@link ConfigurationProvider} 配置文件的数据来源
* @author CarmJos
*/
public class ConfigInitializer<T extends ConfigurationProvider<?>> {
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<String> getClassHeaderComments(@NotNull Class<?> clazz,
@Nullable HeaderComment fieldAnnotation) {
List<String> classComments = readHeaderComments(clazz.getAnnotation(HeaderComment.class));
if (classComments != null) return classComments;
else return readHeaderComments(fieldAnnotation);
}
protected static List<String> 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(); // 最后转为全小写
}
}
@@ -5,4 +5,5 @@ package cc.carm.lib.configuration.core;
* which is used to label and record the configuration information. * which is used to label and record the configuration information.
*/ */
public interface Configuration { public interface Configuration {
} }
@@ -1,7 +1,7 @@
package cc.carm.lib.configuration.core.source; 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.core.Configuration;
import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.impl.CachedConfigValue; import cc.carm.lib.configuration.value.impl.CachedConfigValue;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -0,0 +1,8 @@
package cc.carm.lib.configuration.manifest;
public class ValueManifest {
}
@@ -1,15 +1,85 @@
package cc.carm.lib.configuration.source; 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.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; import org.jetbrains.annotations.NotNull;
public abstract class ConfigurationBuilder<P extends ConfigurationProvider> { import java.util.function.Consumer;
import java.util.function.Function;
protected ConfigurationLoader loader; public abstract class ConfigurationBuilder<P extends ConfigurationProvider<P>, C> {
protected ValueAdapterRegistry<P> processors;
protected Function<P, ConfigurationLoader<P>> loaderFunction = ConfigurationLoader::new;
protected Consumer<ConfigurationLoader<P>> loaderConsumer = loader -> {
};
public abstract @NotNull ConfigurationProvider build(); protected ValueAdapterRegistry<P> adapters = new ValueAdapterRegistry<>();
protected OptionHolder options = new OptionHolder();
public abstract C getThis();
public C loader(Function<P, ConfigurationLoader<P>> loaderFunction) {
this.loaderFunction = loaderFunction;
return getThis();
}
public C loader(ConfigurationLoader<P> loader) {
return loader(p -> loader);
}
public C loader(Consumer<ConfigurationLoader<P>> loaderConsumer) {
this.loaderConsumer = this.loaderConsumer.andThen(loaderConsumer);
return getThis();
}
public C pathGenerator(PathGenerator<P> pathGenerator) {
return loader(loader -> {
loader.setPathGenerator(pathGenerator);
});
}
public C adapters(ValueAdapterRegistry<P> adapters) {
this.adapters = adapters;
return getThis();
}
public C adapter(Consumer<ValueAdapterRegistry<P>> adapterRegistryConsumer) {
adapterRegistryConsumer.accept(adapters);
return getThis();
}
public C adapter(@NotNull ValueAdapter<P, ?, ?> adapter) {
return adapter(a -> a.register(adapter));
}
public <T> C adapter(Class<T> clazz, @NotNull ValueAdapter<P, ?, T> adapter) {
return adapter(a -> a.register(clazz, adapter));
}
public <B, V> C adapter(Class<B> baseClass, Class<V> valueClass,
ConfigDataFunction<B, V> parser, ConfigDataFunction<V, B> serializer) {
return adapter(a -> a.register(baseClass, valueClass, parser, serializer));
}
public C options(OptionHolder options) {
this.options = options;
return getThis();
}
public C option(Consumer<OptionHolder> optionsConsumer) {
optionsConsumer.accept(options);
return getThis();
}
public <O> C option(OptionType<O> option, O value) {
return option(o -> o.set(option, value));
}
public abstract @NotNull P build();
} }
@@ -1,4 +1,196 @@
package cc.carm.lib.configuration.source; 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<P extends ConfigurationProvider<P>> {
protected final P provider;
protected PathGenerator<P> pathGenerator;
public ConfigurationLoader(P provider) {
this(provider, StandardPathGenerator.of(provider));
}
public ConfigurationLoader(P provider, PathGenerator<P> pathGenerator) {
this.provider = provider;
this.pathGenerator = pathGenerator;
}
public void setPathGenerator(PathGenerator<P> pathGenerator) {
this.pathGenerator = pathGenerator;
}
public PathGenerator<P> 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)
);
}
} }
@@ -1,6 +1,37 @@
package cc.carm.lib.configuration.source; 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<P extends ConfigurationProvider<P>> {
protected @NotNull ConfigurationLoader<P> loader = new ConfigurationLoader<>();
protected @NotNull ValueAdapterRegistry<P> adapters = new ValueAdapterRegistry<>();
protected @NotNull OptionHolder options = new OptionHolder();
public OptionHolder options() {
return options;
}
public <T> @NotNull T option(@NotNull OptionType<T> option) {
return options.get(option);
}
public <T> void option(@NotNull OptionType<T> option, @NotNull T value) {
options.set(option, value);
}
public ConfigurationLoader<P> loader() {
return loader;
}
public void load(Configuration configuration) {
loader().load(configuration);
}
} }
@@ -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<P extends ConfigurationProvider<P>> {
@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(); // 最后转为全小写
}
}
@@ -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<P extends ConfigurationProvider<P>> implements PathGenerator<P> {
public static <T extends ConfigurationProvider<T>> StandardPathGenerator<T> of(T provider) {
return of(provider, PathGenerator::covertPathName);
}
public static <T extends ConfigurationProvider<T>> StandardPathGenerator<T> of(T provider, UnaryOperator<String> pathConverter) {
return new StandardPathGenerator<>(provider, pathConverter);
}
protected final P provider;
protected UnaryOperator<String> pathConverter;
public StandardPathGenerator(P provider, UnaryOperator<String> pathConverter) {
this.provider = provider;
this.pathConverter = pathConverter;
}
public @NotNull UnaryOperator<String> getPathConverter() {
return pathConverter;
}
public void setPathConverter(UnaryOperator<String> 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);
}
}
@@ -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<ConfigPath, String> PATH = AnnotatedMetaType.of(ConfigPath.class, ConfigPath::value);
AnnotatedMetaType<ConfigPath, Boolean> ROOT = AnnotatedMetaType.of(ConfigPath.class, ConfigPath::root);
}
@@ -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<Character> PATH_SEPARATOR = of('.');
/**
* Whether to copy files from resource if exists.
*/
OptionType<Boolean> COPY_DEFAULTS = of(true);
/**
* Whether to save default values if offered and not exists in configuration.
*/
OptionType<Boolean> SAVE_DEFAULTS = of(true);
/**
* Whether to load subclasses of configuration class.
*/
OptionType<Boolean> LOAD_SUB_CLASSES = of(true);
}
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.value; 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.ConfigurationProvider;
import cc.carm.lib.configuration.core.source.ConfigurationWrapper; import cc.carm.lib.configuration.core.source.ConfigurationWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
+8 -5
View File
@@ -12,7 +12,7 @@ public class AdaptTest {
@Test @Test
public void test() throws Exception { public void test() throws Exception {
ValueAdapterRegistry<?> registry = new ValueAdapterRegistry<>(new ConfigurationProvider()); ValueAdapterRegistry<ConfigurationProvider> registry = new ValueAdapterRegistry<>();
registry.register(Long.class, PrimitiveAdapters.ofLong()); registry.register(Long.class, PrimitiveAdapters.ofLong());
registry.register(long.class, PrimitiveAdapters.ofLong()); registry.register(long.class, PrimitiveAdapters.ofLong());
registry.register(Integer.class, PrimitiveAdapters.ofInteger()); registry.register(Integer.class, PrimitiveAdapters.ofInteger());
@@ -36,16 +36,19 @@ public class AdaptTest {
registry.register( registry.register(
Duration.class, LocalTime.class, Duration.class, LocalTime.class,
duration -> LocalTime.now().plus(duration), duration -> LocalTime.now().plus(duration),
data -> Duration.between(data, LocalTime.now()) data -> Duration.between(LocalTime.now(), data)
); );
LocalTime v = registry.deserialize(LocalTime.class, "600"); ConfigurationProvider provider = new ConfigurationProvider();
Object d = registry.serialize(v);
LocalTime v = registry.deserialize(provider, LocalTime.class, "600");
Object d = registry.serialize(provider, v);
System.out.println(v); System.out.println(v);
System.out.println(d); 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 { enum TestEnum {
-1
View File
@@ -1,4 +1,3 @@
import cc.carm.lib.configuration.core.ConfigInitializer;
import org.junit.Test; import org.junit.Test;
public class NameTest { public class NameTest {
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.demo.tests.conf; 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.core.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;
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.hocon; package cc.carm.lib.configuration.hocon;
import cc.carm.lib.configuration.core.ConfigInitializer; import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationComments;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import cc.carm.lib.configuration.hocon.exception.HOCONGetValueException; import cc.carm.lib.configuration.hocon.exception.HOCONGetValueException;
import cc.carm.lib.configuration.hocon.util.HOCONUtils; import cc.carm.lib.configuration.hocon.util.HOCONUtils;
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.json; package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.core.ConfigInitializer; import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationProvider; import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.sql; package cc.carm.lib.configuration.sql;
import cc.carm.lib.configuration.core.ConfigInitializer; import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationProvider; import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.api.SQLQuery; import cc.carm.lib.easysql.api.SQLQuery;
@@ -1,7 +1,6 @@
package cc.carm.lib.configuration.yaml; package cc.carm.lib.configuration.yaml;
import cc.carm.lib.configuration.core.ConfigInitializer; import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import cc.carm.lib.configuration.core.source.ConfigurationComments;
import cc.carm.lib.configuration.core.source.impl.FileConfigProvider; import cc.carm.lib.configuration.core.source.impl.FileConfigProvider;
import cc.carm.lib.yamlcommentupdater.CommentedYAML; import cc.carm.lib.yamlcommentupdater.CommentedYAML;
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter; import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
@@ -26,6 +25,7 @@ public class YAMLConfigProvider extends FileConfigProvider<YAMLSectionWrapper> i
} }
public void initializeConfig() { public void initializeConfig() {
ConfigurationOptions
this.configuration = YamlConfiguration.loadConfiguration(file); this.configuration = YamlConfiguration.loadConfiguration(file);
this.initializer = new ConfigInitializer<>(this); this.initializer = new ConfigInitializer<>(this);
} }
+1
View File
@@ -23,6 +23,7 @@
<module>impl/json</module> <module>impl/json</module>
<module>impl/sql</module> <module>impl/sql</module>
<module>impl/hocon</module> <module>impl/hocon</module>
<module>commentable</module>
</modules> </modules>
<name>EasyConfiguration</name> <name>EasyConfiguration</name>