From 3f28065ffa8807d2cae994d1c4cc1e7526655690 Mon Sep 17 00:00:00 2001 From: Redempt Date: Tue, 21 Dec 2021 23:41:20 -0500 Subject: [PATCH] Add ConfigName annotation to specify the path to a value in config --- res/plugin.yml | 2 +- src/redempt/redlib/config/ConfigField.java | 103 ++++++++++++ src/redempt/redlib/config/ConfigManager.java | 3 +- .../config/annotations/ConfigMappable.java | 2 + .../redlib/config/annotations/ConfigName.java | 16 ++ .../config/annotations/ConfigPostInit.java | 2 + .../config/conversion/ObjectConverter.java | 60 +------ .../conversion/StaticRootConverter.java | 34 +--- .../ConstructorInstantiator.java | 4 +- .../instantiation/EmptyInstantiator.java | 9 +- .../config/instantiation/FieldSummary.java | 155 ++++++++++++++++++ .../instantiation/InstantiationInfo.java | 52 ------ .../config/instantiation/Instantiator.java | 10 +- 13 files changed, 311 insertions(+), 141 deletions(-) create mode 100644 src/redempt/redlib/config/ConfigField.java create mode 100644 src/redempt/redlib/config/annotations/ConfigName.java create mode 100644 src/redempt/redlib/config/instantiation/FieldSummary.java delete mode 100644 src/redempt/redlib/config/instantiation/InstantiationInfo.java diff --git a/res/plugin.yml b/res/plugin.yml index 1994edd..3f0895a 100755 --- a/res/plugin.yml +++ b/res/plugin.yml @@ -1,6 +1,6 @@ name: RedLib main: redempt.redlib.RedLib -version: 2021-12-21 20:47 +version: 2021-12-22 04:41 author: Redempt api-version: 1.13 load: STARTUP diff --git a/src/redempt/redlib/config/ConfigField.java b/src/redempt/redlib/config/ConfigField.java new file mode 100644 index 0000000..bee546f --- /dev/null +++ b/src/redempt/redlib/config/ConfigField.java @@ -0,0 +1,103 @@ +package redempt.redlib.config; + +import redempt.redlib.config.annotations.ConfigName; + +import java.lang.reflect.Field; + +/** + * Wraps a Field and stores the name which should be used to store its value in config + * @author Redempt + */ +public class ConfigField { + + private Field field; + private String name; + + /** + * Constructs a ConfigField from a field + * @param field The Field + */ + public ConfigField(Field field) { + this.field = field; + ConfigName annotation = field.getAnnotation(ConfigName.class); + name = annotation == null ? field.getName() : annotation.value(); + } + + /** + * @return The wrapped Field + */ + public Field getField() { + return field; + } + + /** + * Attemps to set the value of the field for the target object to the value + * @param target The target object + * @param value The value + */ + public void set(Object target, Object value) { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Attemps to set the field in a static context to the given value + * @param value The value + */ + public void set(Object value) { + set(null, value); + } + + /** + * Attempts to get the field's value for a given object + * @param target The target object to get the value from + * @return The value + */ + public Object get(Object target) { + try { + return field.get(target); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Attemps to get the value of the field in a static context + * @return The value + */ + public Object get() { + return get(null); + } + + /** + * @return The name for the field that should be used to store config values + */ + public String getName() { + return name; + } + + /** + * Sets the name of this ConfigField + * @param name The name to set + */ + public void setName(String name) { + this.name = name; + } + + public int hashCode() { + return field.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof ConfigField)) { + return false; + } + ConfigField cf = (ConfigField) o; + return cf.field.equals(field); + } + +} diff --git a/src/redempt/redlib/config/ConfigManager.java b/src/redempt/redlib/config/ConfigManager.java index b6e8d4c..bb6fada 100644 --- a/src/redempt/redlib/config/ConfigManager.java +++ b/src/redempt/redlib/config/ConfigManager.java @@ -14,6 +14,7 @@ import redempt.redlib.config.conversion.TypeConverter; import redempt.redlib.config.data.ConfigurationSectionDataHolder; import redempt.redlib.config.data.DataHolder; +import redempt.redlib.config.instantiation.Instantiator; import java.io.File; import java.io.IOException; @@ -254,7 +255,7 @@ private TypeConverter createConverter(ConfigType type) { if (Map.class.isAssignableFrom(type.getType())) { return MapConverter.create(this, type); } - if (type.getType().isAnnotationPresent(ConfigMappable.class) || type.getType().getSuperclass().getName().equals("java.lang.Record")) { + if (type.getType().isAnnotationPresent(ConfigMappable.class) || Instantiator.isRecord(type.getType())) { return ObjectConverter.create(this, type); } return NativeConverter.create(); diff --git a/src/redempt/redlib/config/annotations/ConfigMappable.java b/src/redempt/redlib/config/annotations/ConfigMappable.java index 1af84be..84711a6 100644 --- a/src/redempt/redlib/config/annotations/ConfigMappable.java +++ b/src/redempt/redlib/config/annotations/ConfigMappable.java @@ -1,5 +1,6 @@ package redempt.redlib.config.annotations; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -11,5 +12,6 @@ */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@Documented public @interface ConfigMappable { } diff --git a/src/redempt/redlib/config/annotations/ConfigName.java b/src/redempt/redlib/config/annotations/ConfigName.java new file mode 100644 index 0000000..af42f4b --- /dev/null +++ b/src/redempt/redlib/config/annotations/ConfigName.java @@ -0,0 +1,16 @@ +package redempt.redlib.config.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConfigName { + + String value(); + +} diff --git a/src/redempt/redlib/config/annotations/ConfigPostInit.java b/src/redempt/redlib/config/annotations/ConfigPostInit.java index 5058b5d..28cc5b2 100644 --- a/src/redempt/redlib/config/annotations/ConfigPostInit.java +++ b/src/redempt/redlib/config/annotations/ConfigPostInit.java @@ -1,5 +1,6 @@ package redempt.redlib.config.annotations; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -11,5 +12,6 @@ */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@Documented public @interface ConfigPostInit { } diff --git a/src/redempt/redlib/config/conversion/ObjectConverter.java b/src/redempt/redlib/config/conversion/ObjectConverter.java index f0c16d8..d94182a 100644 --- a/src/redempt/redlib/config/conversion/ObjectConverter.java +++ b/src/redempt/redlib/config/conversion/ObjectConverter.java @@ -1,21 +1,15 @@ package redempt.redlib.config.conversion; -import org.bukkit.configuration.ConfigurationSection; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConfigManager; import redempt.redlib.config.ConfigType; -import redempt.redlib.config.annotations.ConfigPath; -import redempt.redlib.config.annotations.ConfigPostInit; import redempt.redlib.config.data.DataHolder; -import redempt.redlib.config.instantiation.InstantiationInfo; +import redempt.redlib.config.instantiation.FieldSummary; import redempt.redlib.config.instantiation.Instantiator; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * A converter which builds objects from configuration sections @@ -34,52 +28,18 @@ public static TypeConverter create(ConfigManager manager, ConfigType t if (type.getType().isInterface() || Modifier.isAbstract(type.getType().getModifiers())) { throw new IllegalStateException("Cannot automatically convert abstract classe or interface " + type.getType()); } - List fields = new ArrayList<>(); - Map> converters = new HashMap<>(); Instantiator instantiator = Instantiator.getInstantiator(type.getType()); - Field configPath = null; - StringConverter configPathConverter = null; - for (Field field : type.getType().getDeclaredFields()) { - int mod = field.getModifiers(); - if (Modifier.isTransient(mod) || Modifier.isStatic(mod)) { - continue; - } - field.setAccessible(true); - if (field.isAnnotationPresent(ConfigPath.class)) { - configPath = field; - configPathConverter = manager.getStringConverter(ConfigType.get(configPath)); - continue; - } - fields.add(field); - ConfigType fieldType = ConfigType.get(field); - converters.put(field, manager.getConverter(fieldType)); - } - Method postInit = null; - for (Method method : type.getType().getDeclaredMethods()) { - int mod = method.getModifiers(); - if (Modifier.isStatic(mod)) { - continue; - } - if (method.isAnnotationPresent(ConfigPostInit.class)) { - if (method.getParameterCount() != 0) { - throw new IllegalStateException("Post-init method must have no parameters: " + method); - } - method.setAccessible(true); - postInit = method; - break; - } - } - InstantiationInfo info = new InstantiationInfo(postInit, configPath, configPathConverter); + FieldSummary summary = FieldSummary.getFieldSummary(manager, type.getType(), false); return new TypeConverter() { @Override public T loadFrom(DataHolder section, String path, T currentValue) { DataHolder newSection = path == null ? section : section.getSubsection(path); List objs = new ArrayList<>(); - for (Field field : fields) { - Object value = converters.get(field).loadFrom(newSection, field.getName(), null); + for (ConfigField field : summary.getFields()) { + Object value = summary.getConverters().get(field).loadFrom(newSection, field.getName(), null); objs.add(value); } - return (T) instantiator.instantiate(manager, currentValue, type.getType(), fields, objs, path, info); + return (T) instantiator.instantiate(manager, currentValue, type.getType(), objs, path, summary); } @Override @@ -93,12 +53,8 @@ public void saveTo(T t, DataHolder section, String path, boolean overwrite) { return; } DataHolder newSection = path == null ? section : section.createSubsection(path); - try { - for (Field field : fields) { - saveWith(converters.get(field), field.get(t), newSection, field.getName(), overwrite); - } - } catch (IllegalAccessException e) { - e.printStackTrace(); + for (ConfigField field : summary.getFields()) { + saveWith(summary.getConverters().get(field), field.get(t), newSection, field.getName(), overwrite); } } }; diff --git a/src/redempt/redlib/config/conversion/StaticRootConverter.java b/src/redempt/redlib/config/conversion/StaticRootConverter.java index 1633128..b9b562e 100644 --- a/src/redempt/redlib/config/conversion/StaticRootConverter.java +++ b/src/redempt/redlib/config/conversion/StaticRootConverter.java @@ -1,9 +1,11 @@ package redempt.redlib.config.conversion; import org.bukkit.configuration.ConfigurationSection; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConfigManager; import redempt.redlib.config.ConfigType; import redempt.redlib.config.data.DataHolder; +import redempt.redlib.config.instantiation.FieldSummary; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -26,27 +28,13 @@ public class StaticRootConverter { * @return A static root converter */ public static TypeConverter create(ConfigManager manager, Class root) { - List fields = new ArrayList<>(); - Map> converters = new HashMap<>(); - for (Field field : root.getDeclaredFields()) { - int mod = field.getModifiers(); - if (Modifier.isTransient(mod) || !Modifier.isStatic(mod)) { - continue; - } - field.setAccessible(true); - fields.add(field); - converters.put(field, manager.getConverter(ConfigType.get(field))); - } + FieldSummary summary = FieldSummary.getFieldSummary(manager, root, true); return new TypeConverter() { @Override public T loadFrom(DataHolder section, String path, T currentValue) { - try { - for (Field field : fields) { - Object val = converters.get(field).loadFrom(section, field.getName(), null); - field.set(null, val); - } - } catch (IllegalAccessException e) { - e.printStackTrace(); + for (ConfigField field : summary.getFields()) { + Object val = summary.getConverters().get(field).loadFrom(section, field.getName(), null); + field.set(val); } return null; } @@ -58,13 +46,9 @@ public void saveTo(T t, DataHolder section, String path) { @Override public void saveTo(T t, DataHolder section, String path, boolean overwrite) { - try { - for (Field field : fields) { - Object obj = field.get(null); - saveWith(converters.get(field), obj, section, field.getName(), overwrite); - } - } catch (IllegalAccessException e) { - e.printStackTrace(); + for (ConfigField field : summary.getFields()) { + Object obj = field.get(); + saveWith(summary.getConverters().get(field), obj, section, field.getName(), overwrite); } } }; diff --git a/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java b/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java index 6f1839d..afaf598 100644 --- a/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java +++ b/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java @@ -1,5 +1,6 @@ package redempt.redlib.config.instantiation; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConfigManager; import redempt.redlib.config.annotations.ConfigPath; @@ -47,7 +48,6 @@ private ConstructorInstantiator(Constructor constructor) { * @param manager The ConfigManager handling config data * @param target The target object, always ignored by this type of Instantiator * @param clazz The class whose fields are being used - * @param fields The fields being worked with * @param values The values for the fields * @param path The path in config * @param info Extra info about the instantiation @@ -55,7 +55,7 @@ private ConstructorInstantiator(Constructor constructor) { * @return The constructed object */ @Override - public T instantiate(ConfigManager manager, Object target, Class clazz, List fields, List values, String path, InstantiationInfo info) { + public T instantiate(ConfigManager manager, Object target, Class clazz, List values, String path, FieldSummary info) { Object[] objs = new Object[params.length]; int valuePos = 0; for (int i = 0; i < params.length; i++) { diff --git a/src/redempt/redlib/config/instantiation/EmptyInstantiator.java b/src/redempt/redlib/config/instantiation/EmptyInstantiator.java index 18bc965..6521aa4 100644 --- a/src/redempt/redlib/config/instantiation/EmptyInstantiator.java +++ b/src/redempt/redlib/config/instantiation/EmptyInstantiator.java @@ -1,9 +1,9 @@ package redempt.redlib.config.instantiation; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConfigManager; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -37,7 +37,6 @@ public static T instantiate(Class clazz) { * @param manager The ConfigManager handling the config data * @param target The object to load to, or null if creating a new one * @param clazz The class whose fields are being worked with - * @param fields The fields being worked with * @param values The values for the fields * @param path The path in config * @param info Extra info used for post-instantiation steps @@ -45,14 +44,14 @@ public static T instantiate(Class clazz) { * @return The instantiated object, or the input object with its fields modified */ @Override - public T instantiate(ConfigManager manager, Object target, Class clazz, List fields, List values, String path, InstantiationInfo info) { + public T instantiate(ConfigManager manager, Object target, Class clazz, List values, String path, FieldSummary info) { try { T t = target == null ? instantiate(clazz) : (T) target; - for (int i = 0; i < fields.size(); i++) { + for (int i = 0; i < info.getFields().size(); i++) { if (values.get(i) == null) { continue; } - fields.get(i).set(t, values.get(i)); + info.getFields().get(i).set(t, values.get(i)); } if (info.getConfigPath() != null) { Object pathValue = info.getConfigPathConverter().fromString(path); diff --git a/src/redempt/redlib/config/instantiation/FieldSummary.java b/src/redempt/redlib/config/instantiation/FieldSummary.java new file mode 100644 index 0000000..3809699 --- /dev/null +++ b/src/redempt/redlib/config/instantiation/FieldSummary.java @@ -0,0 +1,155 @@ +package redempt.redlib.config.instantiation; + +import redempt.redlib.config.ConfigField; +import redempt.redlib.config.ConfigManager; +import redempt.redlib.config.ConfigType; +import redempt.redlib.config.annotations.ConfigName; +import redempt.redlib.config.annotations.ConfigPath; +import redempt.redlib.config.annotations.ConfigPostInit; +import redempt.redlib.config.conversion.StringConverter; +import redempt.redlib.config.conversion.TypeConverter; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a summary of the relevant fields, converters, and other info required to load objects from config + * @author Redempt + */ +public class FieldSummary { + + /** + * Generates a FieldSummary of a class + * @param manager The ConfigManager with access to converters + * @param clazz The class being summarized + * @param staticContext Whether static fields should be retrieved instead of member fields + * @return A field summary + */ + public static FieldSummary getFieldSummary(ConfigManager manager, Class clazz, boolean staticContext) { + try { + Field configPath = null; + StringConverter configPathConverter = null; + List fields = new ArrayList<>(); + Map> converters = new HashMap<>(); + Method postInit = null; + for (Field field : clazz.getDeclaredFields()) { + int mod = field.getModifiers(); + if (Modifier.isTransient(mod) || Modifier.isStatic(mod) != staticContext) { + continue; + } + field.setAccessible(true); + if (!staticContext && field.isAnnotationPresent(ConfigPath.class)) { + configPath = field; + configPathConverter = manager.getStringConverter(ConfigType.get(configPath)); + continue; + } + ConfigField cf = new ConfigField(field); + fields.add(cf); + converters.put(cf, manager.getConverter(ConfigType.get(field))); + } + + if (!staticContext && Instantiator.isRecord(clazz)) { + Constructor constructor = clazz.getDeclaredConstructor(Arrays.stream(clazz.getDeclaredFields()).map(Field::getType).toArray(Class[]::new)); + Parameter[] params = constructor.getParameters(); + int pos = 0; + for (int i = 0; i < params.length; i++) { + Parameter param = params[i]; + if (param.isAnnotationPresent(ConfigPath.class)) { + continue; + } + ConfigName name = param.getAnnotation(ConfigName.class); + if (name == null) { + continue; + } + fields.get(pos).setName(name.value()); + pos++; + } + } + + if (!staticContext) { + postInit = getPostInitMethod(clazz); + } + return new FieldSummary(fields, converters, configPath, configPathConverter, postInit); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + private static Method getPostInitMethod(Class clazz) { + for (Method method : clazz.getDeclaredMethods()) { + int mod = method.getModifiers(); + if (!method.isAnnotationPresent(ConfigPostInit.class)) { + continue; + } + if (Modifier.isStatic(mod) || Modifier.isAbstract(mod)) { + throw new IllegalStateException("Post-init method may not be static or abstract: " + method.getName()); + } + if (method.getParameterCount() != 0) { + throw new IllegalStateException("Post-init method must have no arguments: " + method.getName()); + } + method.setAccessible(true); + return method; + } + return null; + } + + private List fields; + private Map> converters; + private Field configPath; + private StringConverter configPathConverter; + private Method postInit; + + private FieldSummary(List fields, Map> converters, Field configPath, + StringConverter configPathConverter, Method postInit) { + this.fields = fields; + this.converters = converters; + this.configPath = configPath; + this.configPathConverter = configPathConverter; + this.postInit = postInit; + } + + /** + * @return The ConfigFields that should be loaded to + */ + public List getFields() { + return fields; + } + + /** + * @return The converters for all the field types + */ + public Map> getConverters() { + return converters; + } + + /** + * @return The ConfigPath field, if one exists + */ + public Field getConfigPath() { + return configPath; + } + + /** + * @return The converter for the ConfigPath field, if it exists + */ + public StringConverter getConfigPathConverter() { + return configPathConverter; + } + + /** + * @return The post-init method, if it exists + */ + public Method getPostInit() { + return postInit; + } + +} diff --git a/src/redempt/redlib/config/instantiation/InstantiationInfo.java b/src/redempt/redlib/config/instantiation/InstantiationInfo.java deleted file mode 100644 index 2c69021..0000000 --- a/src/redempt/redlib/config/instantiation/InstantiationInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -package redempt.redlib.config.instantiation; - -import redempt.redlib.config.conversion.StringConverter; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * A class which holds info used for instantiation from config - * @author Redempt - */ -public class InstantiationInfo { - - private Method postInit; - private Field configPath; - private StringConverter configPathConverter; - - /** - * Creates an InstantiationInfo object - * @param postInit The post-init method to invoke, only used by {@link EmptyInstantiator} - * @param configPath The ConfigPath field, if one exists - * @param configPathConverter The converter for the ConfigPath field, if it exists - */ - public InstantiationInfo(Method postInit, Field configPath, StringConverter configPathConverter) { - this.postInit = postInit; - this.configPath = configPath; - this.configPathConverter = configPathConverter; - } - - /** - * @return The post-init method, or null - */ - public Method getPostInit() { - return postInit; - } - - /** - * - * @return The config path field, or null - */ - public Field getConfigPath() { - return configPath; - } - - /** - * @return The config path type converter, or null - */ - public StringConverter getConfigPathConverter() { - return configPathConverter; - } - -} diff --git a/src/redempt/redlib/config/instantiation/Instantiator.java b/src/redempt/redlib/config/instantiation/Instantiator.java index 42a2779..1ebfe88 100644 --- a/src/redempt/redlib/config/instantiation/Instantiator.java +++ b/src/redempt/redlib/config/instantiation/Instantiator.java @@ -1,5 +1,6 @@ package redempt.redlib.config.instantiation; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConfigManager; import redempt.redlib.config.annotations.ConfigMappable; @@ -12,6 +13,10 @@ */ public interface Instantiator { + public static boolean isRecord(Class clazz) { + return clazz.getSuperclass() != null && clazz.getSuperclass().getName().equals("java.lang.Record"); + } + /** * Attemps to get the appropriate Instantiator for the given class type * @param clazz The class type @@ -19,7 +24,7 @@ public interface Instantiator { * @throws IllegalArgumentException If the class cannot be instantiated by known methods */ public static Instantiator getInstantiator(Class clazz) { - if (clazz.getSuperclass().getName().equals("java.lang.Record")) { + if (isRecord(clazz)) { return ConstructorInstantiator.create(clazz); } if (clazz.isAnnotationPresent(ConfigMappable.class)) { @@ -33,13 +38,12 @@ public static Instantiator getInstantiator(Class clazz) { * @param manager The ConfigManager handling config data * @param target The target object, or null * @param clazz The class whose fields are being used - * @param fields The fields being worked with * @param values The values for the fields * @param path The path in config * @param info Extra info about the instantiation * @param The type * @return An instantiated object, or the input object with its fields modified */ - public T instantiate(ConfigManager manager, Object target, Class clazz, List fields, List values, String path, InstantiationInfo info); + public T instantiate(ConfigManager manager, Object target, Class clazz, List values, String path, FieldSummary info); }