1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 18:48:20 +08:00

feat(json): Implement json sources

This commit is contained in:
2025-02-12 04:25:29 +08:00
parent c68d2371ee
commit 05ff61a9d9
44 changed files with 656 additions and 558 deletions
@@ -55,13 +55,13 @@ public class ValueAdapter<TYPE>
@Override
public Object serialize(@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type, @NotNull TYPE value) throws Exception {
if (serializer == null) throw new UnsupportedOperationException("Serializer is not supported");
return serializer.serialize(provider, type, value);
return serializer.serialize(holder, type, value);
}
@Override
public TYPE parse(@NotNull ConfigurationHolder<?> holder, @NotNull ValueType<? super TYPE> type, @NotNull Object value) throws Exception {
if (deserializer == null) throw new UnsupportedOperationException("Deserializer is not supported");
return deserializer.parse(provider, type, value);
return deserializer.parse(holder, type, value);
}
@Override
@@ -101,7 +101,7 @@ public class ValueAdapterRegistry {
@Contract("_,_,null -> null")
public <T> T deserialize(@NotNull ConfigurationHolder<?> holder, @NotNull Class<T> type, @Nullable Object source) throws Exception {
return deserialize(provider, ValueType.of(type), source);
return deserialize(holder, ValueType.of(type), source);
}
@Contract("_,_,null -> null")
@@ -110,7 +110,7 @@ public class ValueAdapterRegistry {
if (type.isInstance(source)) return type.cast(source); // Not required to deserialize
ValueAdapter<T> adapter = adapterOf(type);
if (adapter == null) throw new RuntimeException("No adapter for type " + type);
return adapter.parse(provider, type, source);
return adapter.parse(holder, type, source);
}
@Contract("_,null -> null")
@@ -119,7 +119,7 @@ public class ValueAdapterRegistry {
ValueType<T> type = ValueType.of(value);
ValueAdapter<T> adapter = adapterOf(type);
if (adapter == null) return value; // No adapters, try to return the original value
return adapter.serialize(provider, type, value);
return adapter.serialize(holder, type, value);
}
@@ -3,7 +3,6 @@ package cc.carm.lib.configuration.adapter;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
@@ -12,12 +11,45 @@ import java.util.Objects;
*/
public abstract class ValueType<T> {
public static final ValueType<String> STRING = ofPrimitiveType(String.class);
public static final ValueType<Integer> INTEGER = ofPrimitiveType(Integer.class);
public static final ValueType<Integer> INTEGER_TYPE = ofPrimitiveType(int.class);
public static final ValueType<Long> LONG = ofPrimitiveType(Long.class);
public static final ValueType<Long> LONG_TYPE = ofPrimitiveType(long.class);
public static final ValueType<Double> DOUBLE = ofPrimitiveType(Double.class);
public static final ValueType<Double> DOUBLE_TYPE = ofPrimitiveType(double.class);
public static final ValueType<Float> FLOAT = ofPrimitiveType(Float.class);
public static final ValueType<Float> FLOAT_TYPE = ofPrimitiveType(float.class);
public static final ValueType<Boolean> BOOLEAN = ofPrimitiveType(Boolean.class);
public static final ValueType<Boolean> BOOLEAN_TYPE = ofPrimitiveType(boolean.class);
public static final ValueType<Byte> BYTE = ofPrimitiveType(Byte.class);
public static final ValueType<Byte> BYTE_TYPE = ofPrimitiveType(byte.class);
public static final ValueType<Short> SHORT = ofPrimitiveType(Short.class);
public static final ValueType<Short> SHORT_TYPE = ofPrimitiveType(short.class);
public static final ValueType<Character> CHAR = ofPrimitiveType(Character.class);
public static final ValueType<Character> CHAR_TYPE = ofPrimitiveType(char.class);
public static final ValueType<?>[] PRIMITIVE_TYPES = {
STRING, INTEGER, LONG, DOUBLE, FLOAT, BOOLEAN, BYTE, SHORT, CHAR,
INTEGER_TYPE, LONG_TYPE, DOUBLE_TYPE, FLOAT_TYPE, BOOLEAN_TYPE, BYTE_TYPE, SHORT_TYPE, CHAR_TYPE
};
@SuppressWarnings("unchecked")
public static <T> ValueType<T> of(@NotNull T value) {
return of((Class<T>) value.getClass());
}
@SuppressWarnings("unchecked")
public static <T> ValueType<T> of(final Type type) {
if (type == null) throw new NullPointerException("Type cannot be null");
if (type instanceof Class<?>) { // Try handle primitive types
Class<?> clazz = (Class<?>) type;
for (ValueType<?> valueType : PRIMITIVE_TYPES) {
if (valueType.getRawType() == clazz) {
return (ValueType<T>) valueType;
}
}
}
return new ValueType<T>(type) {
};
}
@@ -54,6 +86,11 @@ public abstract class ValueType<T> {
return of(parameterizedType);
}
private static <T> ValueType<T> ofPrimitiveType(Class<T> clazz) {
return new ValueType<T>(clazz) {
};
}
private final Type type;
protected ValueType() {
@@ -139,6 +176,9 @@ public abstract class ValueType<T> {
if (obj instanceof ValueType) {
return Objects.equals(type, ((ValueType<?>) obj).type);
}
if (obj instanceof Type) {
return Objects.equals(type, obj);
}
return false;
}
@@ -2,16 +2,16 @@ package cc.carm.lib.configuration.adapter.strandard;
import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
public interface StandardAdapters {
ValueAdapter<ConfigurationSection> SECTION_ADAPTER = new ValueAdapter<>(
ValueType.of(ConfigurationSection.class),
ValueAdapter<ConfigureSection> SECTION_ADAPTER = new ValueAdapter<>(
ValueType.of(ConfigureSection.class),
(provider, type, value) -> value,
(provider, type, value) -> {
if (value instanceof ConfigurationSection) {
return (ConfigurationSection) value;
if (value instanceof ConfigureSection) {
return (ConfigureSection) value;
} else throw new IllegalArgumentException("Value is not a ConfigurationSection");
}
);
@@ -5,7 +5,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ConfigValue;
import org.jetbrains.annotations.NotNull;
@@ -21,39 +21,39 @@ public abstract class AbstractSectionBuilder<
protected final @NotNull ValueType<PARAM> paramType;
protected @NotNull ValueHandler<ConfigurationSection, PARAM> parser;
protected @NotNull ValueHandler<PARAM, ? extends Map<Object, Object>> serializer;
protected @NotNull ValueHandler<ConfigureSection, PARAM> parser;
protected @NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer;
public AbstractSectionBuilder(@NotNull ValueType<TYPE> type, @NotNull ValueType<PARAM> paramType,
@NotNull ValueHandler<ConfigurationSection, PARAM> parser,
@NotNull ValueHandler<PARAM, ? extends Map<Object, Object>> serializer) {
@NotNull ValueHandler<ConfigureSection, PARAM> parser,
@NotNull ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
super(type);
this.paramType = paramType;
this.parser = parser;
this.serializer = serializer;
}
public @NotNull SELF parse(DataFunction<ConfigurationSection, PARAM> valueParser) {
public @NotNull SELF parse(DataFunction<ConfigureSection, PARAM> valueParser) {
return parse((p, section) -> valueParser.handle(section));
}
public @NotNull SELF parse(ValueHandler<ConfigurationSection, PARAM> valueParser) {
public @NotNull SELF parse(ValueHandler<ConfigureSection, PARAM> valueParser) {
this.parser = valueParser;
return self();
}
public @NotNull SELF serialize(DataFunction<PARAM, ? extends Map<Object, Object>> serializer) {
public @NotNull SELF serialize(DataFunction<PARAM, ? extends Map<String, Object>> serializer) {
return serialize((p, value) -> serializer.handle(value));
}
public @NotNull SELF serialize(ValueHandler<PARAM, ? extends Map<Object, Object>> serializer) {
public @NotNull SELF serialize(ValueHandler<PARAM, ? extends Map<String, Object>> serializer) {
this.serializer = serializer;
return self();
}
public @NotNull SELF serialize(Consumer<Map<Object, Object>> serializer) {
public @NotNull SELF serialize(Consumer<Map<String, Object>> serializer) {
return serialize((p, value) -> {
Map<Object, Object> map = new LinkedHashMap<>();
Map<String, Object> map = new LinkedHashMap<>();
serializer.accept(map);
return map;
});
@@ -62,12 +62,12 @@ public abstract class AbstractSectionBuilder<
protected ValueAdapter<PARAM> buildAdapter() {
return new ValueAdapter<>(this.paramType)
.parser((p, type, data) -> {
ConfigurationSection section = p.deserialize(ConfigurationSection.class, data);
ConfigureSection section = p.deserialize(ConfigureSection.class, data);
if (section == null) return null;
return this.parser.handle(p, section);
})
.serializer((p, type, data) -> {
Map<Object, Object> map = this.serializer.handle(p, data);
Map<String, Object> map = this.serializer.handle(p, data);
return map == null || map.isEmpty() ? null : map;
});
}
@@ -49,13 +49,13 @@ public abstract class AbstractSourceBuilder<
protected ValueAdapter<PARAM> buildAdapter() {
return new ValueAdapter<>(this.paramType)
.parser((p, type, data) -> {
SOURCE source = p.deserialize(this.sourceType, data);
return this.valueParser.handle(p, source);
.parser((holder, type, data) -> {
SOURCE source = holder.deserialize(this.sourceType, data);
return this.valueParser.handle(holder, source);
})
.serializer((p, type, data) -> {
SOURCE source = this.valueSerializer.handle(p, data);
return p.serialize(source);
.serializer((holder, type, data) -> {
SOURCE source = this.valueSerializer.handle(holder, data);
return holder.serialize(source);
});
}
@@ -23,7 +23,7 @@ public class ConfigListBuilder<V> {
}
public @NotNull SourceListBuilder<String, V> fromString() {
return from(String.class);
return new SourceListBuilder<>(ValueType.STRING, type, ValueHandler.required(), ValueHandler.stringValue(), ArrayList::new);
}
public @NotNull SectionListBuilder<V> fromSection() {
@@ -3,7 +3,7 @@ package cc.carm.lib.configuration.builder.list;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.impl.AbstractSectionBuilder;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.standard.ConfiguredList;
import org.jetbrains.annotations.NotNull;
@@ -15,8 +15,8 @@ public class SectionListBuilder<V> extends AbstractSectionBuilder<List<V>, V, Co
protected @NotNull Supplier<? extends List<V>> constructor;
public SectionListBuilder(@NotNull ValueType<V> paramType,
@NotNull ValueHandler<ConfigurationSection, V> parser,
@NotNull ValueHandler<V, ? extends Map<Object, Object>> serializer,
@NotNull ValueHandler<ConfigureSection, V> parser,
@NotNull ValueHandler<V, ? extends Map<String, Object>> serializer,
@NotNull Supplier<? extends List<V>> constructor) {
super(new ValueType<List<V>>() {
}, paramType, parser, serializer);
@@ -2,7 +2,7 @@ package cc.carm.lib.configuration.builder.value;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@@ -30,7 +30,7 @@ public class ConfigValueBuilder<V> {
}
public @NotNull SourceValueBuilder<String, V> fromString() {
return from(String.class);
return from(ValueType.STRING, ValueHandler.required(), ValueHandler.stringValue());
}
public @NotNull SectionValueBuilder<V> fromSection() {
@@ -38,8 +38,8 @@ public class ConfigValueBuilder<V> {
}
public @NotNull SectionValueBuilder<V> fromSection(
@NotNull ValueHandler<ConfigurationSection, V> valueParser,
@NotNull ValueHandler<V, ? extends Map<Object, Object>> valueSerializer
@NotNull ValueHandler<ConfigureSection, V> valueParser,
@NotNull ValueHandler<V, ? extends Map<String, Object>> valueSerializer
) {
return new SectionValueBuilder<>(this.type, valueParser, valueSerializer);
}
@@ -3,7 +3,7 @@ package cc.carm.lib.configuration.builder.value;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.builder.impl.AbstractSectionBuilder;
import cc.carm.lib.configuration.function.ValueHandler;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import org.jetbrains.annotations.NotNull;
@@ -12,8 +12,8 @@ import java.util.Map;
public class SectionValueBuilder<V> extends AbstractSectionBuilder<V, V, ConfiguredValue<V>, SectionValueBuilder<V>> {
public SectionValueBuilder(@NotNull ValueType<V> type,
@NotNull ValueHandler<ConfigurationSection, V> parser,
@NotNull ValueHandler<V, ? extends Map<Object, Object>> serializer) {
@NotNull ValueHandler<ConfigureSection, V> parser,
@NotNull ValueHandler<V, ? extends Map<String, Object>> serializer) {
super(type, type, parser, serializer);
}
@@ -50,6 +50,11 @@ public interface ValueHandler<T, R> {
return ConfigurationHolder::serialize;
}
@Contract(pure = true)
static <T> @NotNull ValueHandler<T, String> stringValue() {
return (provider, input) -> String.valueOf(input);
}
@Contract(pure = true)
static <T> @NotNull ValueHandler<Object, T> fromObject(ValueType<T> type) {
return (provider, input) -> provider.deserialize(type, input);
@@ -1,46 +1,38 @@
package cc.carm.lib.configuration.source;
import cc.carm.lib.configuration.adapter.ValueAdapterRegistry;
import cc.carm.lib.configuration.adapter.*;
import cc.carm.lib.configuration.adapter.strandard.PrimitiveAdapter;
import cc.carm.lib.configuration.adapter.strandard.StandardAdapters;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.loader.PathGenerator;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigurationSource;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class ConfigurationFactory<SOURCE extends ConfigurationSource<SOURCE, ?>, PROVIDER extends ConfigurationHolder<SOURCE>, SELF> {
protected Function<PROVIDER, ConfigurationInitializer> loaderFunction = PROVIDER -> new ConfigurationInitializer();
protected Consumer<ConfigurationInitializer> loaderConsumer = loader -> {
};
public abstract class ConfigurationFactory<
SOURCE extends ConfigureSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>,
SELF
> {
protected ValueAdapterRegistry adapters = new ValueAdapterRegistry();
protected ConfigurationOptionHolder options = new ConfigurationOptionHolder();
protected ConfigurationInitializer initializer = new ConfigurationInitializer();
public ConfigurationFactory() {
this.adapters.register(PrimitiveAdapter.ADAPTERS);
this.adapters.register(StandardAdapters.SECTION_ADAPTER);
}
public abstract SELF self();
public SELF loader(Function<PROVIDER, ConfigurationInitializer> loaderFunction) {
this.loaderFunction = loaderFunction;
return self();
}
public SELF loader(ConfigurationInitializer loader) {
return loader(PROVIDER -> loader);
}
public SELF loader(Consumer<ConfigurationInitializer> loaderConsumer) {
this.loaderConsumer = this.loaderConsumer.andThen(loaderConsumer);
return self();
}
public SELF pathGenerator(PathGenerator generator) {
return loader(loader -> {
loader.pathGenerator(generator);
});
}
public SELF adapters(ValueAdapterRegistry adapters) {
this.adapters = adapters;
@@ -52,18 +44,37 @@ public abstract class ConfigurationFactory<SOURCE extends ConfigurationSource<SO
return self();
}
// public SELF adapter(@NotNull ValueAdapter<?, ?> adapter) {
// return adapter(a -> a.register(adapter));
// }
//
// public <T> SELF adapter(Class<T> clazz, @NotNull ValueAdapter<?, T> adapter) {
// return adapter(a -> a.register(clazz, adapter));
// }
//
// public <B, V> SELF 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 <T> SELF adapter(@NotNull ValueAdapter<T> adapter) {
return adapter(a -> a.register(adapter));
}
public <T> SELF adapter(@NotNull ValueType<T> type, @NotNull ValueSerializer<T> serializer) {
return adapter(a -> a.register(type, serializer));
}
public <T> SELF adapter(@NotNull ValueType<T> type, @NotNull ValueParser<T> parser) {
return adapter(a -> a.register(type, parser));
}
public <FROM, TO> SELF adapter(@NotNull Class<FROM> from, @NotNull Class<TO> to,
@NotNull DataFunction<FROM, TO> parser,
@NotNull DataFunction<TO, FROM> serializer) {
return adapter(a -> a.register(from, to, parser, serializer));
}
public <FROM, TO> SELF adapter(@NotNull ValueType<FROM> from, @NotNull ValueType<TO> to,
@NotNull DataFunction<FROM, TO> parser,
@NotNull DataFunction<TO, FROM> serializer) {
return adapter(a -> a.register(from, to, parser, serializer));
}
public <T> SELF adapter(@NotNull ValueType<T> type, @NotNull ValueSerializer<T> serializer, @NotNull ValueParser<T> parser) {
return adapter(a -> a.register(type, serializer, parser));
}
public <T> SELF adapter(@NotNull Class<T> type, @NotNull ValueSerializer<T> serializer, @NotNull ValueParser<T> parser) {
return adapter(ValueType.of(type), serializer, parser);
}
public SELF options(ConfigurationOptionHolder options) {
this.options = options;
@@ -79,6 +90,29 @@ public abstract class ConfigurationFactory<SOURCE extends ConfigurationSource<SO
return option(o -> o.set(option, value));
}
public abstract @NotNull PROVIDER build();
public SELF initializer(ConfigurationInitializer initializer) {
this.initializer = initializer;
return self();
}
public SELF initializer(Consumer<ConfigurationInitializer> initializerConsumer) {
initializerConsumer.accept(initializer);
return self();
}
public SELF pathGenerator(PathGenerator generator) {
return initializer(loader -> {
loader.pathGenerator(generator);
});
}
public <M, A extends Annotation> SELF metaAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<M> metadata,
@NotNull Function<A, M> extractor) {
return initializer(loader -> loader.registerAnnotation(annotation, metadata, extractor));
}
public abstract @NotNull HOLDER build();
}
@@ -6,16 +6,15 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigurationSource;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Objects;
public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
protected final @NotNull ValueAdapterRegistry adapters;
protected final @NotNull ConfigurationOptionHolder options;
@@ -23,8 +22,6 @@ public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
protected final @NotNull ConfigurationInitializer initializer;
protected @Nullable S source;
public ConfigurationHolder(@NotNull ValueAdapterRegistry adapters,
@NotNull ConfigurationOptionHolder options,
@NotNull Map<String, ConfigurationMetaHolder> metadata,
@@ -35,16 +32,14 @@ public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
this.metadata = metadata;
}
public @NotNull S source() {
return Objects.requireNonNull(source, "Source is not initialized");
}
public abstract @NotNull SOURCE config();
public void reload() throws Exception {
source().reload();
config().reload();
}
public void save() throws Exception {
source().save();
config().save();
}
public ConfigurationOptionHolder options() {
@@ -83,7 +78,7 @@ public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
return adapters().serialize(this, value);
}
public void load(Class<? extends Configuration> configClass) {
public void initialize(Class<? extends Configuration> configClass) {
try {
initializer.initialize(this, configClass);
} catch (Exception e) {
@@ -91,7 +86,7 @@ public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
}
}
public void load(@NotNull Configuration config) {
public void initialize(@NotNull Configuration config) {
try {
initializer.initialize(this, config);
} catch (Exception e) {
@@ -99,7 +94,7 @@ public class ConfigurationHolder<S extends ConfigurationSource<S, ?>> {
}
}
public void load(@NotNull ValueManifest<?> value) {
public void initialize(@NotNull ValueManifest<?> value) {
value.holder(this);
}
@@ -70,49 +70,49 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) {
appendFieldInitializer((provider, path, field) -> {
appendFieldInitializer((holder, path, field) -> {
A data = field.getAnnotation(annotation);
if (data == null) return;
provider.metadata(path).setIfAbsent(metadata, extractor.apply(data));
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
});
appendClassInitializer((provider, path, clazz) -> {
appendClassInitializer((holder, path, clazz) -> {
A data = clazz.getAnnotation(annotation);
if (data == null) return;
provider.metadata(path).setIfAbsent(metadata, extractor.apply(data));
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
});
}
public @Nullable String getFieldPath(ConfigurationHolder<?> holder, @Nullable String parentPath, @NotNull Field field) {
return pathGenerator.getFieldPath(provider, parentPath, field);
return pathGenerator.getFieldPath(holder, parentPath, field);
}
public @Nullable String getClassPath(ConfigurationHolder<?> holder, @Nullable String parentPath,
@NotNull Class<?> clazz, @Nullable Field clazzField) {
return pathGenerator.getClassPath(provider, parentPath, clazz, clazzField);
return pathGenerator.getClassPath(holder, parentPath, clazz, clazzField);
}
public void initialize(ConfigurationHolder<?> holder, @NotNull Configuration config) throws Exception {
initializeInstance(provider, config, null, null);
if (provider.options().get(StandardOptions.SET_DEFAULTS)) provider.save();
initializeInstance(holder, config, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save();
}
public void initialize(ConfigurationHolder<?> holder, @NotNull Class<? extends Configuration> clazz) throws Exception {
initializeStaticClass(provider, clazz, null, null);
if (provider.options().get(StandardOptions.SET_DEFAULTS)) provider.save();
initializeStaticClass(holder, clazz, null, null);
if (holder.options().get(StandardOptions.SET_DEFAULTS)) holder.save();
}
// 针对实例类的初始化方法
protected void initializeInstance(@NotNull ConfigurationHolder<?> holder,
@NotNull Configuration root, @Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(provider, parentPath, root.getClass(), configField);
String path = getClassPath(holder, parentPath, root.getClass(), configField);
try {
this.classInitializer.whenInitialize(provider, path, root.getClass());
this.classInitializer.whenInitialize(holder, path, root.getClass());
} catch (Exception e) {
e.printStackTrace();
}
Arrays.stream(root.getClass().getDeclaredFields()).forEach(field -> initializeField(provider, root, field, path));
Arrays.stream(root.getClass().getDeclaredFields()).forEach(field -> initializeField(holder, root, field, path));
}
// 针对静态类的初始化方法
@@ -121,22 +121,22 @@ public class ConfigurationInitializer {
@NotNull Class<?> clazz, @Nullable String parentPath, @Nullable Field configField) {
if (!Configuration.class.isAssignableFrom(clazz)) return; // 只解析继承了 ConfigurationRoot 的类
String path = getClassPath(provider, parentPath, clazz, configField);
String path = getClassPath(holder, parentPath, clazz, configField);
try {
this.classInitializer.whenInitialize(provider, path, (Class<? extends Configuration>) clazz);
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz);
} catch (Exception e) {
e.printStackTrace();
}
for (Field field : clazz.getDeclaredFields()) {
initializeField(provider, clazz, field, path);
initializeField(holder, clazz, field, path);
}
if (provider.options().get(StandardOptions.LOAD_SUB_CLASSES)) {
if (holder.options().get(StandardOptions.LOAD_SUB_CLASSES)) {
Class<?>[] classes = clazz.getDeclaredClasses();
for (int i = classes.length - 1; i >= 0; i--) { // 逆向加载,保持顺序。
initializeStaticClass(provider, classes[i], path, null);
initializeStaticClass(holder, classes[i], path, null);
}
}
}
@@ -150,20 +150,20 @@ public class ConfigurationInitializer {
if (object instanceof ConfigValue<?>) {
// 目标是 ConfigValue 实例,进行具体的初始化注入
ConfigValue<?> value = (ConfigValue<?>) object;
String path = getFieldPath(provider, parent, field);
String path = getFieldPath(holder, parent, field);
if (path == null) return;
value.initialize(provider, path);
value.initialize(holder, path);
try {
this.fieldInitializer.whenInitialize(provider, path, field);
this.fieldInitializer.whenInitialize(holder, path, field);
} catch (Exception e) {
e.printStackTrace();
}
} else if (source instanceof Configuration && object instanceof Configuration) {
// 当且仅当 源字段与字段 均为Configuration实例时,才对目标字段进行下一步初始化加载。
initializeInstance(provider, (Configuration) object, parent, field);
initializeInstance(holder, (Configuration) object, parent, field);
} else if (source instanceof Class<?> && object instanceof Class<?>) {
// 当且仅当 源字段与字段 均为静态类时,才对目标字段进行下一步初始化加载。
initializeStaticClass(provider, (Class<?>) object, parent, field);
initializeStaticClass(holder, (Class<?>) object, parent, field);
}
// 以上判断实现以下规范:
@@ -40,8 +40,8 @@ public class PathGenerator {
public @Nullable String getFieldPath(@NotNull ConfigurationHolder<?> holder,
@Nullable String parentPath, @NotNull Field field) {
ConfigPath path = field.getAnnotation(ConfigPath.class);
if (path == null) return link(provider, parentPath, false, field.getName()); // No annotation, use field name.
else return link(provider, parentPath, path.root(), select(path.value(), field.getName()));
if (path == null) return link(holder, parentPath, false, field.getName()); // No annotation, use field name.
else return link(holder, parentPath, path.root(), select(path.value(), field.getName()));
}
public @Nullable String getClassPath(@NotNull ConfigurationHolder<?> holder,
@@ -52,14 +52,14 @@ public class PathGenerator {
// and use filed information.
ConfigPath clazzPath = clazz.getAnnotation(ConfigPath.class);
if (clazzPath != null) return link(provider, parentPath, clazzPath.root(), clazzPath.value());
if (clazzPath != null) return link(holder, parentPath, clazzPath.root(), clazzPath.value());
if (clazzField == null) {
return link(provider, parentPath, false, clazz.getSimpleName()); // No field, use class name.
return link(holder, parentPath, false, clazz.getSimpleName()); // No field, use class name.
}
ConfigPath fieldPath = clazzField.getAnnotation(ConfigPath.class);
if (fieldPath == null) return link(provider, parentPath, false, clazzField.getName());
else return getFieldPath(provider, parentPath, clazzField);
if (fieldPath == null) return link(holder, parentPath, false, clazzField.getName());
else return getFieldPath(holder, parentPath, clazzField);
}
protected String select(String path, String defaultValue) {
@@ -70,7 +70,7 @@ public class PathGenerator {
protected @Nullable String link(@NotNull ConfigurationHolder<?> holder,
@Nullable String parent, boolean root, @Nullable String path) {
if (path == null || path.isEmpty()) return root ? null : parent;
return root || parent == null ? covertPath(path) : parent + pathSeparator(provider) + covertPath(path);
return root || parent == null ? covertPath(path) : parent + pathSeparator(holder) + covertPath(path);
}
public static boolean isBlank(String path) {
@@ -78,7 +78,7 @@ public class PathGenerator {
}
public static char pathSeparator(ConfigurationHolder<?> holder) {
return provider.options().get(StandardOptions.PATH_SEPARATOR);
return holder.options().get(StandardOptions.PATH_SEPARATOR);
}
/**
@@ -27,7 +27,7 @@ public interface StandardOptions {
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* <br> if true, the values will be parsed when
* {@link ConfigurationHolder#load(Configuration)}.
* {@link ConfigurationHolder#initialize(Configuration)}.
*/
ConfigurationOption<Boolean> PRELOAD = of(false);
@@ -1,46 +0,0 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
public abstract class ConfigurationSource<SELF extends ConfigurationSource<SELF, ORIGINAL>, ORIGINAL>
implements ConfigurationSection {
protected final @NotNull ConfigurationHolder<? extends SELF> holder;
protected long lastUpdateMillis;
protected ConfigurationSource(@NotNull ConfigurationHolder<? extends SELF> holder,
long lastUpdateMillis) {
this.holder = holder;
this.lastUpdateMillis = lastUpdateMillis;
}
public @NotNull ConfigurationHolder<? extends SELF> holder() {
return holder;
}
public void reload() throws Exception {
onReload(); // 调用重写的Reload方法
this.lastUpdateMillis = System.currentTimeMillis();
}
protected abstract SELF self();
/**
* @return Original configuration object
*/
public abstract @NotNull ORIGINAL original();
public abstract void save() throws Exception;
protected abstract void onReload() throws Exception;
public long getLastUpdateMillis() {
return this.lastUpdateMillis;
}
public boolean isExpired(long parsedTime) {
return getLastUpdateMillis() > parsedTime;
}
}
@@ -1,6 +1,7 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -8,7 +9,13 @@ import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
public interface ConfigurationSection {
public interface ConfigureSection {
@NotNull ConfigureSource<?, ?, ?> source();
default char separator() {
return source().holder().options().get(StandardOptions.PATH_SEPARATOR);
}
@NotNull
default Set<String> getKeys(boolean deep) {
@@ -33,7 +40,7 @@ public interface ConfigurationSection {
boolean isSection(@NotNull String path);
@Nullable
ConfigurationSection getSection(@NotNull String path);
ConfigureSection getSection(@NotNull String path);
@Nullable Object get(@NotNull String path);
@@ -0,0 +1,97 @@
package cc.carm.lib.configuration.source.section;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public abstract class ConfigureSource<
SECTION extends ConfigureSection, ORIGINAL,
SELF extends ConfigureSource<SECTION, ORIGINAL, SELF>>
implements ConfigureSection {
protected final @NotNull ConfigurationHolder<? extends SELF> holder;
protected long lastUpdateMillis;
protected ConfigureSource(@NotNull ConfigurationHolder<? extends SELF> holder, long lastUpdateMillis) {
this.holder = holder;
this.lastUpdateMillis = lastUpdateMillis;
}
public @NotNull ConfigurationHolder<? extends SELF> holder() {
return holder;
}
public void reload() throws Exception {
onReload(); // 调用重写的Reload方法
this.lastUpdateMillis = System.currentTimeMillis();
}
protected abstract SELF self();
/**
* @return Original configuration object
*/
public abstract @NotNull ORIGINAL original();
/**
* @return Configuration section
*/
public abstract @NotNull SECTION section();
public abstract void save() throws Exception;
protected abstract void onReload() throws Exception;
public long getLastUpdateMillis() {
return this.lastUpdateMillis;
}
public boolean isExpired(long parsedTime) {
return getLastUpdateMillis() > parsedTime;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return section().getValues(deep);
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
section().set(path, value);
}
@Override
public boolean contains(@NotNull String path) {
return section().contains(path);
}
@Override
public boolean isList(@NotNull String path) {
return section().isList(path);
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return section().getList(path);
}
@Override
public boolean isSection(@NotNull String path) {
return section().isSection(path);
}
@Override
public @Nullable ConfigureSection getSection(@NotNull String path) {
return section().getSection(path);
}
@Override
public @Nullable Object get(@NotNull String path) {
return section().get(path);
}
}
@@ -3,7 +3,7 @@ package cc.carm.lib.configuration.value;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigurationSource;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -101,8 +101,8 @@ public class ValueManifest<T> {
throw new IllegalStateException("Value does not have a provider.");
}
public @NotNull ConfigurationSource<?, ?> config() {
return holder().source();
public @NotNull ConfigureSource<?, ?, ?> config() {
return holder().config();
}
public ConfigurationMetaHolder metadata() {
@@ -4,7 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueAdapter;
import cc.carm.lib.configuration.adapter.ValueParser;
import cc.carm.lib.configuration.adapter.ValueSerializer;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.value.ValueManifest;
import cc.carm.lib.configuration.value.impl.CachedConfigValue;
import org.jetbrains.annotations.NotNull;
@@ -58,7 +58,7 @@ public class ConfiguredMap<K, V> extends CachedConfigValue<Map<K, V>> implements
// If the value is expired, we need to update it
Map<K, V> map = createMap();
ConfigurationSection section = config().getSection(path());
ConfigureSection section = config().getSection(path());
if (section == null) return getDefaultFirst(map);
Set<String> keys = section.getKeys(false);
@@ -30,6 +30,11 @@ public class ConfiguredValue<V> extends CachedConfigValue<V> {
return of(ValueType.of(type), () -> null);
}
public static <V> ConfiguredValue<V> of(@NotNull Class<V> type, @NotNull V defaults) {
return of(ValueType.of(type), () -> defaults);
}
public static <V> ConfiguredValue<V> of(@NotNull Class<V> type, @NotNull Supplier<@Nullable V> defaultSupplier) {
return of(ValueType.of(type), defaultSupplier);
}
+11 -3
View File
@@ -5,6 +5,7 @@ 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;
@@ -28,10 +29,17 @@ public class AdaptTest {
data -> Duration.between(LocalTime.now(), data)
);
ConfigurationHolder<TestSource> provider = new ConfigurationHolder<>(
new TestSource(), registry, new ConfigurationOptionHolder(),
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);
+1
View File
@@ -1,3 +1,4 @@
import cc.carm.lib.configuration.source.loader.PathGenerator;
import org.junit.Test;
public class NameTest {
@@ -6,6 +6,7 @@ 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;
@@ -14,10 +15,17 @@ public class LoaderTest {
@Test
public void test() throws Exception {
ConfigurationHolder<TestSource> provider = new ConfigurationHolder<>(
new TestSource(), new ValueAdapterRegistry(), new ConfigurationOptionHolder(),
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);
@@ -1,6 +1,7 @@
package cc.carm.lib.configuration.json;
package cc.carm.test.config;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
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;
@@ -8,8 +9,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JSONConfigurationSection implements ConfigurationSection {
public class TestSection implements ConfigureSection {
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return null;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
@@ -42,7 +46,7 @@ public class JSONConfigurationSection implements ConfigurationSection {
}
@Override
public @Nullable ConfigurationSection getSection(@NotNull String path) {
public @Nullable ConfigureSection getSection(@NotNull String path) {
return null;
}
@@ -1,18 +1,16 @@
package cc.carm.test.config;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import cc.carm.lib.configuration.source.section.ConfigurationSource;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TestSource extends ConfigurationSource<TestSource, Map<String, String>> {
public class TestSource extends ConfigureSource<TestSection, Map<String, String>, TestSource> {
public TestSource() {
super(System.currentTimeMillis());
public TestSource(@NotNull ConfigurationHolder<? extends TestSource> holder, long lastUpdateMillis) {
super(holder, lastUpdateMillis);
}
@Override
@@ -36,47 +34,12 @@ public class TestSource extends ConfigurationSource<TestSource, Map<String, Stri
}
@Override
public @NotNull Set<String> getKeys(boolean deep) {
public @NotNull TestSection section() {
return null;
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
@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 null;
}
@Override
public boolean isSection(@NotNull String path) {
return false;
}
@Override
public @Nullable ConfigurationSection getSection(@NotNull String path) {
public @NotNull ConfigureSource<?, ?, ?> source() {
return null;
}
}
+7
View File
@@ -26,6 +26,13 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -1,6 +1,6 @@
package cc.carm.lib.configuration.demo;
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.HeaderComment;
import cc.carm.lib.configuration.value.ConfigValue;
@@ -3,9 +3,9 @@ package cc.carm.lib.configuration.demo.tests;
import cc.carm.lib.configuration.demo.tests.conf.DemoConfiguration;
import cc.carm.lib.configuration.demo.tests.conf.TestConfiguration;
import cc.carm.lib.configuration.demo.tests.model.TestModel;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.TestOnly;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -14,8 +14,12 @@ import java.util.stream.IntStream;
public class ConfigurationTest {
@TestOnly
public static void testDemo(ConfigurationProvider<?> provider) {
provider.initialize(DemoConfiguration.class);
public static void testDemo(ConfigurationHolder<?> holder) {
try {
holder.initializer().initialize(holder, DemoConfiguration.class);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("----------------------------------------------------");
@@ -48,21 +52,21 @@ public class ConfigurationTest {
System.out.println(DemoConfiguration.MODEL_TEST.get());
DemoConfiguration.MODEL_TEST.set(TestModel.random());
System.out.println("> Test Maps:");
DemoConfiguration.USERS.forEach((k, v) -> System.out.println(k + ": " + v));
LinkedHashMap<Integer, UUID> data = new LinkedHashMap<>();
for (int i = 1; i <= 5; i++) {
data.put(i, UUID.randomUUID());
}
DemoConfiguration.USERS.set(data);
// System.out.println("> Test Maps:");
// DemoConfiguration.USERS.forEach((k, v) -> System.out.println(k + ": " + v));
// LinkedHashMap<Integer, UUID> data = new LinkedHashMap<>();
// for (int i = 1; i <= 5; i++) {
// data.put(i, UUID.randomUUID());
// }
// DemoConfiguration.USERS.set(data);
System.out.println("----------------------------------------------------");
}
public static void testInner(ConfigurationProvider<?> provider) {
public static void testInner(ConfigurationHolder<?> provider) {
TestConfiguration TEST = new TestConfiguration();
provider.initialize(TEST, true);
provider.initialize(TEST);
System.out.println("> Test Inner value before:");
System.out.println(TEST.INNER.INNER_VALUE.getNotNull());
@@ -76,7 +80,7 @@ public class ConfigurationTest {
}
public static void save(ConfigurationProvider<?> provider) {
public static void save(ConfigurationHolder<?> provider) {
try {
provider.save();
} catch (Exception e) {
@@ -1,15 +1,13 @@
package cc.carm.lib.configuration.demo.tests.conf;
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.HeaderComment;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.tests.model.TestModel;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredList;
import cc.carm.lib.configuration.value.standard.ConfiguredMap;
import cc.carm.lib.configuration.value.standard.ConfiguredSection;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import cc.carm.lib.configuration.demo.tests.model.TestModel;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
@@ -37,22 +35,22 @@ public interface DemoConfiguration extends Configuration {
@ConfigPath("user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComment({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("Section数据也支持InlineComment注释")
ConfigValue<TestModel> MODEL_TEST = ConfiguredSection.builderOf(TestModel.class)
ConfigValue<TestModel> MODEL_TEST = ConfiguredValue.builderOf(TestModel.class).fromSection()
.defaults(new TestModel("Carm", UUID.randomUUID()))
.parseValue((section, defaultValue) -> TestModel.deserialize(section))
.serializeValue(TestModel::serialize).build();
.parse((holder, section) -> TestModel.deserialize(section))
.serialize(TestModel::serialize).build();
@HeaderComment({"[ID - UUID]对照表", "", "用于测试Map类型的解析与序列化保存"})
ConfiguredMap<Integer, UUID> USERS = ConfiguredMap.builderOf(Integer.class, UUID.class)
.asLinkedMap().fromString()
.parseKey(Integer::parseInt)
.parseValue(v -> Objects.requireNonNull(UUID.fromString(v)))
.build();
// @HeaderComment({"[ID - UUID]对照表", "", "用于测试Map类型的解析与序列化保存"})
// ConfiguredMap<Integer, UUID> USERS = ConfiguredMap.builderOf(Integer.class, UUID.class)
// .asLinkedMap().fromString()
// .parseKey(Integer::parseInt)
// .parseValue(v -> Objects.requireNonNull(UUID.fromString(v)))
// .build();
/**
* 支持内部类的直接注册。
* 注意,需要使用 {@link ConfigInitializer#initialize(Class, boolean, boolean)} 方法,并设定第三个参数为 true。
* 注意,需要用 {@link cc.carm.lib.configuration.source.option.StandardOptions#LOAD_SUB_CLASSES}
*/
class Sub implements Configuration {
@@ -60,14 +58,14 @@ public interface DemoConfiguration extends Configuration {
@InlineComment("This is an inline comment")
public static final ConfigValue<UUID> UUID_CONFIG_VALUE = ConfiguredValue
.builderOf(UUID.class).fromString()
.parseValue((data, defaultValue) -> UUID.fromString(data))
.parse((holder, data) -> UUID.fromString(data))
.build();
public static class That implements Configuration {
public static final ConfiguredList<UUID> OPERATORS = ConfiguredList
.builderOf(UUID.class).fromString()
.parseValue(s -> Objects.requireNonNull(UUID.fromString(s)))
.parse(s -> Objects.requireNonNull(UUID.fromString(s)))
.build();
}
@@ -1,13 +1,12 @@
package cc.carm.lib.configuration.demo.tests.conf;
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.HeaderComment;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredSection;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import cc.carm.lib.configuration.demo.tests.model.TestModel;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
import java.util.UUID;
@@ -15,16 +14,15 @@ public class TestConfiguration implements Configuration {
public final TestInnerConfiguration INNER = new TestInnerConfiguration();
public final ConfigValue<Double> CLASS_VALUE = ConfiguredValue.of(Double.class, 1.0D);
public final ConfigValue<Double> CLASS_VALUE = ConfiguredValue.of(1.0D);
@ConfigPath("test.user") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComment({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("Section数据也支持InlineComment注释")
public final ConfigValue<TestModel> TEST_MODEL = ConfiguredSection
.builderOf(TestModel.class)
public final ConfigValue<TestModel> TEST_MODEL = ConfiguredValue.builderOf(TestModel.class).fromSection()
.defaults(new TestModel("Carm", UUID.randomUUID()))
.parseValue((section, defaultValue) -> TestModel.deserialize(section))
.serializeValue(TestModel::serialize).build();
.parse((holder, section) -> TestModel.deserialize(section))
.serialize((holder, data) -> data.serialize()).build();
}
@@ -1,6 +1,6 @@
package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.source.Configuration;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.HeaderComment;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComment("Inner Test")
public class TestInnerConfiguration implements Configuration {
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(Double.class, 1.0D);
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(1.0D);
}
@@ -1,5 +1,6 @@
package cc.carm.lib.configuration.demo.tests.model;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -32,7 +33,7 @@ public class TestModel extends AbstractModel {
return map;
}
public static TestModel deserialize(ConfigurationWrapper<?> section) {
public static TestModel deserialize(ConfigureSection section) {
String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid");
@@ -0,0 +1,39 @@
package cc.carm.lib.configuration.source;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.nio.file.Path;
public abstract class FileConfigFactory<SOURCE extends FileConfigSource<?, ?, SOURCE>,
HOLDER extends ConfigurationHolder<SOURCE>, SELF extends FileConfigFactory<SOURCE, HOLDER, SELF>>
extends ConfigurationFactory<SOURCE, HOLDER, SELF> {
protected @NotNull File file;
protected @Nullable String resourcePath;
public FileConfigFactory(@NotNull File file) {
this.file = file;
}
public SELF file(@NotNull File file) {
this.file = file;
return self();
}
public SELF file(@NotNull Path path) {
return file(path.toFile());
}
public SELF file(@NotNull File parent, @NotNull String fileName) {
return file(new File(parent, fileName));
}
public SELF resourcePath(@Nullable String resourcePath) {
this.resourcePath = resourcePath;
return self();
}
}
@@ -2,7 +2,8 @@ package cc.carm.lib.configuration.source;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.option.FileConfigOptions;
import cc.carm.lib.configuration.source.section.ConfigurationSource;
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;
@@ -14,8 +15,8 @@ import java.nio.file.Files;
import java.util.Objects;
import java.util.function.Consumer;
public abstract class FileConfigSource<SELF extends FileConfigSource<SELF, ORIGINAL>, ORIGINAL>
extends ConfigurationSource<SELF, ORIGINAL> {
public abstract class FileConfigSource<SECTION extends ConfigureSection, ORIGINAL, SELF extends FileConfigSource<SECTION, ORIGINAL, SELF>>
extends ConfigureSource<SECTION, ORIGINAL, SELF> {
protected final @NotNull File file;
protected final @Nullable String resourcePath;
@@ -27,6 +28,7 @@ public abstract class FileConfigSource<SELF extends FileConfigSource<SELF, ORIGI
this.resourcePath = resourcePath;
}
public Charset charset() {
return holder().options().get(FileConfigOptions.CHARSET);
}
@@ -56,28 +58,23 @@ public abstract class FileConfigSource<SELF extends FileConfigSource<SELF, ORIGI
}
}
protected <R> R fileInputStream(@NotNull DataFunction<InputStream, R> loader) {
try (FileInputStream is = new FileInputStream(file)) {
protected <R> R fileInputStream(@NotNull DataFunction<InputStream, R> loader) throws Exception {
try (InputStream is = Files.newInputStream(file.toPath())) {
return loader.handle(is);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected <R> R fileReader(@NotNull DataFunction<Reader, R> loader) {
protected <R> R fileReader(@NotNull DataFunction<Reader, R> loader) throws Exception {
return fileInputStream(is -> loader.handle(new InputStreamReader(is, charset())));
}
protected void fileOutputStream(@NotNull Consumer<FileOutputStream> stream) {
try (FileOutputStream os = new FileOutputStream(file)) {
protected void fileOutputStream(@NotNull Consumer<OutputStream> stream) throws Exception {
try (OutputStream os = Files.newOutputStream(file.toPath())) {
stream.accept(os);
} catch (Exception e) {
e.printStackTrace();
}
}
protected void fileWriter(@NotNull Consumer<Writer> writer) {
protected void fileWriter(@NotNull Consumer<Writer> writer) throws Exception {
fileOutputStream(os -> writer.accept(new OutputStreamWriter(os, charset())));
}
+5 -4
View File
@@ -21,11 +21,12 @@
<module>features/commentable</module>
<module>features/file</module>
<!-- <module>demo</module>-->
<!-- <module>providers/yaml</module>-->
<!-- <module>providers/yaml</module>-->
<module>providers/gson</module>
<!-- <module>providers/sql</module>-->
<!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>-->
<!-- <module>providers/hocon</module>-->
<module>demo</module>
</modules>
<name>EasyConfiguration</name>
@@ -1,36 +0,0 @@
package cc.carm.lib.configuration;
import cc.carm.lib.configuration.json.JSONConfigProvider;
import java.io.File;
import java.io.IOException;
public class EasyConfiguration {
private EasyConfiguration() {
}
public static JSONConfigProvider from(File file, String source) {
JSONConfigProvider provider = new JSONConfigProvider(file);
try {
provider.initializeFile(source);
provider.initializeConfig();
} catch (IOException e) {
e.printStackTrace();
}
return provider;
}
public static JSONConfigProvider from(File file) {
return from(file, file.getName());
}
public static JSONConfigProvider from(String fileName) {
return from(fileName, fileName);
}
public static JSONConfigProvider from(String fileName, String source) {
return from(new File(fileName), source);
}
}
@@ -0,0 +1,72 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class JSONConfigFactory extends FileConfigFactory<JSONSource, ConfigurationHolder<JSONSource>, JSONConfigFactory> {
public static JSONConfigFactory from(@NotNull File file) {
return new JSONConfigFactory(file);
}
public static JSONConfigFactory from(@NotNull File parent, @NotNull String configName) {
return new JSONConfigFactory(new File(parent, configName));
}
protected Supplier<Gson> gsonSupplier = () -> JSONSource.DEFAULT_GSON;
public JSONConfigFactory(@NotNull File file) {
super(file);
}
@Override
public JSONConfigFactory self() {
return this;
}
public JSONConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public JSONConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public JSONConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
@Override
public @NotNull ConfigurationHolder<JSONSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
File configFile = this.file;
String sourcePath = this.resourcePath;
return new ConfigurationHolder<JSONSource>(this.adapters, this.options, new ConcurrentHashMap<>(), this.initializer) {
final JSONSource source = new JSONSource(this, 0, configFile, sourcePath, gson);
@Override
public @NotNull JSONSource config() {
return source;
}
};
}
}
@@ -1,73 +0,0 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.LinkedHashMap;
/**
* Some code comes from BungeeCord's implementation of the JsonConfiguration.
*
* @author md_5, CarmJos
*/
public class JSONConfigProvider extends FileConfigProvider<JSONConfigWrapper> {
protected JSONConfigWrapper configuration;
protected ConfigInitializer<JSONConfigProvider> initializer;
public JSONConfigProvider(@NotNull File file) {
super(file);
this.initializer = new ConfigInitializer<>(this);
}
public void initializeConfig() {
onReload();
}
@Override
public @NotNull JSONConfigWrapper getConfiguration() {
return this.configuration;
}
@Override
protected void onReload() {
LinkedHashMap<?, ?> map = null;
try (FileInputStream is = new FileInputStream(file)) {
map = gson.fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), LinkedHashMap.class);
} catch (IOException e) {
e.printStackTrace();
}
if (map == null) map = new LinkedHashMap<>();
this.configuration = new JSONConfigWrapper(map);
}
@Override
public @Nullable ConfigurationComments getComments() {
return null;
}
@Override
public void save() throws Exception {
try (Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8)) {
gson.toJson(configuration.data, writer);
}
}
@Override
public @NotNull ConfigInitializer<? extends ConfigurationProvider<JSONConfigWrapper>> getInitializer() {
return this.initializer;
}
}
@@ -1,109 +0,0 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigSource;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JSONConfigSource extends FileConfigSource<JSONConfigSource, JsonObject> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
JSONConfigWrapper.class,
(JsonSerializer<JSONConfigWrapper>) (src, typeOfSrc, context) -> context.serialize(src.data)
).create();
protected final @NotNull Gson gson;
protected @Nullable JsonObject original;
protected JSONConfigSource(@NotNull ConfigurationHolder<? extends JSONConfigSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) {
super(holder, lastUpdateMillis, file, resourcePath);
this.gson = gson;
}
public void initialize() {
try {
initializeFile();
} catch (Exception e) {
e.printStackTrace();
}
}
public void initializeJson() {
this.original = fileReader(reader -> gson.fromJson(reader, JsonObject.class));
}
@Override
protected JSONConfigSource self() {
return this;
}
@Override
public @NotNull JsonObject original() {
return null;
}
@Override
public void save() throws Exception {
}
@Override
protected void onReload() throws Exception {
}
@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 ConfigurationSection getSection(@NotNull String path) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
}
@@ -1,31 +1,27 @@
package cc.carm.lib.configuration.json;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Some code comes from BungeeCord's implementation of the JsonConfiguration.
*
* @author md_5, CarmJos
*/
public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Object>> {
public class JSONSection implements ConfigureSection {
private static final char SEPARATOR = '.';
protected final ConfigureSource<? extends JSONSection, ?, ?> source;
protected final Map<String, Object> data;
JSONConfigWrapper(Map<?, ?> map) {
public JSONSection(@NotNull ConfigureSource<? extends JSONSection, ?, ?> source,
@NotNull Map<?, ?> data) {
this.source = source;
this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, new JSONConfigWrapper((Map<?, ?>) entry.getValue()));
this.data.put(key, new JSONSection(source, (Map<?, ?>) entry.getValue()));
} else {
this.data.put(key, entry.getValue());
}
@@ -33,7 +29,11 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
}
@Override
public @NotNull Map<String, Object> getSource() {
public @NotNull ConfigureSource<? extends JSONSection, ?, ?> source() {
return this.source;
}
public Map<String, Object> data() {
return this.data;
}
@@ -54,10 +54,10 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) {
value = new JSONConfigWrapper((Map<?, ?>) value);
value = new JSONSection(source(), (Map<?, ?>) value);
}
JSONConfigWrapper section = getSectionFor(path);
JSONSection section = getSectionFor(path);
if (section == this) {
if (value == null) {
this.data.remove(path);
@@ -76,7 +76,7 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
@Override
public @Nullable Object get(@NotNull String path) {
JSONConfigWrapper section = getSectionFor(path);
JSONSection section = getSectionFor(path);
return section == this ? data.get(path) : section.get(getChild(path));
}
@@ -92,44 +92,44 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
}
@Override
public boolean isConfigurationSection(@NotNull String path) {
return get(path) instanceof JSONConfigWrapper;
public boolean isSection(@NotNull String path) {
return get(path) instanceof JSONSection;
}
@Override
public @Nullable JSONConfigWrapper getConfigurationSection(@NotNull String path) {
public @Nullable ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof JSONConfigWrapper) ? (JSONConfigWrapper) val : null;
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
private JSONConfigWrapper getSectionFor(String path) {
int index = path.indexOf(SEPARATOR);
private JSONSection getSectionFor(String path) {
int index = path.indexOf(separator());
if (index == -1) return this;
String root = path.substring(0, index);
Object section = this.data.get(root);
if (section == null) {
section = new JSONConfigWrapper(new LinkedHashMap<>());
section = new JSONSection(source(), new LinkedHashMap<>());
this.data.put(root, section);
}
return (JSONConfigWrapper) section;
return (JSONSection) section;
}
private String getChild(String path) {
int index = path.indexOf(SEPARATOR);
int index = path.indexOf(separator());
return (index == -1) ? path : path.substring(index + 1);
}
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull JSONConfigWrapper section,
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull JSONSection section,
@Nullable String parent, boolean deep) {
for (Map.Entry<String, Object> entry : section.data.entrySet()) {
String path = (parent == null ? "" : parent + ".") + entry.getKey();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + separator()) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof JSONConfigWrapper) {
this.mapChildrenValues(output, (JSONConfigWrapper) entry.getValue(), path, true);
if (deep && entry.getValue() instanceof JSONSection) {
this.mapChildrenValues(output, (JSONSection) entry.getValue(), path, true);
}
}
}
@@ -0,0 +1,80 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigSource;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.*;
public class JSONSource extends FileConfigSource<JSONSection, Map<?, ?>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
JSONSection.class,
(JsonSerializer<JSONSection>) (src, typeOfSrc, context) -> context.serialize(src.data)
).create();
protected final @NotNull Gson gson;
protected @Nullable JSONSection section;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath) {
this(holder, lastUpdateMillis, file, resourcePath, DEFAULT_GSON);
}
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) {
super(holder, lastUpdateMillis, file, resourcePath);
this.gson = gson;
initialize();
}
public void initialize() {
try {
initializeFile();
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected JSONSource self() {
return this;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return this;
}
@Override
public @NotNull Map<?, ?> original() {
return section().data();
}
@Override
public @NotNull JSONSection section() {
return Objects.requireNonNull(this.section, "Section is not initialized");
}
@Override
public void save() throws Exception {
fileWriter(writer -> gson.toJson(original(), writer));
}
@Override
protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class));
if (data == null) data = new LinkedHashMap<>();
this.section = new JSONSection(this, data);
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
}
}
@@ -1,28 +1,26 @@
package config;
import cc.carm.lib.configuration.EasyConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.json.JSONConfigProvider;
import cc.carm.lib.configuration.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.junit.Test;
import java.io.File;
public class JSONConfigTest {
protected final JSONConfigProvider provider = EasyConfiguration.from("target/config.json", "config.json");
protected final ConfigurationHolder<?> holder = JSONConfigFactory
.from(new File("target"), "config.json")
.option(StandardOptions.PATH_SEPARATOR, '-')
.build();
@Test
public void onTest() {
ConfigurationTest.testDemo(this.holder);
ConfigurationTest.testInner(this.holder);
ConfigurationTest.testDemo(this.provider);
ConfigurationTest.testInner(this.provider);
System.out.println("----------------------------------------------------");
provider.getConfiguration().getValues(true).forEach((k, v) -> System.out.println(k + ": " + v));
System.out.println("----------------------------------------------------");
provider.getConfiguration().getValues(false).forEach((k, v) -> System.out.println(k + ": " + v));
System.out.println("----------------------------------------------------");
ConfigurationTest.save(this.provider);
ConfigurationTest.save(this.holder);
}