diff --git a/base/common/src/main/java/band/kessoku/lib/api/KessokuLib.java b/base/common/src/main/java/band/kessoku/lib/api/KessokuLib.java index 7eeabc58..a2c1d87e 100644 --- a/base/common/src/main/java/band/kessoku/lib/api/KessokuLib.java +++ b/base/common/src/main/java/band/kessoku/lib/api/KessokuLib.java @@ -16,9 +16,13 @@ package band.kessoku.lib.api; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; +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; @@ -29,21 +33,23 @@ public final class KessokuLib { private KessokuLib() { } - public static void loadModule(Class moduleCommonClass) { + public static void loadModule(@NotNull 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!"); + } // Try to get module name String moduleName; try { - 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); + final Field field = moduleCommonClass.getField("NAME"); + moduleName = (String) ReflectionUtil.getStaticFieldValue(field); } catch (NoSuchFieldException e) { - 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(); + moduleName = moduleCommonClass.getName(); + getLogger().warn("Field `NAME` is not found in {}! Using fully qualified class name.", moduleName); + } catch (NullPointerException e) { + moduleName = moduleCommonClass.getName(); + getLogger().warn("NAME in {} is not static! Using package name.", moduleName); } initializedModules.add(moduleCommonClass); getLogger().info("{} loaded!", moduleName); diff --git a/base/common/src/main/java/band/kessoku/lib/api/base/KessokuUtils.java b/base/common/src/main/java/band/kessoku/lib/api/base/KessokuUtils.java index d05cbb5e..5cc2c358 100644 --- a/base/common/src/main/java/band/kessoku/lib/api/base/KessokuUtils.java +++ b/base/common/src/main/java/band/kessoku/lib/api/base/KessokuUtils.java @@ -15,8 +15,10 @@ */ package band.kessoku.lib.api.base; -import java.lang.reflect.Constructor; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import org.jetbrains.annotations.NotNull; @@ -24,8 +26,8 @@ public final class KessokuUtils { private KessokuUtils() { } - public static boolean isType(final List list, final Class type) { - for (final Object element : list) { + public static boolean isType(final Collection collection, final Class type) { + for (final Object element : collection) { if (!(type.isInstance(element))) { return false; } @@ -33,16 +35,6 @@ public static boolean isType(final List list, final Class type) { return true; } - public static V constructUnsafely(final Class cls) { - try { - final Constructor constructor = cls.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - public static @NotNull Set getKeysByValue(final Map map, final V value) { final Set result = new HashSet<>(); map.forEach((k, v) -> { diff --git a/base/common/src/main/java/band/kessoku/lib/api/base/reflect/ReflectUtil.java b/base/common/src/main/java/band/kessoku/lib/api/base/reflect/ReflectUtil.java index 138c347a..ff4e2e5c 100644 --- a/base/common/src/main/java/band/kessoku/lib/api/base/reflect/ReflectUtil.java +++ b/base/common/src/main/java/band/kessoku/lib/api/base/reflect/ReflectUtil.java @@ -22,8 +22,13 @@ public final class ReflectUtil { private ReflectUtil() { } - public static boolean isAssignableFrom(Field field, Class... clazzs) { - var flag = Arrays.stream(clazzs).anyMatch(clazz -> !field.getType().isAssignableFrom(clazz)); + public static boolean isAssignableFrom(Field field, Class... classes) { + var flag = Arrays.stream(classes).anyMatch(clazz -> !field.getType().isAssignableFrom(clazz)); + return !flag; + } + + public static boolean isAssignableFrom(Object o, Class... classes) { + var flag = Arrays.stream(classes).anyMatch(clazz -> !o.getClass().isAssignableFrom(clazz)); return !flag; } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/AbstractConfig.java b/config/common/src/main/java/band/kessoku/lib/api/config/AbstractConfig.java deleted file mode 100644 index d3e35313..00000000 --- a/config/common/src/main/java/band/kessoku/lib/api/config/AbstractConfig.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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.FileWriter; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -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.annotations.Comment; -import band.kessoku.lib.api.config.annotations.Comments; -import band.kessoku.lib.api.config.annotations.Name; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; -import org.apache.logging.log4j.core.util.ReflectionUtil; - -@SuppressWarnings({"rawtypes", "unused"}) -public abstract class AbstractConfig { - private final List> preSave = new ArrayList<>(); - private final List> preLoad = new ArrayList<>(); - private final List> postSave = new ArrayList<>(); - private final List> postLoad = new ArrayList<>(); - private List values; - private List categories; - private boolean split = false; - - public AbstractConfig() { - } - - public boolean save() { - preSave.forEach(consumer -> consumer.accept(this)); - File file = this.getPath().toFile(); - ConfigSerializer serializer = this.getSerializer(); - - boolean result = true; - try (FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8)) { - writer.write(serializer.serialize(this.serialize())); - } catch (IOException e) { - result = false; - } - - final boolean finalResult = result; - postSave.forEach(biConsumer -> biConsumer.accept(this, finalResult)); - return finalResult; - } - - public boolean load() { - preLoad.forEach(consumer -> consumer.accept(this)); - ConfigSerializer serializer = this.getSerializer(); - File file = this.getPath().toFile(); - - boolean result = true; - try { - Map map = serializer.deserialize(FileUtils.readFileToString(file, StandardCharsets.UTF_8)); - // Put values into the config - for (Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - Object cValue = entry.getValue(); - - ConfigValue value; - // Check the value is public and not static - try { - Field field = this.getClass().getField(key); - if (!ModifiersUtil.isPublicOrStatic(field, true, false)) { - continue; - } - value = (ConfigValue) ReflectionUtil.getFieldValue(field, this); - } catch (NoSuchFieldException e) { - continue; - } - - ConfigValue.Type type = ConfigValue.Type.asType(cValue); - // Check if the type is valid to deserialize - if (type == ConfigValue.Type.NULL) { - KessokuLib.getLogger().error(KessokuConfig.MARKER, "Illegal type`{}` found in the file!", cValue.getClass().getName()); - continue; - } - - // Check if the type matches the value's type - if (value.getType() != type) { - KessokuLib.getLogger().error(KessokuConfig.MARKER, "Illegal type`{}` found in the file! Expect {}.", type.toString().toLowerCase(), value.getType().toString().toLowerCase()); - continue; - } - - value.setTo(cValue); - } - } catch (IOException e) { - result = false; - } - - final boolean finalResult = result; - postLoad.forEach(biConsumer -> biConsumer.accept(this, finalResult)); - return finalResult; - } - - public void registerPreSaveListener(Consumer preSave) { - this.preSave.add(preSave); - } - - public void registerPreLoadListener(Consumer preLoad) { - this.preLoad.add(preLoad); - } - - public void registerPostSaveListener(BiConsumer postSave) { - this.postSave.add(postSave); - } - - public void registerPostLoadListener(BiConsumer postLoad) { - this.postLoad.add(postLoad); - } - - public void reset() { - this.getValidValues().forEach(ConfigValue::reset); - this.getValidCategories().forEach(AbstractConfig::reset); - } - - public ImmutableList> getValidValues() { - if (this.values != null) { - return ImmutableList.>builder().addAll(this.values.parallelStream().map(field -> - (ConfigValue) ReflectionUtil.getFieldValue(field, this)).toList()).build(); - } - - List fields = new ArrayList<>(); - for (Field declaredField : this.getClass().getDeclaredFields()) { - final boolean flag0 = ReflectUtil.isAssignableFrom(declaredField, ConfigValue.class); - final boolean flag1 = ModifiersUtil.isPublicOrStatic(declaredField, true, false); - if (flag0 && flag1) { - fields.add(declaredField); - } - } - - this.values = fields; - return this.getValidValues(); - } - - public ImmutableList getValidCategories() { - if (this.categories != null){ - return ImmutableList.builder().addAll(this.categories.stream().map(field -> - (AbstractConfig) ReflectionUtil.getFieldValue(field, this)).toList()).build(); - } - - List fields = new ArrayList<>(); - for (Field declaredField : this.getClass().getDeclaredFields()) { - declaredField.setAccessible(true); - - final boolean flag0 = ReflectUtil.isAssignableFrom(declaredField, AbstractConfig.class); - final boolean flag1 = ModifiersUtil.isPublic(declaredField); - if (flag0 && flag1){ - fields.add(declaredField); - } - } - - this.categories = fields; - return this.getValidCategories(); - } - - private ImmutableList getValidFields() { - ImmutableList.Builder builder = ImmutableList.builder(); - for (Field declaredField : this.getClass().getDeclaredFields()) { - final boolean flag0 = ReflectUtil.isAssignableFrom(declaredField, AbstractConfig.class, ConfigValue.class); - final boolean flag1 = Modifier.isPublic(declaredField.getModifiers()); - - if (flag0 && flag1){ - builder.add(declaredField); - } - } - return builder.build(); - } - - private Map serialize() { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Field field : this.getValidFields()) { - ReflectionUtil.makeAccessible(field); - - final String name = field.isAnnotationPresent(Name.class) ? field.getAnnotation(Name.class).value() : field.getName(); - final String[] comments = field.isAnnotationPresent(Comments.class) ? (String[]) Arrays.stream(field.getAnnotation(Comments.class).value()).map(Comment::value).toArray() : new String[0]; - - Object fieldValue = ReflectionUtil.getFieldValue(field, this); - - // ConfigValue - if (fieldValue instanceof ConfigValue value) { - builder.put(name, new ValueWithComment(value.get(), comments)); - continue; - } - - // Category - AbstractConfig category = (AbstractConfig) fieldValue; - if (this.split) { - if (!category.save()) { - KessokuLib.getLogger().error(KessokuConfig.MARKER, "Failed to save category `{}!`", category.getSimpleName()); - } - continue; - } - builder.put(name, new ValueWithComment(category.serialize(), comments)); - } - return builder.build(); - } - - protected void splitToFiles() { - this.split = true; - } - - public boolean isSplitToFiles() { - return this.split; - } - - /** - * Get config full name. If it's a sub file, it will include the paths. - * @return Get the full name of the config. - */ - // Not including file ext, but parent path `/` - public String getName() { - Name name = this.getClass().getAnnotation(Name.class); - return name == null ? this.getClass().getSimpleName() : name.value(); - } - - /** - * Get the config name. Even it's a sub file, just config name. - * @return The simple config name. - */ - public String getSimpleName() { - String[] strings = this.getName().split("/"); - return strings[strings.length - 1]; - } - - public Path getPath() { - return Path.of(FilenameUtils.normalize(this.getName() + "." + this.getSerializer().getFileExtension()).replace('/', File.separatorChar)); - } - - public ConfigSerializer getSerializer() { - return KessokuConfig.getSerializerInstance(Objects.requireNonNull(KessokuConfig.getSerializer(this), String.format("Config %s isn't registered!", this.getName()))); - } - - public record ValueWithComment(Object object, String[] comments) { - } -} diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/Config.java b/config/common/src/main/java/band/kessoku/lib/api/config/Config.java new file mode 100644 index 00000000..d699be53 --- /dev/null +++ b/config/common/src/main/java/band/kessoku/lib/api/config/Config.java @@ -0,0 +1,6 @@ +package band.kessoku.lib.api.config; + +import band.kessoku.lib.impl.config.AbstractConfig; + +public class Config extends AbstractConfig { +} diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/ConfigSerializer.java b/config/common/src/main/java/band/kessoku/lib/api/config/ConfigSerializer.java index fe36bfe5..eb6038c5 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/ConfigSerializer.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/ConfigSerializer.java @@ -15,10 +15,12 @@ */ package band.kessoku.lib.api.config; +import band.kessoku.lib.impl.config.AbstractConfig; + import java.util.Map; public interface ConfigSerializer { - String serialize(Map value); + String serialize(Map valueMap); Map deserialize(String value); diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/ConfigValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/ConfigValue.java index 0a7f28fb..881ae8cd 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/ConfigValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/ConfigValue.java @@ -15,6 +15,8 @@ */ package band.kessoku.lib.api.config; +import band.kessoku.lib.api.base.reflect.ReflectUtil; + import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -42,25 +44,47 @@ default F get() { T getDefaultTo(); enum Type { - LIST, MAP, BOOLEAN, STRING, INTEGER, LONG, FLOAT, DOUBLE, NULL; + ARRAY(List.class), + MAP(Map.class), + BOOLEAN(Boolean.class), + STRING(String.class), + INTEGER(Character.class, Byte.class, Short.class, Integer.class, Long.class), + DECIMAL(Float.class, Double.class), + NULL(Void.class); + + public final Class[] classes; + + Type(Class... classes) { + this.classes = classes; + } public static Type asType(Object o) { - return switch (o) { - case List ignored -> LIST; - case Map ignored -> MAP; - case Boolean ignored -> BOOLEAN; - case String ignored -> STRING; - case Long ignored -> LONG; - case Integer ignored -> INTEGER; - case Float ignored -> FLOAT; - case Double ignored -> DOUBLE; - - /* AmarokIce Note: - try catch 对性能会造成额外影响。此处 throw 后在 { @code AbstractConfig } 中捕获是无意义的。 - 因此,改用 NULL 作为空置对象。 - */ - case null, default -> NULL; - // case null, default -> throw new IllegalArgumentException(); + if (o.getClass().isArray()) return ARRAY; + for (Type type : Type.values()) { + if (type.is(o)) return type; + } + return NULL; + } + + public boolean canCastFrom(Type type) { + // todo + return this == type; + } + + public boolean is(Object o) { + return ReflectUtil.isAssignableFrom(o, this.classes); + } + + public Object getDefaultValue() { + return switch (this) { + case ARRAY -> new Object[0]; + case MAP -> Map.of(); + case BOOLEAN -> false; + case STRING -> ""; + case INTEGER -> 0L; + case DECIMAL -> 0.0d; + + default -> null; }; } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/KessokuConfig.java b/config/common/src/main/java/band/kessoku/lib/api/config/KessokuConfig.java index 33b167d2..b0b9e852 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/KessokuConfig.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/KessokuConfig.java @@ -15,12 +15,24 @@ */ package band.kessoku.lib.api.config; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import band.kessoku.lib.api.KessokuLib; +import band.kessoku.lib.api.base.reflect.ModifiersUtil; +import band.kessoku.lib.impl.config.AbstractConfig; import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.core.util.ReflectionUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,42 +40,146 @@ import org.slf4j.MarkerFactory; public final class KessokuConfig { - private static final Map> configs = new HashMap<>(); + private static final Map configs = new HashMap<>(); private static final Map, ConfigSerializer> serializerCache = new HashMap<>(); public static final String MOD_ID = "kessoku_config"; public static final String NAME = "Kessoku Config API"; - public static final Marker MARKER = MarkerFactory.getMarker("[" + NAME +"]"); + public static final Marker MARKER = MarkerFactory.getMarker("[" + NAME + "]"); - @SuppressWarnings({"unchecked", "unused"}) - public static T register(T config, Class serializer) { - if (config.getClass().isAnonymousClass()) throw new IllegalArgumentException(); + private static final Map>> preSave = new HashMap<>(); + private static final Map>> postSave = new HashMap<>(); + private static final Map>> preLoad = new HashMap<>(); + private static final Map>> postLoad = new HashMap<>(); + @SuppressWarnings("unused") + public static T register(Class configClass, Class serializer) { + Objects.requireNonNull(serializer, "Serializer shouldn't be null!"); + Objects.requireNonNull(configClass, "Config shouldn't be null!"); + + // Create the config instance + T config = ReflectionUtil.instantiate(configClass); + + // Create config file try { FileUtils.touch(config.getPath().toFile()); } catch (IOException e) { throw new RuntimeException(e); } - configs.put(config, (Class) serializer); + configs.put(config, getSerializerInstance(serializer)); return config; } + public static boolean save(AbstractConfig config) { + // Pre save + preSave.getOrDefault(config, List.of()).forEach(consumer -> consumer.accept(config)); + File configFile = config.getPath().toFile(); + ConfigSerializer serializer = config.getSerializer(); + + boolean success = true; + try (FileWriter writer = new FileWriter(configFile, StandardCharsets.UTF_8)) { + writer.write(serializer.serialize(config.serialize())); + } catch (IOException e) { + success = false; + } + + // post save + final boolean finalResult = success; + postSave.getOrDefault(config, List.of()).forEach(biConsumer -> biConsumer.accept(config, finalResult)); + return success; + } + + public static boolean load(AbstractConfig config) { + // pre load + preLoad.getOrDefault(config, List.of()).forEach(consumer -> consumer.accept(config)); + ConfigSerializer serializer = config.getSerializer(); + File file = config.getPath().toFile(); + if (!file.exists()) { + config.save(); + return false; + } + + boolean success = true; + try { + Map map = serializer.deserialize(FileUtils.readFileToString(file, StandardCharsets.UTF_8)); + // Put values into the config + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object loadedValue = entry.getValue(); + + ConfigValue configValue; + // Check the value is public and not static + try { + Field field = config.getClass().getField(key); + if (!ModifiersUtil.isPublicOrStatic(field, true, false)) { + continue; + } + if (!field.getDeclaringClass().isAssignableFrom(ConfigValue.class)) { + continue; + } + configValue = (ConfigValue) ReflectionUtil.getFieldValue(field, config); + } catch (NoSuchFieldException e) { + // not found + continue; + } + + ConfigValue.Type type = ConfigValue.Type.asType(loadedValue); + // Check if the type is valid to deserialize + if (type == ConfigValue.Type.NULL) { + KessokuLib.getLogger().error(KessokuConfig.MARKER, "Illegal type`{}` found in the file!", loadedValue.getClass().getName()); + continue; + } + + // Check if the type matches the value's type + if (configValue.getType() != type) { + KessokuLib.getLogger().error(KessokuConfig.MARKER, "Illegal type`{}` found in the file! Expect {}.", type.toString().toLowerCase(), configValue.getType().toString().toLowerCase()); + continue; + } + + configValue.setTo(loadedValue); + } + } catch (IOException e) { + success = false; + } + + final boolean finalResult = success; + postLoad.getOrDefault(config, List.of()).forEach(biConsumer -> biConsumer.accept(config, finalResult)); + return success; + } + + public static void registerPreSaveListener(final AbstractConfig config, Consumer preSaveConsumer) { + Objects.requireNonNull(preSave.putIfAbsent(config, new ArrayList<>())).add(preSaveConsumer); + } + + public static void registerPreLoadListener(AbstractConfig config, Consumer preLoadConsumer) { + Objects.requireNonNull(preLoad.putIfAbsent(config, new ArrayList<>())).add(preLoadConsumer); + } + + public static void registerPostSaveListener(AbstractConfig config, BiConsumer postSaveConsumer) { + Objects.requireNonNull(postSave.putIfAbsent(config, new ArrayList<>())).add(postSaveConsumer); + } + + public static void registerPostLoadListener(AbstractConfig config, BiConsumer postLoadConsumer) { + Objects.requireNonNull(postLoad.putIfAbsent(config, new ArrayList<>())).add(postLoadConsumer); + } + + // Serializers @Contract(pure = true) @Nullable - public static Class getSerializer(T config) { + public static ConfigSerializer getSerializer(T config) { return configs.get(config); } @Contract(pure = true) @SuppressWarnings("unchecked") @NotNull - public static T getSerializerInstance(Class serializer) { + private static T getSerializerInstance(Class serializer) { try { return (T) Objects.requireNonNull(serializerCache.containsKey(serializer) ? serializerCache.get(serializer) : serializerCache.put(serializer, - serializer.getConstructor().newInstance())); + serializer.getConstructor().newInstance())); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/annotations/Comment.java b/config/common/src/main/java/band/kessoku/lib/api/config/annotations/Comment.java index 808ed7f3..11b7a050 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/annotations/Comment.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/annotations/Comment.java @@ -15,7 +15,11 @@ */ package band.kessoku.lib.api.config.annotations; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/Json5Serializer.java b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/Json5Serializer.java index 76920690..5d2330df 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/Json5Serializer.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/Json5Serializer.java @@ -15,9 +15,10 @@ */ package band.kessoku.lib.api.config.serializers; +import java.util.HashMap; import java.util.Map; -import band.kessoku.lib.api.config.AbstractConfig; +import band.kessoku.lib.impl.config.AbstractConfig; import band.kessoku.lib.api.config.ConfigSerializer; import club.someoneice.json.JSON; import club.someoneice.json.node.JsonNode; @@ -26,33 +27,20 @@ // TODO public class Json5Serializer implements ConfigSerializer { @Override - public String serialize(Map value) { + public String serialize(Map valueMap) { return ""; } @Override @SuppressWarnings("unchecked") public Map deserialize(String value) { - return (Map) JSON.json5.parse(value).asTypeNodeOrThrow(JsonNode.NodeType.Map).getObj(); + Map map = new HashMap<>(); + ((Map>) JSON.json5.parse(value).asTypeNodeOrThrow(JsonNode.NodeType.Map).getObj()).forEach((key, jsonNode) -> map.put(key, jsonNode.getObj())); + return map; } @Override public String getFileExtension() { return "json5"; } - - private Json5Builder.ObjectBean toBean(Map value) { - Json5Builder builder = new Json5Builder(); - Json5Builder.ObjectBean objectBean = builder.getObjectBean(); - value.forEach((s, valueWithComment) -> { - for (String comment : valueWithComment.comments()) { - objectBean.addNote(comment); - } - - if (valueWithComment.object() instanceof Map) { - objectBean.addBean(s, this.toBean((Map) valueWithComment.object())); - } - }); - return objectBean; - } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/JsonSerializer.java b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/JsonSerializer.java index 0d8be82e..0773a25c 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/JsonSerializer.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/JsonSerializer.java @@ -15,9 +15,10 @@ */ package band.kessoku.lib.api.config.serializers; +import java.util.HashMap; import java.util.Map; -import band.kessoku.lib.api.config.AbstractConfig; +import band.kessoku.lib.impl.config.AbstractConfig; import band.kessoku.lib.api.config.ConfigSerializer; import club.someoneice.json.JSON; import club.someoneice.json.Pair; @@ -26,21 +27,18 @@ import club.someoneice.json.processor.JsonBuilder; public class JsonSerializer implements ConfigSerializer { + // TODO @Override - public String serialize(Map value) { - MapNode node = new MapNode(); - value.entrySet().stream() - .map(it -> new Pair<>(it.getKey(), JsonNode.asJsonNodeOrEmpty(it.getValue().object()))) - .filter(it -> it.getValue().nonNull()) - .forEach(it -> node.put(it.getKey(), it.getValue())); - - return JsonBuilder.prettyPrint(node); + public String serialize(Map valueMap) { + return ""; } @Override @SuppressWarnings("unchecked") public Map deserialize(String value) { - return (Map) JSON.json.parse(value).asTypeNodeOrThrow(JsonNode.NodeType.Map).getObj(); + Map map = new HashMap<>(); + ((Map>) JSON.json.parse(value).asTypeNodeOrThrow(JsonNode.NodeType.Map).getObj()).forEach((key, jsonNode) -> map.put(key, jsonNode.getObj())); + return map; } @Override diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/TomlSerializer.java b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/TomlSerializer.java index f4ad88b8..a3d0dd00 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/serializers/TomlSerializer.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/serializers/TomlSerializer.java @@ -17,16 +17,14 @@ import java.util.Map; -import band.kessoku.lib.api.config.AbstractConfig; +import band.kessoku.lib.impl.config.AbstractConfig; import band.kessoku.lib.api.config.ConfigSerializer; import com.electronwill.nightconfig.core.CommentedConfig; -//todo +// todo public class TomlSerializer implements ConfigSerializer { @Override - public String serialize(Map value) { - CommentedConfig config = CommentedConfig.inMemory(); - value.forEach(config::set); + public String serialize(Map value) { return ""; } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/BooleanValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/BooleanValue.java index 2e0b35cf..d0e8e8b8 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/BooleanValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/BooleanValue.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; public final class BooleanValue extends DefaultConfigValue { - private BooleanValue(Supplier defaultValue) { + private BooleanValue(final Supplier defaultValue) { super(defaultValue); } @@ -31,12 +31,14 @@ public Type getType() { } @Contract("_ -> new") - public static @NotNull BooleanValue of(boolean bool) { + @NotNull + public static BooleanValue of(final boolean bool) { return new BooleanValue(() -> bool); } @Contract("_ -> new") - public static @NotNull BooleanValue of(Supplier booleanSupplier) { + @NotNull + public static BooleanValue of(final Supplier booleanSupplier) { return new BooleanValue(booleanSupplier); } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/DoubleValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/DecimalValue.java similarity index 69% rename from config/common/src/main/java/band/kessoku/lib/api/config/values/DoubleValue.java rename to config/common/src/main/java/band/kessoku/lib/api/config/values/DecimalValue.java index 84714e41..4c88f8d5 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/DoubleValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/DecimalValue.java @@ -20,23 +20,25 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -public final class DoubleValue extends DefaultConfigValue { - private DoubleValue(Supplier defaultValue) { +public final class DecimalValue extends DefaultConfigValue { + private DecimalValue(final Supplier defaultValue) { super(defaultValue); } @Override public Type getType() { - return Type.DOUBLE; + return Type.DECIMAL; } @Contract("_ -> new") - public static @NotNull DoubleValue of(double d) { - return new DoubleValue(() -> d); + @NotNull + public static DecimalValue of(final double d) { + return new DecimalValue(() -> d); } @Contract("_ -> new") - public static @NotNull DoubleValue of(Supplier doubleSupplier) { - return new DoubleValue(doubleSupplier); + @NotNull + public static DecimalValue of(final Supplier decimalSupplier) { + return new DecimalValue(decimalSupplier); } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/DefaultConfigValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/DefaultConfigValue.java index cc3f8f76..75c9b3f1 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/DefaultConfigValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/DefaultConfigValue.java @@ -19,7 +19,7 @@ import band.kessoku.lib.api.config.ConfigValue; -sealed abstract class DefaultConfigValue implements ConfigValue permits BooleanValue, DoubleValue, FloatValue, IntegerValue, ListValue, LongValue, MapValue, StringValue { +sealed abstract class DefaultConfigValue implements ConfigValue permits BooleanValue, DecimalValue, ListValue, IntegerValue, MapValue, StringValue { public final Supplier defaultValue; public T value; diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/FloatValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/FloatValue.java deleted file mode 100644 index 07c5909b..00000000 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/FloatValue.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.values; - -import java.util.function.Supplier; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -public final class FloatValue extends DefaultConfigValue { - private FloatValue(Supplier defaultValue) { - super(defaultValue); - } - - @Override - public Type getType() { - return Type.FLOAT; - } - - @Contract("_ -> new") - public static @NotNull FloatValue of(float f) { - return new FloatValue(() -> f); - } - - @Contract("_ -> new") - public static @NotNull FloatValue of(Supplier floatSupplier) { - return new FloatValue(floatSupplier); - } -} diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/IntegerValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/IntegerValue.java index b2f81974..31bf0d4e 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/IntegerValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/IntegerValue.java @@ -20,8 +20,8 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -public final class IntegerValue extends DefaultConfigValue { - private IntegerValue(Supplier defaultValue) { +public final class IntegerValue extends DefaultConfigValue { + private IntegerValue(final Supplier defaultValue) { super(defaultValue); } @@ -31,12 +31,14 @@ public Type getType() { } @Contract("_ -> new") - public static @NotNull IntegerValue of(int integer) { - return new IntegerValue(() -> integer); + @NotNull + public static IntegerValue of(final long l) { + return new IntegerValue(() -> l); } @Contract("_ -> new") - public static @NotNull IntegerValue of(Supplier integerSupplier) { + @NotNull + public static IntegerValue of(final Supplier integerSupplier) { return new IntegerValue(integerSupplier); } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/ListValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/ListValue.java index 43ed0d08..86db4a76 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/ListValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/ListValue.java @@ -15,23 +15,28 @@ */ package band.kessoku.lib.api.config.values; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.function.Supplier; -import com.google.common.collect.ImmutableList; +import band.kessoku.lib.api.config.ConfigValue; +import com.google.gson.internal.reflect.ReflectionHelper; +import com.sun.jna.internal.ReflectionUtils; +import io.netty.util.internal.ReflectionUtil; +import org.apache.commons.lang3.reflect.MethodUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; @SuppressWarnings({"rawtypes"}) -public final class ListValue extends DefaultConfigValue> implements List { - private ListValue(Supplier> defaultValue) { +public final class ListValue> extends DefaultConfigValue> implements List { + private ListValue(final Supplier> defaultValue) { super(defaultValue); } @Override public Type getType() { - return Type.LIST; + return Type.ARRAY; } @Override @@ -41,31 +46,52 @@ public void reset() { } @Override - @SuppressWarnings({"unchecked"}) - public @NotNull @Unmodifiable List getDefaultFrom() { - return (ImmutableList) ImmutableList.builder().add(super.getDefaultFrom()).build(); + @NotNull + @Unmodifiable + public List getDefaultFrom() { + return Collections.unmodifiableList(super.getDefaultFrom()); } @Override - @SuppressWarnings({"unchecked"}) - public @NotNull @Unmodifiable List getDefaultTo() { - return (ImmutableList) ImmutableList.builder().add(super.getDefaultTo()).build(); + @NotNull + @Unmodifiable + public List getDefaultTo() { + return Collections.unmodifiableList(super.getDefaultTo()); + } + + public List normalize() { + List list = new ArrayList<>(); + this.value.forEach(value -> { + if (List.of(Type.ARRAY,Type.MAP).contains(value.getType())) { + try { + list.add(MethodUtils.getAccessibleMethod(value.getClass(),"normalize").invoke(value)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + list.add(value.getTo()); + } + }); + return list; } // constructor @Contract("_ -> new") @SafeVarargs - public static @NotNull ListValue of(E... elements) { + @NotNull + public static > ListValue of(final E... elements) { return new ListValue<>(() -> new ArrayList<>(List.of(elements))); } @Contract("_ -> new") - public static @NotNull ListValue of(List list) { + @NotNull + public static > ListValue of(final List list) { return new ListValue<>(() -> new ArrayList<>(list)); } @Contract("_ -> new") - public static @NotNull ListValue of(Supplier> listSupplier) { + @NotNull + public static > ListValue of(final Supplier> listSupplier) { return new ListValue<>(listSupplier); } @@ -81,7 +107,7 @@ public boolean isEmpty() { } @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { return this.value.contains(o); } @@ -97,42 +123,43 @@ public boolean contains(Object o) { } @Override - public T1 @NotNull [] toArray(T1 @NotNull [] a) { + public T1 @NotNull [] toArray(final T1 @NotNull [] a) { return this.value.toArray(a); } @Override - public boolean add(T t) { + public boolean add(final T t) { return this.value.add(t); } @Override - public boolean remove(Object o) { + public boolean remove(final Object o) { return this.value.remove(o); } @Override - public boolean containsAll(@NotNull Collection c) { + @SuppressWarnings("SlowListContainsAll") + public boolean containsAll(@NotNull final Collection c) { return this.value.containsAll(c); } @Override - public boolean addAll(@NotNull Collection c) { + public boolean addAll(@NotNull final Collection c) { return this.value.addAll(c); } @Override - public boolean addAll(int index, @NotNull Collection c) { + public boolean addAll(final int index, @NotNull final Collection c) { return this.value.addAll(index, c); } @Override - public boolean removeAll(@NotNull Collection c) { + public boolean removeAll(@NotNull final Collection c) { return this.value.removeAll(c); } @Override - public boolean retainAll(@NotNull Collection c) { + public boolean retainAll(@NotNull final Collection c) { return this.value.retainAll(c); } @@ -142,47 +169,50 @@ public void clear() { } @Override - public T get(int index) { + public T get(final int index) { return this.value.get(index); } @Override - public T set(int index, T element) { + public T set(final int index, final T element) { return this.value.set(index, element); } @Override - public void add(int index, T element) { + public void add(final int index, final T element) { this.value.add(index, element); } @Override - public T remove(int index) { + public T remove(final int index) { return this.value.remove(index); } @Override - public int indexOf(Object o) { + public int indexOf(final Object o) { return this.value.indexOf(o); } @Override - public int lastIndexOf(Object o) { + public int lastIndexOf(final Object o) { return this.value.lastIndexOf(o); } @Override - public @NotNull ListIterator listIterator() { + @NotNull + public ListIterator listIterator() { return this.value.listIterator(); } @Override - public @NotNull ListIterator listIterator(int index) { + @NotNull + public ListIterator listIterator(final int index) { return this.value.listIterator(index); } @Override - public @NotNull List subList(int fromIndex, int toIndex) { + @NotNull + public List subList(final int fromIndex, final int toIndex) { return this.value.subList(fromIndex, toIndex); } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/LongValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/LongValue.java deleted file mode 100644 index 4418a959..00000000 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/LongValue.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.values; - -import java.util.function.Supplier; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -public final class LongValue extends DefaultConfigValue { - private LongValue(Supplier defaultValue) { - super(defaultValue); - } - - @Override - public Type getType() { - return Type.LONG; - } - - @Contract("_ -> new") - public static @NotNull LongValue of(long l) { - return new LongValue(() -> l); - } - - @Contract("_ -> new") - public static @NotNull LongValue of(Supplier longSupplier) { - return new LongValue(longSupplier); - } -} diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/MapValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/MapValue.java index c937ccae..492dd0cb 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/MapValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/MapValue.java @@ -15,20 +15,23 @@ */ package band.kessoku.lib.api.config.values; +import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; -import com.google.common.collect.ImmutableMap; +import band.kessoku.lib.api.config.ConfigValue; +import org.apache.commons.lang3.reflect.MethodUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; -public final class MapValue extends DefaultConfigValue> implements Map { - private MapValue(Supplier> defaultValue) { +public final class MapValue, V extends ConfigValue> extends DefaultConfigValue> implements Map { + private MapValue(final Supplier> defaultValue) { super(defaultValue); } @@ -43,25 +46,50 @@ public void reset() { } @Override - @SuppressWarnings({"unchecked"}) - public @NotNull @Unmodifiable Map getDefaultFrom() { - return (ImmutableMap) ImmutableMap.builder().putAll(super.getDefaultFrom()).build(); + @NotNull + @Unmodifiable + public Map getDefaultFrom() { + return Collections.unmodifiableMap(super.getDefaultFrom()); } @Override - @SuppressWarnings({"unchecked"}) - public @NotNull @Unmodifiable Map getDefaultTo() { - return (ImmutableMap) ImmutableMap.builder().putAll(super.getDefaultTo()).build(); + @NotNull + @Unmodifiable + public Map getDefaultTo() { + return Collections.unmodifiableMap(super.getDefaultTo()); + } + + public Map normalize() { + HashMap map = new HashMap<>(); + this.value.forEach((key, value) -> { + if (List.of(Type.ARRAY, Type.MAP).contains(value.getType())) { + try { + map.put(key.getTo(), MethodUtils.getAccessibleMethod(value.getClass(), "normalize").invoke(value)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + map.put(key.getTo(), value.getTo()); + } + }); + return map; } // constructor + @NotNull + public static , V extends ConfigValue> MapValue of() { + return new MapValue<>(HashMap::new); + } + @Contract("_ -> new") - public static @NotNull MapValue of(Map m) { + @NotNull + public static , V extends ConfigValue> MapValue of(final Map m) { return new MapValue<>(() -> m); } @Contract("_ -> new") - public static @NotNull MapValue of(Supplier> mapSupplier) { + @NotNull + public static , V extends ConfigValue> MapValue of(final Supplier> mapSupplier) { return new MapValue<>(mapSupplier); } @@ -77,32 +105,32 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return this.value.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return this.value.containsValue(value); } @Override - public V get(Object key) { + public V get(final Object key) { return this.value.get(key); } @Override - public @Nullable V put(K key, V value) { + public V put(final K key, final V value) { return this.value.put(key, value); } @Override - public V remove(Object key) { + public V remove(final Object key) { return this.value.remove(key); } @Override - public void putAll(@NotNull Map m) { + public void putAll(@NotNull final Map m) { this.value.putAll(m); } @@ -112,17 +140,20 @@ public void clear() { } @Override - public @NotNull Set keySet() { + @NotNull + public Set keySet() { return this.value.keySet(); } @Override - public @NotNull Collection values() { + @NotNull + public Collection values() { return this.value.values(); } @Override - public @NotNull Set> entrySet() { + @NotNull + public Set> entrySet() { return this.value.entrySet(); } } diff --git a/config/common/src/main/java/band/kessoku/lib/api/config/values/StringValue.java b/config/common/src/main/java/band/kessoku/lib/api/config/values/StringValue.java index 8d0ff915..02a52665 100644 --- a/config/common/src/main/java/band/kessoku/lib/api/config/values/StringValue.java +++ b/config/common/src/main/java/band/kessoku/lib/api/config/values/StringValue.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; public final class StringValue extends DefaultConfigValue { - private StringValue(Supplier defaultValue) { + private StringValue(final Supplier defaultValue) { super(defaultValue); } @@ -31,12 +31,14 @@ public Type getType() { } @Contract("_ -> new") - public static @NotNull StringValue of(String s) { + @NotNull + public static StringValue of(final String s) { return new StringValue(() -> s); } @Contract("_ -> new") - public static @NotNull StringValue of(Supplier stringSupplier) { + @NotNull + public static StringValue of(final Supplier stringSupplier) { return new StringValue(stringSupplier); } } diff --git a/config/common/src/main/java/band/kessoku/lib/impl/config/AbstractConfig.java b/config/common/src/main/java/band/kessoku/lib/impl/config/AbstractConfig.java new file mode 100644 index 00000000..ddd80653 --- /dev/null +++ b/config/common/src/main/java/band/kessoku/lib/impl/config/AbstractConfig.java @@ -0,0 +1,190 @@ +/* + * 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.impl.config; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +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.ConfigSerializer; +import band.kessoku.lib.api.config.ConfigValue; +import band.kessoku.lib.api.config.KessokuConfig; +import band.kessoku.lib.api.config.annotations.Name; +import band.kessoku.lib.api.config.values.MapValue; +import band.kessoku.lib.api.config.values.StringValue; +import org.apache.logging.log4j.core.util.ReflectionUtil; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; + +@SuppressWarnings({"unused", "UnusedReturnValue", "unchecked", "rawtypes"}) +// todo Change the name +public class AbstractConfig implements ConfigValue>>, Cloneable { + private List valuesCache; + private List categoriesCache; + + public AbstractConfig() { + } + + @Override + public final void setFrom(final AbstractConfig value) { + this.setTo(value.getTo()); + } + + @Override + public final void setTo(final MapValue> value) { + for (Map.Entry> entry : value.entrySet()) { + final String key = entry.getKey().getTo(); + final ConfigValue fromConfigValue = entry.getValue(); + final Field field; + try { + field = this.getClass().getField(key); + } catch (NoSuchFieldException e) { + KessokuLib.getLogger().info(KessokuConfig.MARKER, "{} doesn't exist in {}!", key, this.getName()); + continue; + } + if (!ReflectUtil.isAssignableFrom(field, ConfigValue.class)) { + KessokuLib.getLogger().info(KessokuConfig.MARKER, "{} from {} is not a ConfigValue!", key, this.getName()); + continue; + } + if (ModifiersUtil.isStatic(field)) { + KessokuLib.getLogger().warn(KessokuConfig.MARKER, "Invalid ConfigValue {} found in config {}! It shouldn't be static but it is!", key, this.getName()); + continue; + } + ReflectionUtil.makeAccessible(field); + final ConfigValue configValue; + try { + configValue = (ConfigValue) field.get(this); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + if (!configValue.getType().canCastFrom(fromConfigValue.getType())) { + KessokuLib.getLogger().warn(KessokuConfig.MARKER, "{} and {} are incompatible!", fromConfigValue.getType().name(), configValue.getType().name()); + } + configValue.setTo(fromConfigValue.getTo()); + } + } + + @Override + public final AbstractConfig getFrom() { + return this; + } + + @Override + public final MapValue> getTo() { + final MapValue mapValue = MapValue.of(); + this.getValidFields().forEach(field -> { + final Name nameAnnotation = field.getAnnotation(Name.class); + final String name = nameAnnotation == null ? field.getName() : nameAnnotation.value(); + try { + mapValue.put(StringValue.of(name),(ConfigValue) field.get(this)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + return mapValue; + } + + public final void reset() { + this.getDeclaredValues().forEach(ConfigValue::reset); + this.getDeclaredCategories().forEach(AbstractConfig::reset); + } + + @Override + public final Type getType() { + return Type.MAP; + } + + @Override + public final AbstractConfig getDefaultFrom() { + AbstractConfig copy = (AbstractConfig) this.clone(); + copy.reset(); + return copy; + } + + @Override + public final MapValue> getDefaultTo() { + return getDefaultFrom().getTo(); + } + + @Unmodifiable + public final List> getDeclaredValues() { + if (this.valuesCache == null) { + this.valuesCache = this.getValidFields().stream().filter(field -> field.getDeclaringClass().isAssignableFrom(ConfigValue.class)).toList(); + } + return this.valuesCache.stream().map(field -> + (ConfigValue) ReflectionUtil.getFieldValue(field, this)).toList(); + } + + @Unmodifiable + public final List getDeclaredCategories() { + if (this.categoriesCache == null) { + this.categoriesCache = this.getValidFields().stream().filter(field -> field.getDeclaringClass().isAssignableFrom(AbstractConfig.class)).toList(); + } + return this.categoriesCache.stream().map(field -> + (AbstractConfig) ReflectionUtil.getFieldValue(field, this)).toList(); + } + + private List getValidFields() { + final List fields = new ArrayList<>(); + for (Field declaredField : this.getClass().getDeclaredFields()) { + final boolean isValidType = ReflectUtil.isAssignableFrom(declaredField, AbstractConfig.class, ConfigValue.class); + final boolean isValidModifier = ModifiersUtil.isPublicOrStatic(declaredField, true, false); + + if (isValidType && isValidModifier) { + fields.add(declaredField); + } + } + return Collections.unmodifiableList(fields); + } + + public final String getName() { + Name name = this.getClass().getAnnotation(Name.class); + return name == null ? this.getClass().getSimpleName() : name.value(); + } + + /** + * Get the path of this config + */ + public final Path getPath() { + // todo + return Path.of(""); + } + + public final ConfigSerializer getSerializer() { + return Objects.requireNonNull(KessokuConfig.getSerializer(this), String.format("Config %s isn't registered!", this.getName())); + } + + public record WrappedValue(ConfigValue configValue, String[] comments) { + } + + @Override + @ApiStatus.Internal + protected final Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // Shouldn't happen + throw new RuntimeException(e); + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd7804a2..2440ec93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,11 +5,11 @@ yarn = "1.21.1+build.3" yarn-patch-neoforge = "1.21+build.4" # Loaders -neo = "21.1.77" +neo = "21.1.83" fabric-loader = "0.16.9" # Mods -fabric-api = "0.108.0+1.21.1" +fabric-api = "0.110.0+1.21.1" # Plugin version loom = "1.7-SNAPSHOT"