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

feat(sql): Support SQL sources

This commit is contained in:
2025-03-03 18:51:21 +08:00
parent 844cbfab53
commit a699f6c164
13 changed files with 107 additions and 89 deletions
@@ -4,6 +4,8 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
@@ -29,7 +31,6 @@ public abstract class ValueType<T> {
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
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/**
* Whether to pre parse the config values.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* Whether to pre parse the config values,
* may good to set true if you want to keep the config format.
* <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}.
* <br> if false, the values will be parsed when calling
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
*/
ConfigurationOption<Boolean> PRELOAD = of(false);
ConfigurationOption<Boolean> PRELOAD = of(true);
}
@@ -4,6 +4,7 @@ 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.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -108,15 +109,16 @@ public class ValueManifest<T> {
return holder().metadata(path());
}
@ApiStatus.Internal
protected Object getData() {
return config().get(path());
}
@ApiStatus.Internal
protected void setData(@Nullable Object value) {
config().set(path(), value);
}
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
};
+13
View File
@@ -33,6 +33,19 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-versioned</artifactId>
<version>4.0.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -25,6 +25,7 @@ public interface DatabaseConfiguration extends Configuration {
ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
static String buildJDBC() {
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST);
System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after);
TEST.INSTANCE.STATUS.set(after);
System.out.println("> Test Inner value after:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve());
System.out.println(TEST.INSTANCE.STATUS.resolve());
}
@@ -1,11 +1,7 @@
package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath;
import cc.carm.lib.configuration.annotation.FooterComments;
import cc.carm.lib.configuration.annotation.HeaderComments;
import cc.carm.lib.configuration.annotation.InlineComment;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.annotation.*;
import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue;
@@ -29,7 +25,8 @@ import java.util.UUID;
public interface DemoConfiguration extends Configuration {
@ConfigPath(root = true)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 1.0D);
@ConfigVersion(2)
ConfigValue<Double> VERSION = ConfiguredValue.of(Double.class, 2.0D);
@ConfigPath(root = true)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
@@ -39,10 +36,6 @@ public interface DemoConfiguration extends Configuration {
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。
// 注意: 若对应类也有注解,则优先使用类的注解。
Class<?> DATABASE = DatabaseConfiguration.class;
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test")
public class InstanceConfig implements Configuration {
public final ConfigValue<Double> INNER_VALUE = ConfiguredValue.of(1.0D);
public final ConfigValue<Double> STATUS = ConfiguredValue.of(1.0D);
}
@@ -20,7 +20,7 @@ public class RegistryConfig implements Configuration {
@FooterComments({"12313213212"})
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。
public final ConfigValue<UserRecord> TEST_MODEL = ConfiguredValue.builderOf(UserRecord.class).fromSection()
public final ConfigValue<UserRecord> OWNER = ConfiguredValue.builderOf(UserRecord.class).fromSection()
.defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build();
@@ -5,6 +5,7 @@ import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
import cc.carm.lib.easysql.api.SQLManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -79,34 +80,19 @@ public class SQLConfigFactory extends ConfigurationFactory<SQLSource, Configurat
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser) {
return resolver(id, new SQLValueResolver<T>(type) {
@Override
public @NotNull T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return parser.handle(data);
}
});
return resolver(id, SQLValueResolver.of(type, parser));
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<Object, String> serializer) {
@NotNull DataFunction<T, String> serializer) {
return resolver(id, ValueType.of(clazz), parser, serializer);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser,
@NotNull DataFunction<Object, String> serializer) {
return resolver(id, new SQLValueResolver<T>(type) {
@Override
public @NotNull T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return parser.handle(data);
}
@Override
public @NotNull String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) throws Exception {
return serializer.handle(value);
}
});
@NotNull DataFunction<T, String> serializer) {
return resolver(id, SQLValueResolver.of(type, parser, serializer));
}
public SQLConfigFactory tableName(@NotNull String tableName) {
@@ -128,6 +114,7 @@ public class SQLConfigFactory extends ConfigurationFactory<SQLSource, Configurat
if (manager == null) throw new NullPointerException("No SQLManager instance provided.");
Commentable.registerMeta(this.initializer);
VersionedMetaTypes.register(this.initializer);
return new ConfigurationHolder<SQLSource>(this.adapters, this.options, this.metadata, this.initializer) {
final SQLSource source = new SQLSource(
@@ -1,6 +1,12 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface SQLOptions {
/**
* Whether to purge the configuration's in-database data when saving.
*/
ConfigurationOption<Boolean> PURGE = ConfigurationOption.of( true);
}
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
@@ -93,6 +92,10 @@ public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Objec
return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
}
public int purge() throws Exception {
return this.table.createDelete().addCondition("namespace", namespace).build().execute();
}
@Override
public void save() throws Exception {
LocalDateTime time = LocalDateTime.now(); // Update time
@@ -102,29 +105,29 @@ public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Objec
Map<String, ConfigValue<?>> values = holder().registeredValues();
for (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {
@NotNull String path = entry.getKey();
@NotNull ConfigValue<?> value = entry.getValue();
@NotNull ConfigValue<?> config = entry.getValue();
@Nullable Object value = section.get(path);
try {
int typeID = typeOf(entry.getValue());
String data = null;
if (value != null) {
if (value instanceof SourcedSection) {
data = serialize(typeID, ((SourcedSection) value).rawMap());
} else if (value instanceof List) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) value) {
if (obj instanceof SourcedSection) {
list.add(((SourcedSection) obj).rawMap());
} else {
list.add(obj);
}
}
data = serialize(typeID, list);
if (value instanceof SourcedSection) {
value = ((SourcedSection) value).rawMap();
} else if (value instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) value) {
if (obj instanceof SourcedSection) {
list.add(((SourcedSection) obj).rawMap());
} else {
data = serialize(typeID, value);
list.add(obj);
}
}
value = list;
}
if (value == null) continue;
try {
int typeID = typeIdOf(value);
String data = serialize(typeID, value);
if (data == null) continue;
int version = holder().metadata(path).get(VersionedMetaTypes.VERSION, 0);
dataValues.add(new Object[]{
@@ -138,6 +141,9 @@ public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Objec
}
}
if (holder.option(SQLOptions.PURGE)) {
purge();
}
this.table.createReplaceBatch()
.setColumnNames(
"namespace", "path", "update_time", "version", "type", "value",
@@ -157,12 +163,8 @@ public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Objec
if (path == null) continue; // Path should be not null
int ver = rs.getInt("version");
try {
Object val = parse(rs.getInt("type"), rs.getString("value"));
System.out.println("Path <" + path + "> Value = " + val);
loaded.put(path, val);
if (ver != 0) {
holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
}
loaded.put(path, parse(rs.getInt("type"), rs.getString("value")));
if (ver != 0) holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
} catch (Exception e) {
e.printStackTrace();
}
@@ -175,20 +177,20 @@ public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Objec
protected @Nullable Object parse(int type, String value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.resolve(holder(), value);
return function.resolve(this, value);
}
protected @Nullable String serialize(int type, @NotNull Object value) throws Exception {
SQLValueResolver<?> function = this.resolvers.get(type);
if (function == null) throw new IllegalStateException("No resolvers for type #" + type);
return function.serialize(holder(), value);
return function.serialize(this, value);
}
protected int typeOf(@NotNull ValueType<?> value) {
protected int typeIdOf(@NotNull Object value) {
return this.resolvers.entrySet().stream()
.filter(entry -> entry.getValue().isTypeOf(value))
.filter(entry -> entry.getValue().isInstance(value))
.findFirst().map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value));
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value.getClass().getName()));
}
@@ -2,7 +2,6 @@ package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataFunction;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,26 +25,26 @@ public abstract class SQLValueResolver<T> {
public static final @NotNull SQLValueResolver<List<?>> LIST = new SQLValueResolver<List<?>>(new ValueType<List<?>>() {
}) {
@Override
public @Nullable List<?> resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return holder.config().gson().fromJson(data, List.class);
public @Nullable List<?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, List.class);
}
@Override
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) {
return holder.config().gson().toJson(value);
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull SQLValueResolver<Map<?, ?>> MAP = new SQLValueResolver<Map<?, ?>>(new ValueType<Map<?, ?>>() {
}) {
@Override
public @Nullable Map<?, ?> resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
return holder.config().gson().fromJson(data, LinkedHashMap.class);
public @Nullable Map<?, ?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, LinkedHashMap.class);
}
@Override
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) {
return holder.config().gson().toJson(value);
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
@@ -70,12 +69,29 @@ public abstract class SQLValueResolver<T> {
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type, @NotNull DataFunction<String, V> resolver) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception {
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
};
}
public static <V> SQLValueResolver<V> of(@NotNull ValueType<V> type,
@NotNull DataFunction<String, V> resolver,
@NotNull DataFunction<V, String> serializer) {
return new SQLValueResolver<V>(type) {
@Override
public @NotNull V resolve(@NotNull SQLSource source, String data) throws Exception {
return resolver.handle(data);
}
@Override
public @NotNull String serialize(@NotNull SQLSource source, Object value) throws Exception {
return serializer.handle(type.cast(value));
}
};
}
protected final @NotNull ValueType<T> type;
protected SQLValueResolver(@NotNull ValueType<T> type) {
@@ -86,17 +102,13 @@ public abstract class SQLValueResolver<T> {
return type;
}
public boolean isTypeOf(@NotNull Class<?> clazz) {
return type.isSubtypeOf(clazz);
public boolean isInstance(@NotNull Object obj) {
return getType().isInstance(obj);
}
public boolean isTypeOf(@NotNull ValueType<?> valueType) {
return valueType.equals(type);
}
public abstract @Nullable T resolve(@NotNull SQLSource source, String data) throws Exception;
public abstract @Nullable T resolve(@NotNull ConfigurationHolder<? extends SQLSource> holder, String data) throws Exception;
public @Nullable String serialize(@NotNull ConfigurationHolder<? extends SQLSource> holder, Object value) throws Exception {
public @Nullable String serialize(@NotNull SQLSource source, Object value) throws Exception {
return String.valueOf(value);
}