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

feat(json): Implement json sources

This commit is contained in:
2025-02-12 04:25:29 +08:00
parent c68d2371ee
commit 05ff61a9d9
44 changed files with 656 additions and 558 deletions
@@ -1,36 +0,0 @@
package cc.carm.lib.configuration;
import cc.carm.lib.configuration.json.JSONConfigProvider;
import java.io.File;
import java.io.IOException;
public class EasyConfiguration {
private EasyConfiguration() {
}
public static JSONConfigProvider from(File file, String source) {
JSONConfigProvider provider = new JSONConfigProvider(file);
try {
provider.initializeFile(source);
provider.initializeConfig();
} catch (IOException e) {
e.printStackTrace();
}
return provider;
}
public static JSONConfigProvider from(File file) {
return from(file, file.getName());
}
public static JSONConfigProvider from(String fileName) {
return from(fileName, fileName);
}
public static JSONConfigProvider from(String fileName, String source) {
return from(new File(fileName), source);
}
}
@@ -0,0 +1,72 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class JSONConfigFactory extends FileConfigFactory<JSONSource, ConfigurationHolder<JSONSource>, JSONConfigFactory> {
public static JSONConfigFactory from(@NotNull File file) {
return new JSONConfigFactory(file);
}
public static JSONConfigFactory from(@NotNull File parent, @NotNull String configName) {
return new JSONConfigFactory(new File(parent, configName));
}
protected Supplier<Gson> gsonSupplier = () -> JSONSource.DEFAULT_GSON;
public JSONConfigFactory(@NotNull File file) {
super(file);
}
@Override
public JSONConfigFactory self() {
return this;
}
public JSONConfigFactory gson(@NotNull Supplier<Gson> gsonSupplier) {
this.gsonSupplier = gsonSupplier;
return self();
}
public JSONConfigFactory gson(@NotNull Gson gson) {
return gson(() -> gson);
}
public JSONConfigFactory gson(@NotNull Consumer<GsonBuilder> builder) {
return gson(() -> {
GsonBuilder gsonBuilder = new GsonBuilder();
builder.accept(gsonBuilder);
return gsonBuilder.create();
});
}
@Override
public @NotNull ConfigurationHolder<JSONSource> build() {
Gson gson = gsonSupplier.get();
if (gson == null) throw new NullPointerException("No Gson instance provided.");
File configFile = this.file;
String sourcePath = this.resourcePath;
return new ConfigurationHolder<JSONSource>(this.adapters, this.options, new ConcurrentHashMap<>(), this.initializer) {
final JSONSource source = new JSONSource(this, 0, configFile, sourcePath, gson);
@Override
public @NotNull JSONSource config() {
return source;
}
};
}
}
@@ -1,73 +0,0 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.comment.ConfigurationComments;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.LinkedHashMap;
/**
* Some code comes from BungeeCord's implementation of the JsonConfiguration.
*
* @author md_5, CarmJos
*/
public class JSONConfigProvider extends FileConfigProvider<JSONConfigWrapper> {
protected JSONConfigWrapper configuration;
protected ConfigInitializer<JSONConfigProvider> initializer;
public JSONConfigProvider(@NotNull File file) {
super(file);
this.initializer = new ConfigInitializer<>(this);
}
public void initializeConfig() {
onReload();
}
@Override
public @NotNull JSONConfigWrapper getConfiguration() {
return this.configuration;
}
@Override
protected void onReload() {
LinkedHashMap<?, ?> map = null;
try (FileInputStream is = new FileInputStream(file)) {
map = gson.fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), LinkedHashMap.class);
} catch (IOException e) {
e.printStackTrace();
}
if (map == null) map = new LinkedHashMap<>();
this.configuration = new JSONConfigWrapper(map);
}
@Override
public @Nullable ConfigurationComments getComments() {
return null;
}
@Override
public void save() throws Exception {
try (Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8)) {
gson.toJson(configuration.data, writer);
}
}
@Override
public @NotNull ConfigInitializer<? extends ConfigurationProvider<JSONConfigWrapper>> getInitializer() {
return this.initializer;
}
}
@@ -1,109 +0,0 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigSource;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JSONConfigSource extends FileConfigSource<JSONConfigSource, JsonObject> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
JSONConfigWrapper.class,
(JsonSerializer<JSONConfigWrapper>) (src, typeOfSrc, context) -> context.serialize(src.data)
).create();
protected final @NotNull Gson gson;
protected @Nullable JsonObject original;
protected JSONConfigSource(@NotNull ConfigurationHolder<? extends JSONConfigSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) {
super(holder, lastUpdateMillis, file, resourcePath);
this.gson = gson;
}
public void initialize() {
try {
initializeFile();
} catch (Exception e) {
e.printStackTrace();
}
}
public void initializeJson() {
this.original = fileReader(reader -> gson.fromJson(reader, JsonObject.class));
}
@Override
protected JSONConfigSource self() {
return this;
}
@Override
public @NotNull JsonObject original() {
return null;
}
@Override
public void save() throws Exception {
}
@Override
protected void onReload() throws Exception {
}
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.emptyMap();
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
}
@Override
public boolean contains(@NotNull String path) {
return false;
}
@Override
public boolean isList(@NotNull String path) {
return false;
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return Collections.emptyList();
}
@Override
public boolean isSection(@NotNull String path) {
return false;
}
@Override
public @Nullable ConfigurationSection getSection(@NotNull String path) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
}
@@ -1,53 +0,0 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.section.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JSONConfigurationSection implements ConfigurationSection {
@Override
public @NotNull Map<String, Object> getValues(boolean deep) {
return Collections.emptyMap();
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
}
@Override
public boolean contains(@NotNull String path) {
return false;
}
@Override
public boolean isList(@NotNull String path) {
return false;
}
@Override
public @Nullable List<?> getList(@NotNull String path) {
return Collections.emptyList();
}
@Override
public boolean isSection(@NotNull String path) {
return false;
}
@Override
public @Nullable ConfigurationSection getSection(@NotNull String path) {
return null;
}
@Override
public @Nullable Object get(@NotNull String path) {
return null;
}
}
@@ -1,31 +1,27 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.section.ConfigureSection;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Some code comes from BungeeCord's implementation of the JsonConfiguration.
*
* @author md_5, CarmJos
*/
public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Object>> {
public class JSONSection implements ConfigureSection {
private static final char SEPARATOR = '.';
protected final ConfigureSource<? extends JSONSection, ?, ?> source;
protected final Map<String, Object> data;
JSONConfigWrapper(Map<?, ?> map) {
public JSONSection(@NotNull ConfigureSource<? extends JSONSection, ?, ?> source,
@NotNull Map<?, ?> data) {
this.source = source;
this.data = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
for (Map.Entry<?, ?> entry : data.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.data.put(key, new JSONConfigWrapper((Map<?, ?>) entry.getValue()));
this.data.put(key, new JSONSection(source, (Map<?, ?>) entry.getValue()));
} else {
this.data.put(key, entry.getValue());
}
@@ -33,7 +29,11 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
}
@Override
public @NotNull Map<String, Object> getSource() {
public @NotNull ConfigureSource<? extends JSONSection, ?, ?> source() {
return this.source;
}
public Map<String, Object> data() {
return this.data;
}
@@ -54,10 +54,10 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (value instanceof Map) {
value = new JSONConfigWrapper((Map<?, ?>) value);
value = new JSONSection(source(), (Map<?, ?>) value);
}
JSONConfigWrapper section = getSectionFor(path);
JSONSection section = getSectionFor(path);
if (section == this) {
if (value == null) {
this.data.remove(path);
@@ -76,7 +76,7 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
@Override
public @Nullable Object get(@NotNull String path) {
JSONConfigWrapper section = getSectionFor(path);
JSONSection section = getSectionFor(path);
return section == this ? data.get(path) : section.get(getChild(path));
}
@@ -92,44 +92,44 @@ public class JSONConfigWrapper implements ConfigurationWrapper<Map<String, Objec
}
@Override
public boolean isConfigurationSection(@NotNull String path) {
return get(path) instanceof JSONConfigWrapper;
public boolean isSection(@NotNull String path) {
return get(path) instanceof JSONSection;
}
@Override
public @Nullable JSONConfigWrapper getConfigurationSection(@NotNull String path) {
public @Nullable ConfigureSection getSection(@NotNull String path) {
Object val = get(path);
return (val instanceof JSONConfigWrapper) ? (JSONConfigWrapper) val : null;
return (val instanceof ConfigureSection) ? (ConfigureSection) val : null;
}
private JSONConfigWrapper getSectionFor(String path) {
int index = path.indexOf(SEPARATOR);
private JSONSection getSectionFor(String path) {
int index = path.indexOf(separator());
if (index == -1) return this;
String root = path.substring(0, index);
Object section = this.data.get(root);
if (section == null) {
section = new JSONConfigWrapper(new LinkedHashMap<>());
section = new JSONSection(source(), new LinkedHashMap<>());
this.data.put(root, section);
}
return (JSONConfigWrapper) section;
return (JSONSection) section;
}
private String getChild(String path) {
int index = path.indexOf(SEPARATOR);
int index = path.indexOf(separator());
return (index == -1) ? path : path.substring(index + 1);
}
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull JSONConfigWrapper section,
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull JSONSection section,
@Nullable String parent, boolean deep) {
for (Map.Entry<String, Object> entry : section.data.entrySet()) {
String path = (parent == null ? "" : parent + ".") + entry.getKey();
for (Map.Entry<String, Object> entry : section.data().entrySet()) {
String path = (parent == null ? "" : parent + separator()) + entry.getKey();
output.remove(path);
output.put(path, entry.getValue());
if (deep && entry.getValue() instanceof JSONConfigWrapper) {
this.mapChildrenValues(output, (JSONConfigWrapper) entry.getValue(), path, true);
if (deep && entry.getValue() instanceof JSONSection) {
this.mapChildrenValues(output, (JSONSection) entry.getValue(), path, true);
}
}
}
@@ -0,0 +1,80 @@
package cc.carm.lib.configuration.json;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.FileConfigSource;
import cc.carm.lib.configuration.source.section.ConfigureSource;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.*;
public class JSONSource extends FileConfigSource<JSONSection, Map<?, ?>, JSONSource> {
public static final @NotNull Gson DEFAULT_GSON = new GsonBuilder()
.serializeNulls().disableHtmlEscaping().setPrettyPrinting()
.registerTypeAdapter(
JSONSection.class,
(JsonSerializer<JSONSection>) (src, typeOfSrc, context) -> context.serialize(src.data)
).create();
protected final @NotNull Gson gson;
protected @Nullable JSONSection section;
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath) {
this(holder, lastUpdateMillis, file, resourcePath, DEFAULT_GSON);
}
protected JSONSource(@NotNull ConfigurationHolder<? extends JSONSource> holder, long lastUpdateMillis,
@NotNull File file, @Nullable String resourcePath, @NotNull Gson gson) {
super(holder, lastUpdateMillis, file, resourcePath);
this.gson = gson;
initialize();
}
public void initialize() {
try {
initializeFile();
onReload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected JSONSource self() {
return this;
}
@Override
public @NotNull ConfigureSource<?, ?, ?> source() {
return this;
}
@Override
public @NotNull Map<?, ?> original() {
return section().data();
}
@Override
public @NotNull JSONSection section() {
return Objects.requireNonNull(this.section, "Section is not initialized");
}
@Override
public void save() throws Exception {
fileWriter(writer -> gson.toJson(original(), writer));
}
@Override
protected void onReload() throws Exception {
Map<?, ?> data = fileReader(reader -> gson.fromJson(reader, LinkedHashMap.class));
if (data == null) data = new LinkedHashMap<>();
this.section = new JSONSection(this, data);
this.lastUpdateMillis = System.currentTimeMillis(); // 更新时间
}
}
@@ -1,28 +1,26 @@
package config;
import cc.carm.lib.configuration.EasyConfiguration;
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
import cc.carm.lib.configuration.json.JSONConfigProvider;
import cc.carm.lib.configuration.json.JSONConfigFactory;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import cc.carm.lib.configuration.source.option.StandardOptions;
import org.junit.Test;
import java.io.File;
public class JSONConfigTest {
protected final JSONConfigProvider provider = EasyConfiguration.from("target/config.json", "config.json");
protected final ConfigurationHolder<?> holder = JSONConfigFactory
.from(new File("target"), "config.json")
.option(StandardOptions.PATH_SEPARATOR, '-')
.build();
@Test
public void onTest() {
ConfigurationTest.testDemo(this.holder);
ConfigurationTest.testInner(this.holder);
ConfigurationTest.testDemo(this.provider);
ConfigurationTest.testInner(this.provider);
System.out.println("----------------------------------------------------");
provider.getConfiguration().getValues(true).forEach((k, v) -> System.out.println(k + ": " + v));
System.out.println("----------------------------------------------------");
provider.getConfiguration().getValues(false).forEach((k, v) -> System.out.println(k + ": " + v));
System.out.println("----------------------------------------------------");
ConfigurationTest.save(this.provider);
ConfigurationTest.save(this.holder);
}