Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Attention][Feature] Rewrite config #21

Open
wants to merge 15 commits into
base: dev/1.21
Choose a base branch
from
35 changes: 15 additions & 20 deletions base/common/src/main/java/band/kessoku/lib/api/KessokuLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@
package band.kessoku.lib.api;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.lang.reflect.Modifier;
import java.util.*;

import org.apache.logging.log4j.core.util.ReflectionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -33,23 +29,22 @@ public final class KessokuLib {
private KessokuLib() {
}

public static void loadModule(@NotNull final Class<?> moduleCommonClass) {
if (moduleCommonClass.isArray())
throw new UnsupportedOperationException("What the hell are you doing?? KessokuLib.loadModule receives an array class! " + moduleCommonClass.getName());
if (isModuleLoaded(moduleCommonClass)) {
throw new UnsupportedOperationException("Module `" + moduleCommonClass.getName() + "` has already been loaded!");
}
public static void loadModule(Class<?> moduleCommonClass) {
// Try to get module name
final String moduleName;
String moduleName;
try {
final Field field = moduleCommonClass.getField("NAME");
moduleName = (String) ReflectionUtil.getStaticFieldValue(field);
Field field = moduleCommonClass.getField("NAME");
if (!Modifier.isStatic(field.getModifiers())) {
throw new IllegalArgumentException("NAME in " + moduleCommonClass.getPackageName() + " is not static!");
}
field.setAccessible(true);
moduleName = (String) field.get(null);
} catch (NoSuchFieldException e) {
getLogger().error("Invalid Kessoku module! Field `NAME` is not found in {}!", moduleCommonClass.getName());
return;
} catch (NullPointerException e) {
getLogger().error("Invalid Kessoku module! NAME in {} is not static!", moduleCommonClass.getName());
return;
getLogger().warn("no NAME found for {}! Using package name", moduleCommonClass.getPackageName());
moduleName = moduleCommonClass.getPackageName();
} catch (IllegalAccessException e) {
// Already set accessible, shouldn't be called
moduleName = moduleCommonClass.getPackageName();
}
initializedModules.add(moduleCommonClass);
getLogger().info("{} loaded!", moduleName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*/
package band.kessoku.lib.api.base.reflect;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.Arrays;

import band.kessoku.lib.api.KessokuLib;

public final class ReflectUtil {
private ReflectUtil() {
}
Expand All @@ -31,4 +34,14 @@ public static boolean isAssignableFrom(Object o, Class<?>... classes) {
var flag = Arrays.stream(classes).anyMatch(clazz -> !o.getClass().isAssignableFrom(clazz));
return !flag;
}

public static boolean markAccessible(AccessibleObject obj) {
try {
obj.setAccessible(true);
return true;
} catch (Exception e) {
KessokuLib.getLogger().error(e.getMessage(), e);
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright (c) 2024 KessokuTeaTime
*
* Licensed under the GNU Lesser General Pubic License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/lgpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package band.kessoku.lib.api.config;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.*;

import band.kessoku.lib.api.KessokuLib;
import band.kessoku.lib.api.base.reflect.ModifiersUtil;
import band.kessoku.lib.api.base.reflect.ReflectUtil;
import band.kessoku.lib.api.config.api.*;
import club.someoneice.json.Pair;
import com.google.common.collect.*;
import com.google.common.io.Files;

/**
* The config handler, also see {@link Config Config}. <br>
* The config format or file type determined by config codec.
*
* @see Config Config
* @see Codec Codec
* @see ConfigBasicCodec Basic Config Codec
*
* @author AmarokIce
*/
public final class ConfigHandler {
private static Path configDir;
/**
* All mods config(s).
*/
private static final Table<String, String, Pair<Class<?>, ConfigHandler>> MOD_CONFIG_DATA = HashBasedTable.create();

/**
* Record all config fields, next time to use needn't scan it again.
*
* @see ConfigHandler#readByClass(Class, ConfigHandler)
* @see ConfigHandler#saveToClass(Class, ConfigHandler, Map)
*/
private ImmutableList<Field> fields;

/**
* Format codec for {@code Config} and {@code Category}. <br>
*
* @see Category
*/
private final Codec<Map<String, ConfigData>> formatCodec;
private final Class<?> clazz;

private ConfigHandler(Codec<Map<String, ConfigData>> codec, Class<?> clazz) {
this.formatCodec = codec;
this.clazz = clazz;
}

private static void registerConfig(Class<?> clazz) throws IllegalAccessException, IOException {
Config configSetting = clazz.getAnnotation(Config.class);
final String modid = configSetting.value();
final String name = configSetting.name().isEmpty() ? configSetting.value() : configSetting.name();
final var codec = ConfigBasicCodec.getCodec(configSetting.codec());

ConfigHandler configHandler = new ConfigHandler(codec, clazz);
MOD_CONFIG_DATA.put(modid, name, new Pair<>(clazz, configHandler));

var defMap = readByClass(clazz, configHandler);

File cfgFile = ConfigHandler.configDir.resolve(name).toFile();
if (!cfgFile.exists()) {
cfgFile.createNewFile();
}

String data = Arrays.toString(Files.toByteArray(ConfigHandler.configDir.toFile()));
var localMap = codec.encode(data);

Map<String, ConfigData> commands = Maps.newLinkedHashMap();
for (Map.Entry<String, ConfigData> entry : defMap.entrySet()) {
String key = entry.getKey();
commands.put(key, localMap.containsKey(key) ? localMap.get(key) : entry.getValue());
}

saveToClass(clazz, configHandler, commands);
data = codec.decode(commands);
Files.write(data.getBytes(), cfgFile);
}

public static Map<String, ConfigData> readByClass(Class<?> clazz, ConfigHandler cfg) throws IllegalAccessException {
Map<String, ConfigData> map = Maps.newLinkedHashMap();

if (Objects.isNull(cfg.fields)) {
scanFields(clazz, cfg);
}

for (Field field : cfg.fields) {
String name = field.isAnnotationPresent(Name.class) ? field.getAnnotation(Name.class).value() : field.getName();
List<String> comments = field.isAnnotationPresent(Comment.class) ? Lists.newArrayList(field.getAnnotation(Comment.class).value()) : Collections.emptyList();

String data = ReflectUtil.isAssignableFrom(field, Category.class)
? cfg.formatCodec.decode(readByClass(field.getType(), cfg))
: ((ConfigValue<?>) field.get(null)).decode();

map.put(name, new ConfigData(name, data, comments));
}

return map;
}

public static void saveToClass(Class<?> clazz, ConfigHandler cfg, Map<String, ConfigData> data) throws IllegalAccessException {
if (Objects.isNull(cfg.fields)) {
scanFields(clazz, cfg);
}

for (Field field : cfg.fields) {
String name = field.isAnnotationPresent(Name.class) ? field.getAnnotation(Name.class).value() : field.getName();
var raw = ((ConfigValue<?>) field.get(null));
if (ReflectUtil.isAssignableFrom(field, Category.class)) {
saveToClass(field.getType(), cfg, cfg.formatCodec.encode(data.get(name).rawValue()));
} else {
raw.encode(data.get(name).rawValue());
}
}
}

public static void scanFields(Class<?> clazz, ConfigHandler cfg) {
ImmutableList.Builder<Field> builder = ImmutableList.builder();
for (Field field : clazz.getDeclaredFields()) {
if (!ModifiersUtil.isStatic(field)) {
continue;
}

if (!ReflectUtil.isAssignableFrom(field, ConfigValue.class)) {
continue;
}

ReflectUtil.markAccessible(field);
builder.add(field);
}

cfg.fields = builder.build();
}

/**
* The magic method for auto scan all config class with {@link Config @Config}.
*
* @see Config @Config
* @param configDir The based config directory, get by loader.
*/
public static void handleConfigs(List<Class<?>> list, Path configDir) {
ConfigHandler.configDir = configDir;
list.forEach(it -> {
try {
registerConfig(it);
} catch (Exception e) {
KessokuLib.getLogger().error(e.getMessage(), e);
}
});
}
}

This file was deleted.

Loading