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

Compare commits

..

8 Commits

49 changed files with 1006 additions and 257 deletions
+1 -1
View File
@@ -259,7 +259,7 @@ Many thanks to Jetbrains for kindly providing a license for us to work on this a
Many thanks to [ArtformGames](https://github.com/ArtformGames) for their Many thanks to [ArtformGames](https://github.com/ArtformGames) for their
strong support and active contribution to this project! strong support and active contribution to this project!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/refs/heads/master/logo/logo_full.png" alt="ArtfromGames" width="500"> <img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## Open Source License ## Open Source License
+1 -1
View File
@@ -280,7 +280,7 @@ EasyConfiguration for MineCraft!
万分感谢来自 [ArtformGames](https://github.com/ArtformGames) 对本项目的大力支持与积极贡献! 万分感谢来自 [ArtformGames](https://github.com/ArtformGames) 对本项目的大力支持与积极贡献!
<img src="https://raw.githubusercontent.com/ArtformGames/.github/refs/heads/master/logo/logo_full.png" alt="ArtfromGames" width="500"> <img src="https://raw.githubusercontent.com/ArtformGames/.github/master/logo/logo_full.svg" width="317px" height="117px" alt="ArtformGames">
## 开源协议 ## 开源协议
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.6</version> <version>4.0.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -157,7 +158,7 @@ public abstract class ValueType<T> {
ParameterizedType pt = (ParameterizedType) type; ParameterizedType pt = (ParameterizedType) type;
Type raw = pt.getRawType(); Type raw = pt.getRawType();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(raw.getTypeName()); sb.append(raw.getClass().getName());
sb.append('<'); sb.append('<');
Type[] args = pt.getActualTypeArguments(); Type[] args = pt.getActualTypeArguments();
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@@ -166,6 +167,7 @@ public abstract class ValueType<T> {
} }
sb.append(args[i].getTypeName()); sb.append(args[i].getTypeName());
} }
sb.append('>');
return sb.toString(); return sb.toString();
} }
return type.getTypeName(); return type.getTypeName();
@@ -6,9 +6,11 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.loader.ConfigurationInitializer; import cc.carm.lib.configuration.source.loader.ConfigurationInitializer;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata; import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
import cc.carm.lib.configuration.source.meta.StandardMeta;
import cc.carm.lib.configuration.source.option.ConfigurationOption; import cc.carm.lib.configuration.source.option.ConfigurationOption;
import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder; import cc.carm.lib.configuration.source.option.ConfigurationOptionHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.ValueManifest; import cc.carm.lib.configuration.value.ValueManifest;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -18,6 +20,8 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> { public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, SOURCE>> {
@@ -51,7 +55,7 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
return options; return options;
} }
public <O> O option(@NotNull ConfigurationOption<O> option) { public <O> @NotNull O option(@NotNull ConfigurationOption<O> option) {
return options().get(option); return options().get(option);
} }
@@ -66,14 +70,26 @@ public abstract class ConfigurationHolder<SOURCE extends ConfigureSource<?, ?, S
@NotNull @NotNull
@UnmodifiableView @UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) { public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type) {
return extractMetadata(type, Objects::nonNull);
}
@NotNull
@UnmodifiableView
public <M> Map<String, M> extractMetadata(@NotNull ConfigurationMetadata<M> type, @NotNull Predicate<M> filter) {
Map<String, M> metas = new LinkedHashMap<>(); Map<String, M> metas = new LinkedHashMap<>();
for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) { for (Map.Entry<String, ConfigurationMetaHolder> entry : this.metadata.entrySet()) {
M data = entry.getValue().get(type); M data = entry.getValue().get(type);
if (data != null) metas.put(entry.getKey(), data); if (filter.test(data)) metas.put(entry.getKey(), data);
} }
return Collections.unmodifiableMap(metas); return Collections.unmodifiableMap(metas);
} }
@NotNull
@UnmodifiableView
public Map<String, ConfigValue<?>> registeredValues() {
return extractMetadata(StandardMeta.VALUE);
}
public ValueAdapterRegistry adapters() { public ValueAdapterRegistry adapters() {
return this.adapters; return this.adapters;
} }
@@ -5,26 +5,27 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@FunctionalInterface @FunctionalInterface
public interface ConfigInitializeHandler<T> { public interface ConfigInitializeHandler<T, V> {
static <T> ConfigInitializeHandler<T> start() { static <T, V> ConfigInitializeHandler<T, V> start() {
return (provider, path, value) -> { return (provider, path, value, instace) -> {
}; };
} }
void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path, @NotNull T value) throws Exception; void whenInitialize(@NotNull ConfigurationHolder<?> holder, @Nullable String path,
@NotNull T value, @Nullable V instance) throws Exception;
default ConfigInitializeHandler<T> andThen(ConfigInitializeHandler<T> after) { default ConfigInitializeHandler<T, V> andThen(ConfigInitializeHandler<T, V> after) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
after.whenInitialize(provider, path, value); after.whenInitialize(provider, path, value, instance);
}; };
} }
default ConfigInitializeHandler<T> compose(ConfigInitializeHandler<T> before) { default ConfigInitializeHandler<T, V> compose(ConfigInitializeHandler<T, V> before) {
return (provider, path, value) -> { return (provider, path, value, instance) -> {
before.whenInitialize(provider, path, value); before.whenInitialize(provider, path, value, instance);
whenInitialize(provider, path, value); whenInitialize(provider, path, value, instance);
}; };
} }
@@ -21,18 +21,18 @@ import java.util.function.Function;
public class ConfigurationInitializer { public class ConfigurationInitializer {
protected @NotNull PathGenerator pathGenerator; protected @NotNull PathGenerator pathGenerator;
protected @NotNull ConfigInitializeHandler<Field> fieldInitializer; protected @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer;
protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer; protected @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer;
public ConfigurationInitializer() { public ConfigurationInitializer() {
this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start()); this(PathGenerator.of(), ConfigInitializeHandler.start(), ConfigInitializeHandler.start());
} }
public ConfigurationInitializer(@NotNull PathGenerator pathGenerator, public ConfigurationInitializer(@NotNull PathGenerator pathGenerator,
@NotNull ConfigInitializeHandler<Field> fieldInitializer, @NotNull ConfigInitializeHandler<Field, ConfigValue<?>> valueInitializer,
@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { @NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.pathGenerator = pathGenerator; this.pathGenerator = pathGenerator;
this.fieldInitializer = fieldInitializer; this.valueInitializer = valueInitializer;
this.classInitializer = classInitializer; this.classInitializer = classInitializer;
} }
@@ -44,34 +44,34 @@ public class ConfigurationInitializer {
return pathGenerator; return pathGenerator;
} }
public ConfigInitializeHandler<Field> fieldInitializer() { public ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer() {
return fieldInitializer; return valueInitializer;
} }
public void fieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void fieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = fieldInitializer; this.valueInitializer = fieldInitializer;
} }
public ConfigInitializeHandler<Class<? extends Configuration>> classInitializer() { public ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer() {
return classInitializer; return classInitializer;
} }
public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { public void classInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = classInitializer; this.classInitializer = classInitializer;
} }
public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field> fieldInitializer) { public void appendFieldInitializer(@NotNull ConfigInitializeHandler<Field, ConfigValue<?>> fieldInitializer) {
this.fieldInitializer = this.fieldInitializer.andThen(fieldInitializer); this.valueInitializer = this.valueInitializer.andThen(fieldInitializer);
} }
public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>> classInitializer) { public void appendClassInitializer(@NotNull ConfigInitializeHandler<Class<? extends Configuration>, Object> classInitializer) {
this.classInitializer = this.classInitializer.andThen(classInitializer); this.classInitializer = this.classInitializer.andThen(classInitializer);
} }
public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerClassAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendClassInitializer((holder, path, clazz) -> { appendClassInitializer((holder, path, clazz, instance) -> {
A data = clazz.getAnnotation(annotation); A data = clazz.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -81,7 +81,7 @@ public class ConfigurationInitializer {
public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation, public <T, A extends Annotation> void registerFieldAnnotation(@NotNull Class<A> annotation,
@NotNull ConfigurationMetadata<T> metadata, @NotNull ConfigurationMetadata<T> metadata,
@NotNull Function<A, T> extractor) { @NotNull Function<A, T> extractor) {
appendFieldInitializer((holder, path, field) -> { appendFieldInitializer((holder, path, field, instance) -> {
A data = field.getAnnotation(annotation); A data = field.getAnnotation(annotation);
if (data == null) return; if (data == null) return;
holder.metadata(path).setIfAbsent(metadata, extractor.apply(data)); holder.metadata(path).setIfAbsent(metadata, extractor.apply(data));
@@ -123,7 +123,7 @@ public class ConfigurationInitializer {
@Nullable String parentPath, @Nullable Field configField) { @Nullable String parentPath, @Nullable Field configField) {
String path = getClassPath(holder, parentPath, root.getClass(), configField); String path = getClassPath(holder, parentPath, root.getClass(), configField);
try { try {
this.classInitializer.whenInitialize(holder, path, root.getClass()); this.classInitializer.whenInitialize(holder, path, root.getClass(), root);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -140,7 +140,7 @@ public class ConfigurationInitializer {
String path = getClassPath(holder, parentPath, clazz, configField); String path = getClassPath(holder, parentPath, clazz, configField);
try { try {
this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz); this.classInitializer.whenInitialize(holder, path, (Class<? extends Configuration>) clazz, configField);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -169,15 +169,15 @@ public class ConfigurationInitializer {
String path = getFieldPath(holder, parent, field); String path = getFieldPath(holder, parent, field);
if (path == null) return; if (path == null) return;
value.initialize(holder, path); value.initialize(holder, path);
holder.metadata(path).set(StandardMeta.UNIT, true); // Mark the minimal config value unit. holder.metadata(path).set(StandardMeta.VALUE, value); // Mark the minimal config value unit.
try {
this.fieldInitializer.whenInitialize(holder, path, field);
} catch (Exception e) {
e.printStackTrace();
}
if (holder.option(StandardOptions.SET_DEFAULTS)) { if (holder.option(StandardOptions.SET_DEFAULTS)) {
value.setDefault(); // Set default value. value.setDefault(); // Set default value.
} }
try {
this.valueInitializer.whenInitialize(holder, path, field, value);
} catch (Exception e) {
e.printStackTrace();
}
if (holder.option(StandardOptions.PRELOAD)) { if (holder.option(StandardOptions.PRELOAD)) {
value.get(); // Preload the value by calling #get method. value.get(); // Preload the value by calling #get method.
} }
@@ -1,10 +1,12 @@
package cc.carm.lib.configuration.source.meta; package cc.carm.lib.configuration.source.meta;
import cc.carm.lib.configuration.value.ConfigValue;
public interface StandardMeta { public interface StandardMeta {
/** /**
* To mark the {@link cc.carm.lib.configuration.value.ConfigValue} as a minimal unit path. * To mark the {@link ConfigValue} instance of specific path.
*/ */
ConfigurationMetadata<Boolean> UNIT = ConfigurationMetadata.of(false); ConfigurationMetadata<ConfigValue<?>> VALUE = ConfigurationMetadata.of();
} }
@@ -23,12 +23,13 @@ public interface StandardOptions {
ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true); ConfigurationOption<Boolean> LOAD_SUB_CLASSES = of(true);
/** /**
* Whether to pre parse the config values. * Whether to pre parse the config values,
* <br> if false, the values will be parsed when calling * may good to set true if you want to keep the config format.
* {@link cc.carm.lib.configuration.value.ConfigValue#get()}
* <br> if true, the values will be parsed when * <br> if true, the values will be parsed when
* {@link ConfigurationHolder#initialize(Configuration)}. * {@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);
} }
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.UnmodifiableView;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -30,6 +31,24 @@ public interface ConfigureSection {
@Contract(pure = true) @Contract(pure = true)
@Nullable ConfigureSection parent(); @Nullable ConfigureSection parent();
/**
* Get the current section's path from {@link #parent()} of this section.
*
* @return The current path of this section, if {@link #isRoot()}, return empty string.
*/
@NotNull String path();
/**
* Get the full path of this section.
*
* @return The full path of this section, if {@link #isRoot()}, return empty string.
*/
default @NotNull String fullPath() {
if (parent() == null) return "";
return (parent().isRoot() ? "" : parent().fullPath() + pathSeparator()) + path();
}
/** /**
* Get the path separator for the section. * Get the path separator for the section.
* *
@@ -44,6 +63,7 @@ public interface ConfigureSection {
* *
* @return True if this section is a root section, false otherwise. * @return True if this section is a root section, false otherwise.
*/ */
@Contract(pure = true)
default boolean isRoot() { default boolean isRoot() {
return parent() == null; return parent() == null;
} }
@@ -57,6 +77,15 @@ public interface ConfigureSection {
return getValues(false).isEmpty(); return getValues(false).isEmpty();
} }
/**
* Gets the number of keys in this section.
*
* @return Number of keys in this section.
*/
default int size(boolean deep) {
return getKeys(deep).size();
}
/** /**
* Gets a set containing all keys in this section. * Gets a set containing all keys in this section.
* <p> * <p>
@@ -238,11 +267,39 @@ public interface ConfigureSection {
/** /**
* Creates a new {@link ConfigureSection} with specified values. * Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section. * <p> The {@link #parent()} of the new section will be this section.
* <p> This section will not be saved until {@link #set(String, Object)} is called.
* <p> If you want to create and use a section and set it to this section, use {@link #computeSection(String)}.
* *
* @param data The data to be used to create section. * @param data The data to be used to create section.
* @return Newly created section * @return Newly created section
*/ */
@NotNull ConfigureSection createSection(@NotNull Map<?, ?> data); @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data);
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return createSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Creates a new {@link ConfigureSection} with specified values.
* <p> The {@link #parent()} of the new section will be this section.
*
* @param data The data to be used to create section.
* @return Newly created section
*/
default @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return createSection(path, data.get());
}
/** /**
* Creates a new empty {@link ConfigureSection}. * Creates a new empty {@link ConfigureSection}.
@@ -250,8 +307,8 @@ public interface ConfigureSection {
* *
* @return Newly created section * @return Newly created section
*/ */
default @NotNull ConfigureSection createSection() { default @NotNull ConfigureSection createSection(@NotNull String path) {
return createSection(new LinkedHashMap<>()); return createSection(path, new LinkedHashMap<>());
} }
/** /**
@@ -262,17 +319,64 @@ public interface ConfigureSection {
* *
* @param path The path to get the section from. * @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist. * @return The section at the path, or a new section if it does not exist.
* @see #createSection() * @see #createSection(String, Map)
*/ */
default @NotNull ConfigureSection computeSection(@NotNull String path) { default @NotNull ConfigureSection computeSection(@NotNull String path) {
return computeSection(path, new LinkedHashMap<>());
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Map<?, ?> data) {
ConfigureSection current = getSection(path); ConfigureSection current = getSection(path);
if (current == null) { if (current == null) {
current = createSection(); current = createSection(path, data);
set(path, current); set(path, current);
} }
return current; return current;
} }
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Consumer<Map<String, Object>> data) {
return computeSection(path, () -> {
Map<String, Object> map = new LinkedHashMap<>();
data.accept(map);
return map;
});
}
/**
* Get or create a section at the given path.
* <p>
* Any value previously set at this path will be replaced if it is not a section
* or will be returned if it is an exists {@link ConfigureSection}.
*
* @param path The path to get the section from.
* @return The section at the path, or a new section if it does not exist.
* @see #createSection(String, Map)
*/
default @NotNull ConfigureSection computeSection(@NotNull String path, @NotNull Supplier<Map<String, Object>> data) {
return computeSection(path, data.get());
}
/** /**
* Get the origin value of the path. * Get the origin value of the path.
* *
@@ -92,6 +92,11 @@ public abstract class ConfigureSource<
return null; return null;
} }
@Override
public @NotNull String path() {
return "";
}
@Override @Override
public @NotNull Map<String, Object> getValues(boolean deep) { public @NotNull Map<String, Object> getValues(boolean deep) {
return section().getValues(deep); return section().getValues(deep);
@@ -103,8 +108,8 @@ public abstract class ConfigureSource<
} }
@Override @Override
public @NotNull ConfigureSection createSection(@NotNull Map<?, ?> data) { public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return section().createSection(data); return section().createSection(path, data);
} }
@Override @Override
@@ -4,6 +4,7 @@ import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder; import cc.carm.lib.configuration.source.meta.ConfigurationMetaHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -20,7 +21,6 @@ public class ValueManifest<T> {
protected @NotNull Supplier<@Nullable T> defaultSupplier; protected @NotNull Supplier<@Nullable T> defaultSupplier;
public ValueManifest(@NotNull ValueType<T> type) { public ValueManifest(@NotNull ValueType<T> type) {
this(type, () -> null, EMPTY_INITIALIZER, null, null); this(type, () -> null, EMPTY_INITIALIZER, null, null);
} }
@@ -93,12 +93,12 @@ public class ValueManifest<T> {
public @NotNull String path() { public @NotNull String path() {
if (path != null) return path; if (path != null) return path;
else throw new IllegalStateException("No section path provided."); else throw new IllegalStateException("No section path provided for Value(" + type() + ").");
} }
public @NotNull ConfigurationHolder<?> holder() { public @NotNull ConfigurationHolder<?> holder() {
if (this.holder != null) return this.holder; if (this.holder != null) return this.holder;
throw new IllegalStateException("Value does not have a provider."); throw new IllegalStateException("Value(" + type() + ") does not have a provider.");
} }
public @NotNull ConfigureSource<?, ?, ?> config() { public @NotNull ConfigureSource<?, ?, ?> config() {
@@ -109,16 +109,17 @@ public class ValueManifest<T> {
return holder().metadata(path()); return holder().metadata(path());
} }
@ApiStatus.Internal
protected Object getData() { protected Object getData() {
return config().get(path()); return config().get(path());
} }
@ApiStatus.Internal
protected void setData(@Nullable Object value) { protected void setData(@Nullable Object value) {
config().set(path(), value); config().set(path(), value);
} }
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, valuePath) -> {
private static final @NotNull BiConsumer<@NotNull ConfigurationHolder<?>, @NotNull String> EMPTY_INITIALIZER = (provider, path) -> {
}; };
} }
+14 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.6</version> <version>4.0.8</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
@@ -33,6 +33,19 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </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> </dependencies>
<build> <build>
@@ -7,7 +7,7 @@ import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.value.standard.ConfiguredValue; import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"}) @HeaderComments({"", "数据库配置", " 用于提供数据库连接,进行数据库操作。"})
public class DatabaseConfiguration implements Configuration { public interface DatabaseConfiguration extends Configuration {
@ConfigPath("driver") @ConfigPath("driver")
@HeaderComments({ @HeaderComments({
@@ -16,18 +16,19 @@ public class DatabaseConfiguration implements Configuration {
"- MySQL(新): com.mysql.cj.jdbc.Driver", "- MySQL(新): com.mysql.cj.jdbc.Driver",
"- MariaDB(推荐): org.mariadb.jdbc.Driver", "- MariaDB(推荐): org.mariadb.jdbc.Driver",
}) })
protected static final ConfigValue<String> DRIVER_NAME = ConfiguredValue.of( ConfigValue<String> DRIVER_NAME = ConfiguredValue.of(
String.class, "com.mysql.cj.jdbc.Driver" String.class, "com.mysql.cj.jdbc.Driver"
); );
protected static final ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1"); ConfigValue<String> HOST = ConfiguredValue.of(String.class, "127.0.0.1");
protected static final ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306); ConfigValue<Integer> PORT = ConfiguredValue.of(Integer.class, 3306);
protected static final ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft"); ConfigValue<String> DATABASE = ConfiguredValue.of(String.class, "minecraft");
protected static final ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root"); ConfigValue<String> USERNAME = ConfiguredValue.of(String.class, "root");
protected static final ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password"); ConfigValue<String> PASSWORD = ConfiguredValue.of(String.class, "password");
protected static final ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
protected static String buildJDBC() { ConfigValue<String> EXTRA = ConfiguredValue.of(String.class, "?useSSL=false");
static String buildJDBC() {
return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get()); return String.format("jdbc:mysql://%s:%s/%s%s", HOST.get(), PORT.get(), DATABASE.get(), EXTRA.get());
} }
@@ -69,14 +69,14 @@ public class ConfigurationTest {
provider.initialize(TEST); provider.initialize(TEST);
System.out.println("> Test Inner value before:"); System.out.println("> Test Inner value before:");
System.out.println(TEST.INSTANCE.INNER_VALUE.resolve()); System.out.println(TEST.INSTANCE.STATUS.resolve());
double after = Math.random() * 200D; double after = Math.random() * 200D;
System.out.println("> Test Inner value -> " + after); System.out.println("> Test Inner value -> " + after);
TEST.INSTANCE.INNER_VALUE.set(after); TEST.INSTANCE.STATUS.set(after);
System.out.println("> Test Inner value 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; package cc.carm.lib.configuration.demo.tests.conf;
import cc.carm.lib.configuration.Configuration; import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.annotation.ConfigPath; import cc.carm.lib.configuration.annotation.*;
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.demo.tests.model.ItemStack; import cc.carm.lib.configuration.demo.tests.model.ItemStack;
import cc.carm.lib.configuration.demo.tests.model.UserRecord; import cc.carm.lib.configuration.demo.tests.model.UserRecord;
import cc.carm.lib.configuration.value.ConfigValue; import cc.carm.lib.configuration.value.ConfigValue;
@@ -29,7 +25,8 @@ import java.util.UUID;
public interface DemoConfiguration extends Configuration { public interface DemoConfiguration extends Configuration {
@ConfigPath(root = true) @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) @ConfigPath(root = true)
@FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"}) @FooterComments({"此处内容将显示在配置条目的下方", "可用于补充说明,但一般不建议使用"})
@@ -39,10 +36,6 @@ public interface DemoConfiguration extends Configuration {
@FooterComments({"上述的枚举内容本质上是通过STRING解析的"}) @FooterComments({"上述的枚举内容本质上是通过STRING解析的"})
ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS); ConfigValue<ChronoUnit> TEST_ENUM = ConfiguredValue.of(ChronoUnit.class, ChronoUnit.DAYS);
// 支持通过 Class<?> 变量标注子配置,一并注册。
// 注意: 若对应类也有注解,则优先使用类的注解。
Class<?> DATABASE = DatabaseConfiguration.class;
@ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。 @ConfigPath("registered_users") // 通过注解规定配置文件中的路径,若不进行注解则以变量名自动生成。
@HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。 @HeaderComments({"Section类型数据测试"}) // 通过注解给配置添加注释。
@InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。 @InlineComment("默认地注释会加到Section的首行末尾") // 通过注解给配置添加注释。
@@ -8,6 +8,6 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
@HeaderComments("Inner Test") @HeaderComments("Inner Test")
public class InstanceConfig implements Configuration { 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"}) @FooterComments({"12313213212"})
@InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。 @InlineComment(value = "用户名(匹配注释)", regex = "name") // 通过注解给配置添加注释。
@InlineComment(value = "信息", regex = {"info.*", "info.game.*"}) // 通过注解给配置添加注释。 @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())) .defaults(new UserRecord("Carm", UUID.randomUUID()))
.parse((holder, section) -> UserRecord.deserialize(section)) .parse((holder, section) -> UserRecord.deserialize(section))
.serialize((holder, data) -> data.serialize()).build(); .serialize((holder, data) -> data.serialize()).build();
@@ -36,6 +36,7 @@ public class UserRecord extends AbstractRecord {
} }
public static UserRecord deserialize(ConfigureSection section) { public static UserRecord deserialize(ConfigureSection section) {
System.out.println("> Deserializing -> " + section.fullPath());
String name = section.getString("name"); String name = section.getString("name");
if (name == null) throw new NullPointerException("name is null"); if (name == null) throw new NullPointerException("name is null");
String uuidString = section.getString("info.uuid"); String uuidString = section.getString("info.uuid");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -10,9 +10,11 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
protected final @NotNull Map<String, Object> data; protected final @NotNull Map<String, Object> data;
protected final @Nullable R parent; protected final @Nullable R parent;
protected final @NotNull String path;
protected AbstractMapSection(@Nullable R parent) { protected AbstractMapSection(@Nullable R parent, @NotNull String path) {
this.parent = parent; this.parent = parent;
this.path = path;
this.data = new LinkedHashMap<>(); this.data = new LinkedHashMap<>();
} }
@@ -20,15 +22,17 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
for (Map.Entry<?, ?> entry : data.entrySet()) { for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "" : entry.getKey().toString(); String key = (entry.getKey() == null) ? "" : entry.getKey().toString();
if (entry.getValue() instanceof Map) { if (entry.getValue() instanceof Map) {
this.data.put(key, createSection((Map<?, ?>) entry.getValue())); this.data.put(key, createSection(key, (Map<?, ?>) entry.getValue()));
} else if (entry.getValue() instanceof List) { } else if (entry.getValue() instanceof List) {
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<>();
int index = 0;
for (Object obj : (List<?>) entry.getValue()) { for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof Map) { if (obj instanceof Map) {
list.add(createSection((Map<?, ?>) obj)); list.add(createSection(key + "[" + index + "]", (Map<?, ?>) obj));
} else { } else {
list.add(obj); list.add(obj);
} }
index++;
} }
this.data.put(key, list); this.data.put(key, list);
} else { } else {
@@ -40,11 +44,11 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
public abstract @NotNull R self(); public abstract @NotNull R self();
@Override @Override
public abstract @NotNull R createSection(@NotNull Map<?, ?> data); public abstract @NotNull R createSection(@NotNull String path, @NotNull Map<?, ?> data);
@Override @Override
public @NotNull R createSection() { public @NotNull String path() {
return createSection(new LinkedHashMap<>()); return this.path;
} }
@Override @Override
@@ -63,7 +67,12 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return data.isEmpty(); return this.data.isEmpty();
}
@Override
public int size(boolean deep) {
return deep ? getKeys(true).size() : this.data.size();
} }
@UnmodifiableView @UnmodifiableView
@@ -72,6 +81,16 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
for (Map.Entry<String, Object> entry : this.data.entrySet()) { for (Map.Entry<String, Object> entry : this.data.entrySet()) {
if (entry.getValue() instanceof AbstractMapSection<?>) { if (entry.getValue() instanceof AbstractMapSection<?>) {
output.put(entry.getKey(), ((AbstractMapSection<?>) entry.getValue()).rawMap()); output.put(entry.getKey(), ((AbstractMapSection<?>) entry.getValue()).rawMap());
} else if (entry.getValue() instanceof List<?>) {
List<Object> list = new ArrayList<>();
for (Object obj : (List<?>) entry.getValue()) {
if (obj instanceof AbstractMapSection<?>) {
list.add(((AbstractMapSection<?>) obj).rawMap());
} else {
list.add(obj);
}
}
output.put(entry.getKey(), list);
} else { } else {
output.put(entry.getKey(), entry.getValue()); output.put(entry.getKey(), entry.getValue());
} }
@@ -86,12 +105,17 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
@Override @Override
public @NotNull Map<String, Object> getValues(boolean deep) { public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.unmodifiableMap(deep ? mappingValues(this, null, true) : data()); return Collections.unmodifiableMap(deep ? mappingValues(this, null, true, String.valueOf(pathSeparator())) : data());
}
@Override
public @NotNull @UnmodifiableView Set<String> getKeys(boolean deep) {
return Collections.unmodifiableSet(deep ? mappingKeys(this, null, true, String.valueOf(pathSeparator())) : data().keySet());
} }
@Override @Override
public void set(@NotNull String path, @Nullable Object value) { public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) value = createSection((Map<?, ?>) value); if (value instanceof Map) value = createSection(path, (Map<?, ?>) value);
R section = getSectionFor(path); R section = getSectionFor(path);
if (section == this) { if (section == this) {
@@ -125,7 +149,7 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
if (index == -1) return self(); if (index == -1) return self();
String root = path.substring(0, index); String root = path.substring(0, index);
return (R) data().computeIfAbsent(root, k -> createSection()); return (R) computeSection(root);
} }
/** /**
@@ -135,18 +159,30 @@ public abstract class AbstractMapSection<R extends AbstractMapSection<R>> implem
* @param parent The parent path * @param parent The parent path
* @param deep If the mapping should be deep * @param deep If the mapping should be deep
*/ */
protected Map<String, Object> mappingValues(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep) { protected static Map<String, Object> mappingValues(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Map<String, Object> output = new LinkedHashMap<>(); Map<String, Object> output = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) { for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator()) + entry.getKey(); String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
output.remove(path); output.remove(path);
output.put(path, entry.getValue()); output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof AbstractMapSection<?>) { if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
output.putAll(mappingValues((AbstractMapSection<?>) entry.getValue(), path, true)); output.putAll(mappingValues((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
} }
} }
return output; return output;
} }
protected static Set<String> mappingKeys(@NotNull AbstractMapSection<?> section, @Nullable String parent, boolean deep, String pathSeparator) {
Set<String> keys = new LinkedHashSet<>();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + pathSeparator) + entry.getKey();
keys.add(path);
if (deep && entry.getValue() instanceof AbstractMapSection<?>) {
keys.addAll(mappingKeys((AbstractMapSection<?>) entry.getValue(), path, true, pathSeparator));
}
}
return keys;
}
} }
@@ -32,6 +32,11 @@ public class ImmutableSection implements ConfigureSection {
return this.parent; return this.parent;
} }
@Override
public @NotNull String path() {
return section().path();
}
private @NotNull ConfigureSection section() { private @NotNull ConfigureSection section() {
return section; return section;
} }
@@ -52,8 +57,8 @@ public class ImmutableSection implements ConfigureSection {
} }
@Override @Override
public @NotNull ConfigureSection createSection(@NotNull Map<?, ?> data) { public @NotNull ImmutableSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new ImmutableSection(this, section().createSection(data)); return new ImmutableSection(this, section().createSection(path, data));
} }
@Override @Override
@@ -11,7 +11,7 @@ import java.util.function.Supplier;
public class MemorySection extends AbstractMapSection<MemorySection> { public class MemorySection extends AbstractMapSection<MemorySection> {
public static MemorySection of() { public static MemorySection of() {
return of((MemorySection) null); return of(new LinkedHashMap<>());
} }
public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) { public static MemorySection of(@NotNull Consumer<Map<String, Object>> data) {
@@ -23,23 +23,23 @@ public class MemorySection extends AbstractMapSection<MemorySection> {
} }
public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) { public static MemorySection of(@NotNull Supplier<Map<?, ?>> data) {
return of(data.get(), null); return of(data.get());
} }
public static MemorySection of(@NotNull Map<?, ?> data) { public static MemorySection of(@NotNull Map<?, ?> data) {
return of(data, null); return of(data, null, "");
} }
public static MemorySection of(@Nullable MemorySection parent) { public static MemorySection of(@Nullable MemorySection parent, @NotNull String path) {
return of(new LinkedHashMap<>(), parent); return of(new LinkedHashMap<>(), parent, path);
} }
public static MemorySection of(@NotNull Map<?, ?> data, @Nullable MemorySection parent) { public static MemorySection of(@NotNull Map<?, ?> data, @Nullable MemorySection parent, @NotNull String path) {
return new MemorySection(data, parent); return new MemorySection(data, parent, path);
} }
public MemorySection(@NotNull Map<?, ?> raw, @Nullable MemorySection parent) { public MemorySection(@NotNull Map<?, ?> raw, @Nullable MemorySection parent, @NotNull String path) {
super(parent); super(parent, path);
migrate(raw); migrate(raw);
} }
@@ -49,8 +49,8 @@ public class MemorySection extends AbstractMapSection<MemorySection> {
} }
@Override @Override
public @NotNull MemorySection createSection(@NotNull Map<?, ?> data) { public @NotNull MemorySection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new MemorySection(data, this); return new MemorySection(data, this, path);
} }
} }
@@ -28,6 +28,11 @@ public class ShadedSection implements ConfigureSection {
return this.parent; return this.parent;
} }
@Override
public @NotNull String path() {
return this.template.path();
}
@Override @Override
public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) { public @NotNull @UnmodifiableView Map<String, Object> getValues(boolean deep) {
if (source == null) return template.getValues(deep); if (source == null) return template.getValues(deep);
@@ -95,8 +100,13 @@ public class ShadedSection implements ConfigureSection {
} }
@Override @Override
public @NotNull ConfigureSection createSection(@NotNull Map<?, ?> data) { public @NotNull ConfigureSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
throw new UnsupportedOperationException("not supported yet"); if (source == null) {
return new ShadedSection(this, template, MemorySection.of(data));
} else {
ConfigureSection section = source.computeSection(path, data);
return new ShadedSection(this, template, section);
}
} }
@Override @Override
@@ -9,19 +9,19 @@ import java.util.Map;
public class SourcedSection extends AbstractMapSection<SourcedSection> { public class SourcedSection extends AbstractMapSection<SourcedSection> {
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source) { public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source) {
return new SourcedSection(source, new LinkedHashMap<>(), null); return new SourcedSection(source, new LinkedHashMap<>(), null, "");
} }
public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source, public static @NotNull SourcedSection root(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@Nullable Map<?, ?> raw) { @Nullable Map<?, ?> raw) {
return new SourcedSection(source, raw == null ? new LinkedHashMap<>() : raw, null); return new SourcedSection(source, raw == null ? new LinkedHashMap<>() : raw, null, "");
} }
protected final @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source; protected final @NotNull ConfigureSource<? extends SourcedSection, ?, ?> source;
public SourcedSection(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source, public SourcedSection(@NotNull ConfigureSource<? extends SourcedSection, ?, ?> source,
@NotNull Map<?, ?> raw, @Nullable SourcedSection parent) { @NotNull Map<?, ?> raw, @Nullable SourcedSection parent, @NotNull String path) {
super(parent); super(parent, path);
this.source = source; this.source = source;
migrate(raw); migrate(raw);
} }
@@ -41,8 +41,8 @@ public class SourcedSection extends AbstractMapSection<SourcedSection> {
} }
@Override @Override
public @NotNull SourcedSection createSection(@NotNull Map<?, ?> data) { public @NotNull SourcedSection createSection(@NotNull String path, @NotNull Map<?, ?> data) {
return new SourcedSection(source(), data, this); return new SourcedSection(source(), data, this, path);
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -1,6 +1,5 @@
package cc.carm.lib.configuration.annotation; package cc.carm.lib.configuration.annotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Range;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@@ -22,7 +21,7 @@ public @interface ConfigVersion {
* *
* @return the version of the configuration field * @return the version of the configuration field
*/ */
@Range(from = 0, to = Integer.MAX_VALUE) @Range(from = 0, to = 65535)
int value() default 0; int value() default 0;
} }
@@ -1,13 +0,0 @@
package cc.carm.lib.configuration.option;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface VersionedOptions {
/**
* Whether to set newer defaults when a {@link cc.carm.lib.configuration.value.ConfigValue}'s marked version
* is newer than the current in storage.
*/
ConfigurationOption<Boolean> RESET_NEWER_DEFAULTS = ConfigurationOption.of(true);
}
@@ -1,4 +1,4 @@
package cc.carm.lib.configuration.commentable; package cc.carm.lib.configuration.versioned;
import cc.carm.lib.configuration.annotation.ConfigVersion; import cc.carm.lib.configuration.annotation.ConfigVersion;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
@@ -19,7 +19,18 @@ public interface VersionedMetaTypes {
} }
static void register(@NotNull ConfigurationInitializer initializer) { static void register(@NotNull ConfigurationInitializer initializer) {
initializer.registerFieldAnnotation(ConfigVersion.class, VERSION, ConfigVersion::value); initializer.appendFieldInitializer((holder, path, field, value) -> {
ConfigVersion annotation = field.getAnnotation(ConfigVersion.class);
if (annotation == null || value == null) return;
int currentVersion = annotation.value();
int savedVersion = holder.metadata(path).get(VERSION, 0);
if (currentVersion == savedVersion) return;
if (currentVersion > savedVersion) { // This values updated.
value.setDefault(true); // Mark as default value, force write.
}
holder.metadata(path).set(VERSION, currentVersion);
});
} }
} }
+3 -9
View File
@@ -15,7 +15,7 @@
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>4.0.6</version> <version>4.0.8</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>features/section</module> <module>features/section</module>
@@ -27,8 +27,8 @@
<module>providers/yaml</module> <module>providers/yaml</module>
<module>providers/gson</module> <module>providers/gson</module>
<!-- <module>providers/hocon</module>--> <!-- <module>providers/hocon</module>-->
<!-- <module>providers/sql</module>--> <module>providers/sql</module>
<!-- <module>providers/mongodb</module>--> <module>providers/mongodb</module>
<module>demo</module> <module>demo</module>
</modules> </modules>
@@ -89,12 +89,6 @@
<url>https://repo1.maven.org/maven2/</url> <url>https://repo1.maven.org/maven2/</url>
</repository> </repository>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/CarmJos/*</url>
</repository>
</repositories> </repositories>
<distributionManagement> <distributionManagement>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+18 -10
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<deps.mongodb.version>5.3.1</deps.mongodb.version>
<log4j.version>2.24.3</log4j.version> <log4j.version>2.24.3</log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-mongodb</artifactId> <artifactId>easyconfiguration-mongodb</artifactId>
@@ -30,7 +31,7 @@
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId> <artifactId>easyconfiguration-feature-section</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -38,14 +39,7 @@
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>5.3.1</version> <version>${deps.mongodb.version}</version>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -69,6 +63,20 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-demo</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -0,0 +1,75 @@
package cc.carm.lib.configuration.source.mongodb;
import cc.carm.lib.configuration.source.ConfigurationFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class MongoConfigFactory extends ConfigurationFactory<MongoSource, ConfigurationHolder<MongoSource>, MongoConfigFactory> {
public static MongoConfigFactory from(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
return new MongoConfigFactory(collectionSupplier);
}
public static MongoConfigFactory from(@NotNull MongoCollection<Document> collection) {
return from(() -> collection);
}
public static MongoConfigFactory from(@NotNull MongoDatabase database, @NotNull String collectionName) {
return from(() -> database.getCollection(collectionName));
}
protected @NotNull Supplier<MongoCollection<Document>> collectionSupplier;
protected @NotNull String namespace = "config";
public MongoConfigFactory(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
super();
this.collectionSupplier = collectionSupplier;
}
public MongoConfigFactory collection(@NotNull Supplier<MongoCollection<Document>> collectionSupplier) {
this.collectionSupplier = collectionSupplier;
return this;
}
public MongoConfigFactory collection(@NotNull MongoCollection<Document> collection) {
return collection(() -> collection);
}
public MongoConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return this;
}
public MongoConfigFactory namespace(@NotNull Supplier<String> namespace) {
return namespace(namespace.get());
}
@Override
protected MongoConfigFactory self() {
return this;
}
@Override
public @NotNull ConfigurationHolder<MongoSource> build() {
MongoCollection<Document> collection = this.collectionSupplier.get();
if (collection == null) {
throw new IllegalStateException("Failed to get MongoCollection<Document> from supplier");
}
return new ConfigurationHolder<MongoSource>(this.adapters, this.options, this.metadata, this.initializer) {
final @NotNull MongoSource source = new MongoSource(this, System.currentTimeMillis(), collection, namespace);
@Override
public @NotNull MongoSource config() {
return this.source;
}
};
}
}
@@ -1,4 +1,76 @@
package cc.carm.lib.configuration.source.mongodb; package cc.carm.lib.configuration.source.mongodb;
public class MongoSource { import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.SourcedSection;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.ReplaceOptions;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
public class MongoSource extends ConfigureSource<SourcedSection, Map<String, Object>, MongoSource> {
protected final @NotNull MongoCollection<Document> collection;
protected final @NotNull String namespace;
protected SourcedSection rootSection;
protected MongoSource(@NotNull ConfigurationHolder<? extends MongoSource> holder, long lastUpdateMillis,
@NotNull MongoCollection<Document> collection, @NotNull String namespace) {
super(holder, lastUpdateMillis);
this.collection = collection;
this.namespace = namespace;
try {
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected @NotNull MongoSource self() {
return this;
}
@Override
public @NotNull Map<String, Object> original() {
return section().data();
}
@Override
public @NotNull SourcedSection section() {
return Objects.requireNonNull(rootSection, "RootSection is not initialized");
}
public @NotNull String namespace() {
return this.namespace;
}
public @NotNull MongoCollection<Document> collection() {
return this.collection;
}
@Override
public void save() throws Exception {
Map<String, Object> data = this.rootSection.rawMap();
if (data.isEmpty()) return; // Skip saving if empty
if (data.containsKey("_id") && data.size() == 1) return; // Skip saving if only contains _id
ReplaceOptions options = new ReplaceOptions().upsert(true);
Document storage = new Document(data).append("_id", this.namespace);
this.collection.replaceOne(new Document("_id", this.namespace), storage, options);
}
@Override
protected void onReload() throws Exception {
Document storage = this.collection.find(new Document("_id", this.namespace)).first();
if (storage == null) storage = new Document();
else storage.remove("_id"); // Remove _id
this.rootSection = SourcedSection.root(this, storage);
}
} }
@@ -0,0 +1,12 @@
package config;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
interface MongoConfig extends Configuration {
ConfiguredValue<String> HOST = ConfiguredValue.of("127.0.0.1");
ConfiguredValue<Integer> PORT = ConfiguredValue.of(27017);
ConfiguredValue<String> USERNAME = ConfiguredValue.of("minecraft");
ConfiguredValue<String> PASSWORD = ConfiguredValue.of("minecraft");
ConfiguredValue<String> DATABASE = ConfiguredValue.of("minecraft");
}
@@ -0,0 +1,57 @@
package config;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.mongodb.MongoConfigFactory;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import org.bson.UuidRepresentation;
import org.junit.Test;
import java.io.File;
public class MongoTest {
boolean local = false;
@Test
public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/mongo.json")).build();
gsonHolder.initialize(MongoConfig.class);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(
"mongodb://" + MongoConfig.HOST.resolve() + ":" + MongoConfig.PORT.resolve()
))
.credential(MongoCredential.createCredential(
MongoConfig.USERNAME.resolve(), MongoConfig.DATABASE.resolve(),
MongoConfig.PASSWORD.resolve().toCharArray()
))
.uuidRepresentation(UuidRepresentation.STANDARD)
.build();
MongoClient mongoClient = MongoClients.create(settings);
MongoDatabase mongoDatabase = mongoClient.getDatabase(MongoConfig.DATABASE.resolve());
ConfigurationHolder<?> mongoHolder = MongoConfigFactory
.from(mongoDatabase, "configs")
.namespace("my_plugin")
.build();
// Test the configuration
ConfigurationTest.testDemo(mongoHolder);
ConfigurationTest.testInner(mongoHolder);
ConfigurationTest.save(mongoHolder);
}
}
+10 -10
View File
@@ -6,16 +6,16 @@ SQL database implementation, support for MySQL or MariaDB.
```mysql ```mysql
CREATE TABLE IF NOT EXISTS conf CREATE TABLE IF NOT EXISTS conf
( (
`namespace` VARCHAR(32) NOT NULL, # 命名空间 (代表其属于谁,类似于单个配置文件地址的概念) `namespace` VARCHAR(32) NOT NULL, # 命名空间 (代表其属于谁,类似于单个配置文件地址的概念)
`path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath) `path` VARCHAR(96) NOT NULL, # 配置路径 (ConfigPath)
`type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...) `type` TINYINT UNSIGNED NOT NULL DEFAULT 0, # 数据类型 (Integer/Byte/List/Map/...)
`value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式) `value` MEDIUMTEXT, # 配置项的值 (可能为JSON格式)
`inline_comment` TEXT, # 配置项的用法,本质是行内注释 `inline_comment` TEXT comment 'usage', # 配置项的用法,本质是行内注释
`header_comment` MEDIUMTEXT, # 配置项的描述,本质是顶部注释 `header_comment` MEDIUMTEXT comment 'description', # 配置项的描述,本质是顶部注释
`footer_comment` MEDIUMTEXT, # 配置项的描述,本质是部注释 `footer_comment` MEDIUMTEXT comment 'example', # 配置项的参考,本质是部注释
`version` INT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本 `version` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, # 配置项的版本
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间 `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, # 创建时间
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`namespace`, `path`) PRIMARY KEY (`namespace`, `path`)
) ENGINE = InnoDB ) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4; DEFAULT CHARSET = utf8mb4;
+20 -5
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.0</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -15,7 +15,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<log4j.version>2.24.3</log4j.version> <deps.mysql-driver.version>8.0.33</deps.mysql-driver.version>
<deps.log4j.version>2.24.3</deps.log4j.version>
</properties> </properties>
<artifactId>easyconfiguration-sql</artifactId> <artifactId>easyconfiguration-sql</artifactId>
@@ -77,21 +78,35 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version> <version>${deps.log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-gson</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${deps.mysql-driver.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -0,0 +1,133 @@
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.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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.HashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SQLConfigFactory extends ConfigurationFactory<SQLSource, ConfigurationHolder<SQLSource>, SQLConfigFactory> {
public static SQLConfigFactory from(@NotNull Supplier<@NotNull SQLManager> managerSupplier) {
return new SQLConfigFactory(managerSupplier);
}
public static SQLConfigFactory from(@NotNull SQLManager manager) {
return from(() -> manager);
}
protected @NotNull Supplier<SQLManager> managerSupplier;
protected Supplier<Gson> gsonSupplier = () -> SQLSource.DEFAULT_GSON;
protected HashMap<Integer, SQLValueResolver<?>> resolvers = new HashMap<>(SQLValueResolver.STANDARD_RESOLVERS);
protected String tableName = "configs";
protected String namespace = "default";
public SQLConfigFactory(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
}
@Override
protected SQLConfigFactory self() {
return this;
}
public SQLConfigFactory manager(@NotNull Supplier<SQLManager> managerSupplier) {
this.managerSupplier = managerSupplier;
return self();
}
public SQLConfigFactory manager(@NotNull SQLManager manager) {
return manager(() -> manager);
}
public SQLConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public SQLConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
public SQLConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public SQLConfigFactory resolver(@Range(from = 0, to = 255) int type, @NotNull SQLValueResolver<?> resolver) {
this.resolvers.put(type, resolver);
return self();
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull Class<T> clazz,
@NotNull DataFunction<String, T> parser) {
return resolver(id, ValueType.of(clazz), parser);
}
public <T> SQLConfigFactory resolver(@Range(from = 0, to = 255) int id, @NotNull ValueType<T> type,
@NotNull DataFunction<String, T> parser) {
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<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<T, String> serializer) {
return resolver(id, SQLValueResolver.of(type, parser, serializer));
}
public SQLConfigFactory tableName(@NotNull String tableName) {
this.tableName = tableName;
return self();
}
public SQLConfigFactory namespace(@NotNull String namespace) {
this.namespace = namespace;
return self();
}
@Override
public @NotNull ConfigurationHolder<SQLSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
SQLManager manager = this.managerSupplier.get();
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(
this, System.currentTimeMillis(),
gson, manager, resolvers, tableName, namespace
);
@Override
public @NotNull SQLSource config() {
return source;
}
};
}
}
@@ -1,6 +1,12 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.source.option.ConfigurationOption;
public interface SQLOptions { public interface SQLOptions {
/**
* Whether to purge the configuration's in-database data when saving.
*/
ConfigurationOption<Boolean> PURGE = ConfigurationOption.of( true);
} }
@@ -1,53 +1,62 @@
package cc.carm.lib.configuration.source.sql; package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.commentable.Commentable;
import cc.carm.lib.configuration.source.ConfigurationHolder; import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.section.ConfigureSource; import cc.carm.lib.configuration.source.section.ConfigureSource;
import cc.carm.lib.configuration.source.section.MemorySection; import cc.carm.lib.configuration.source.section.SourcedSection;
import cc.carm.lib.configuration.value.ConfigValue;
import cc.carm.lib.configuration.versioned.VersionedMetaTypes;
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.SQLTable; import cc.carm.lib.easysql.api.SQLTable;
import cc.carm.lib.easysql.api.enums.IndexType; import cc.carm.lib.easysql.api.enums.IndexType;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSource> { public class SQLSource extends ConfigureSource<SourcedSection, Map<String, Object>, SQLSource> {
protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder() protected static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting() .serializeNulls().disableHtmlEscaping().create();
.create();
protected final @NotNull Gson gson; protected final @NotNull Gson gson;
protected final @NotNull SQLManager sqlManager; protected final @NotNull SQLManager sqlManager;
protected final @NotNull String namespace; protected final @NotNull String namespace;
protected final @NotNull SQLTable table; protected final @NotNull SQLTable table;
protected final @NotNull Set<String> updated = new HashSet<>(); protected final @NotNull Map<Integer, SQLValueResolver<?>> resolvers;
protected MemorySection rootSection; protected SourcedSection rootSection;
public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis, public SQLSource(@NotNull ConfigurationHolder<? extends SQLSource> holder, long lastUpdateMillis,
@NotNull Gson gson, @NotNull SQLManager sqlManager, @NotNull String tableName, @NotNull String namespace) { @NotNull Gson gson, @NotNull SQLManager sqlManager,
@NotNull Map<Integer, SQLValueResolver<?>> resolvers,
@NotNull String tableName, @NotNull String namespace) {
super(holder, lastUpdateMillis); super(holder, lastUpdateMillis);
this.gson = gson; this.gson = gson;
this.sqlManager = sqlManager; this.sqlManager = sqlManager;
this.resolvers = resolvers;
this.namespace = namespace; this.namespace = namespace;
this.table = SQLTable.of(tableName, builder -> { this.table = SQLTable.of(tableName, builder -> {
builder.addColumn("namespace", "VARCHAR(32) NOT NULL"); builder.addColumn("namespace", "VARCHAR(32) NOT NULL");
builder.addColumn("path", "VARCHAR(96) NOT NULL"); builder.addColumn("path", "VARCHAR(96) NOT NULL");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("value", "TEXT"); builder.addColumn("value", "TEXT");
builder.addColumn("inline_comment", "TEXT"); builder.addColumn("inline_comment", "TEXT");
builder.addColumn("header_comments", "MEDIUMTEXT"); builder.addColumn("header_comments", "MEDIUMTEXT");
builder.addColumn("footer_comments", "MEDIUMTEXT"); builder.addColumn("footer_comments", "MEDIUMTEXT");
builder.addColumn("type", "TINYINT NOT NULL DEFAULT 0");
builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0"); builder.addColumn("version", "MEDIUMINT UNSIGNED NOT NULL DEFAULT 0");
builder.addColumn("create_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); builder.addColumn("create_time", "TIMESTAMP NOT NULL");
builder.addColumn("update_time", "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); builder.addColumn("update_time", "TIMESTAMP NOT NULL");
builder.setIndex( builder.setIndex(
IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(), IndexType.PRIMARY_KEY, "pk_" + tableName.toLowerCase(),
@@ -55,82 +64,134 @@ public class SQLSource extends ConfigureSource<MemorySection, Map<?, ?>, SQLSour
); );
builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); builder.setTableSettings("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}); });
try {
this.table.create(this.sqlManager);
onReload();
} catch (Exception e) {
e.printStackTrace();
}
} }
@Override @Override
protected SQLSource self() { protected @NotNull SQLSource self() {
return this; return this;
} }
public @NotNull Gson gson() {
return gson;
}
@Override @Override
public @NotNull Map<?, ?> original() { public @NotNull Map<String, Object> original() {
return section().data(); return section().data();
} }
@Override @Override
public @NotNull MemorySection section() { public @NotNull SourcedSection section() {
return Objects.requireNonNull(this.rootSection, "Root section is not initialized."); 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 @Override
public void save() throws Exception { public void save() throws Exception {
if (this.updated.isEmpty()) return; // Nothing to save LocalDateTime time = LocalDateTime.now(); // Update time
List<Object[]> dataValues = new ArrayList<>();
Date date = new Date(); // Update time SourcedSection section = section();
List<Object[]> values = new ArrayList<>(); Map<String, ConfigValue<?>> values = holder().registeredValues();
for (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {
@NotNull String path = entry.getKey();
@NotNull ConfigValue<?> config = entry.getValue();
@Nullable Object value = section.get(path);
for (String path : this.updated) { if (value instanceof SourcedSection) {
Object value = get(path); 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 {
list.add(obj);
}
}
value = list;
}
// if (value instanceof SQLConfigWrapper) { if (value == null) continue;
// value = getSourceMap(((SQLConfigWrapper) value).getSource());
// } try {
// int typeID = typeIdOf(value);
// SQLValueResolver<?> type = SQLValueTypes.get(value.getClass()); String data = serialize(typeID, value);
// if (type != null) { if (data == null) continue;
// values.add(new Object[]{
// this.namespace, path, date, int version = holder().metadata(path).get(VersionedMetaTypes.VERSION, 0);
// type.getID(), type.serializeObject(value), dataValues.add(new Object[]{
// getComments().getInlineComment(path), namespace, path, time, version, typeID, data,
// GSON.toJson(getComments().getHeaderComment(path)) Commentable.getInlineComment(holder(), path),
// }); gson.toJson(Commentable.getHeaderComments(holder(), path)),
// } gson.toJson(Commentable.getFooterComments(holder(), path))
});
} catch (Exception ex) {
ex.printStackTrace();
}
} }
this.updated.clear(); if (holder.option(SQLOptions.PURGE)) {
purge();
}
this.table.createReplaceBatch() this.table.createReplaceBatch()
.setColumnNames("namespace", "path", "update_time", "type", "value", "inline_comment", "header_comments") .setColumnNames(
.setAllParams(values) "namespace", "path", "update_time", "version", "type", "value",
.execute(); "inline_comment", "header_comments", "footer_comments"
).setAllParams(dataValues).execute();
} }
@Override @Override
protected void onReload() throws Exception { protected void onReload() throws Exception {
LinkedHashMap<String, Object> values = new LinkedHashMap<>(); Map<String, Object> loaded = new LinkedHashMap<>();
try (SQLQuery query = this.table.createQuery()
// try (SQLQuery query = this.table.createQuery() .addCondition("namespace", namespace)
// .addCondition("namespace", namespace) .build().execute()) {
// .build().execute()) { ResultSet rs = query.getResultSet();
// ResultSet rs = query.getResultSet(); while (rs.next()) {
// while (rs.next()) { String path = rs.getString("path");
// String path = rs.getString("path"); if (path == null) continue; // Path should be not null
// int type = rs.getInt("type"); int ver = rs.getInt("version");
// try { try {
// SQLValueResolver<?> resolver = SQLValueTypes.get(type); loaded.put(path, parse(rs.getInt("type"), rs.getString("value")));
// if (resolver == null) throw new IllegalStateException("No resolver for type #" + type); if (ver != 0) holder().metadata(path).set(VersionedMetaTypes.VERSION, ver);
// String value = rs.getString("value"); } catch (Exception e) {
// values.put(path, resolver.resolve(value)); e.printStackTrace();
// }
// loadInlineComment(path, rs.getString("inline_comment")); }
// loadHeaderComment(path, rs.getString("header_comments")); }
// } catch (Exception ex) { this.rootSection = SourcedSection.root(this, loaded);
// ex.printStackTrace();
// }
// }
// }
//
// this.rootConfiguration = new SQLConfigWrapper(this, values);
} }
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(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(this, value);
}
protected int typeIdOf(@NotNull Object value) {
return this.resolvers.entrySet().stream()
.filter(entry -> entry.getValue().isInstance(value))
.findFirst().map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalStateException("No resolvers for value " + value.getClass().getName()));
}
} }
@@ -0,0 +1,115 @@
package cc.carm.lib.configuration.source.sql;
import cc.carm.lib.configuration.adapter.ValueType;
import cc.carm.lib.configuration.function.DataFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class SQLValueResolver<T> {
public static final @NotNull SQLValueResolver<String> STRING = SQLValueResolver.of(ValueType.STRING, s -> s);
public static final @NotNull SQLValueResolver<Byte> BYTE = SQLValueResolver.of(ValueType.BYTE, Byte::parseByte);
public static final @NotNull SQLValueResolver<Short> SHORT = SQLValueResolver.of(ValueType.SHORT, Short::parseShort);
public static final @NotNull SQLValueResolver<Integer> INTEGER = SQLValueResolver.of(ValueType.INTEGER, Integer::parseInt);
public static final @NotNull SQLValueResolver<Long> LONG = SQLValueResolver.of(ValueType.LONG, Long::parseLong);
public static final @NotNull SQLValueResolver<Float> FLOAT = SQLValueResolver.of(ValueType.FLOAT, Float::parseFloat);
public static final @NotNull SQLValueResolver<Double> DOUBLE = SQLValueResolver.of(ValueType.DOUBLE, Double::parseDouble);
public static final @NotNull SQLValueResolver<Boolean> BOOLEAN = SQLValueResolver.of(ValueType.BOOLEAN, Boolean::parseBoolean);
public static final @NotNull SQLValueResolver<Character> CHAR = SQLValueResolver.of(ValueType.CHAR, s -> s.charAt(0));
public static final @NotNull SQLValueResolver<List<?>> LIST = new SQLValueResolver<List<?>>(new ValueType<List<?>>() {
}) {
@Override
public @Nullable List<?> resolve(@NotNull SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, List.class);
}
@Override
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 SQLSource source, String data) throws Exception {
return source.gson().fromJson(data, LinkedHashMap.class);
}
@Override
public @Nullable String serialize(@NotNull SQLSource source, Object value) {
return source.gson().toJson(value);
}
};
public static final @NotNull Map<Integer, SQLValueResolver<?>> STANDARD_RESOLVERS = Collections.unmodifiableMap(standards());
static Map<Integer, SQLValueResolver<?>> standards() {
Map<Integer, SQLValueResolver<?>> map = new LinkedHashMap<>();
map.put(0, STRING);
map.put(1, BYTE);
map.put(2, SHORT);
map.put(3, INTEGER);
map.put(4, LONG);
map.put(5, FLOAT);
map.put(6, DOUBLE);
map.put(7, BOOLEAN);
map.put(8, CHAR);
map.put(10, LIST);
map.put(11, MAP);
return map;
}
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 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) {
this.type = type;
}
public @NotNull ValueType<T> getType() {
return type;
}
public boolean isInstance(@NotNull Object obj) {
return getType().isInstance(obj);
}
public abstract @Nullable T resolve(@NotNull SQLSource source, String data) throws Exception;
public @Nullable String serialize(@NotNull SQLSource source, Object value) throws Exception {
return String.valueOf(value);
}
}
@@ -1,27 +1,48 @@
package config; package config;
import cc.carm.lib.configuration.demo.DatabaseConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.sql.SQLConfigFactory;
import cc.carm.lib.easysql.EasySQL; import cc.carm.lib.easysql.EasySQL;
import cc.carm.lib.easysql.api.SQLManager; import cc.carm.lib.easysql.api.SQLManager;
import cc.carm.lib.easysql.beecp.BeeDataSourceConfig; import cc.carm.lib.easysql.beecp.BeeDataSourceConfig;
import org.junit.Test;
import java.io.File;
public class SQLConfigTest { public class SQLConfigTest {
boolean local = false;
@Test
public void test() { public void test() {
if (!local) return;
ConfigurationHolder<?> gsonHolder = JSONConfigFactory.from(new File("target/sql.json")).build();
gsonHolder.initialize(DatabaseConfiguration.class);
BeeDataSourceConfig config = new BeeDataSourceConfig(); BeeDataSourceConfig config = new BeeDataSourceConfig();
config.setDriverClassName("org.h2.Driver"); config.setDriverClassName(DatabaseConfiguration.DRIVER_NAME.resolve());
config.setJdbcUrl("jdbc:h2:file:target/test;DB_CLOSE_DELAY=-1;MODE=MySQL;"); config.setJdbcUrl(DatabaseConfiguration.buildJDBC());
config.setUsername(DatabaseConfiguration.USERNAME.resolve());
config.setPassword(DatabaseConfiguration.PASSWORD.resolve());
SQLManager manager = EasySQL.createManager(config); SQLManager manager = EasySQL.createManager(config);
manager.setDebugMode(true); manager.setDebugMode(true);
// SQLConfigProvider provider = EasyConfiguration.from(manager, "conf_test", "TESTING"); ConfigurationHolder<?> holder = SQLConfigFactory.from(manager)
// .tableName("test_configs")
// ConfigurationTest.testDemo(provider); .namespace("testing")
// ConfigurationTest.testInner(provider); .build();
//
// ConfigurationTest.save(provider); ConfigurationTest.testDemo(holder);
// ConfigurationTest.testInner(holder);
// EasySQL.shutdownManager(manager);
ConfigurationTest.save(holder);
EasySQL.shutdownManager(manager);
} }
} }
+1 -8
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>easyconfiguration-parent</artifactId> <artifactId>easyconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId> <groupId>cc.carm.lib</groupId>
<version>4.0.6</version> <version>4.0.8</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<properties> <properties>
@@ -29,13 +29,6 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>yamlcommentwriter</artifactId>
<version>1.2.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>${project.parent.groupId}</groupId> <groupId>${project.parent.groupId}</groupId>
<artifactId>easyconfiguration-feature-file</artifactId> <artifactId>easyconfiguration-feature-file</artifactId>
@@ -56,7 +56,7 @@ public class YAMLSource
@Override @Override
protected @NotNull YAMLSource self() { protected @NotNull YAMLSource self() {
return null; return this;
} }
@Override @Override
@@ -9,7 +9,6 @@ import org.junit.Test;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
public class YamlTests { public class YamlTests {