diff --git a/res/plugin.yml b/res/plugin.yml index 9c5e69b..18bd7bb 100755 --- a/res/plugin.yml +++ b/res/plugin.yml @@ -1,6 +1,6 @@ name: RedLib main: redempt.redlib.RedLib -version: 2023-12-22 19:59 +version: 2024-02-13 00:41 author: Redempt api-version: 1.13 load: STARTUP diff --git a/src/redempt/redlib/config/annotations/ConfigConstructor.java b/src/redempt/redlib/config/annotations/ConfigConstructor.java new file mode 100644 index 0000000..4f180ee --- /dev/null +++ b/src/redempt/redlib/config/annotations/ConfigConstructor.java @@ -0,0 +1,14 @@ +package redempt.redlib.config.annotations; + +import java.lang.annotation.*; + +/** + * Denotes a constructor which can be used to initialize the object from config. + * Parameter names in the constructor must match field names in the class. + */ +@Inherited +@Target(ElementType.CONSTRUCTOR) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConfigConstructor { +} diff --git a/src/redempt/redlib/config/conversion/ObjectConverter.java b/src/redempt/redlib/config/conversion/ObjectConverter.java index 82ccbf9..62c082a 100644 --- a/src/redempt/redlib/config/conversion/ObjectConverter.java +++ b/src/redempt/redlib/config/conversion/ObjectConverter.java @@ -30,13 +30,13 @@ public static TypeConverter create(ConversionManager manager, ConfigType< if (type.getType().isInterface() || Modifier.isAbstract(type.getType().getModifiers())) { throw new IllegalStateException("Cannot automatically convert abstract classe or interface " + type.getType()); } - Instantiator instantiator = Instantiator.getInstantiator(type.getType()); FieldSummary summary = FieldSummary.getFieldSummary(manager, type.getType(), false); + Instantiator instantiator = Instantiator.getInstantiator(type.getType()); 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<>(); + List objs = new ArrayList<>(summary.getFields().size()); for (ConfigField field : summary.getFields()) { Object value = summary.getConverters().get(field).loadFrom(newSection, field.getName(), null); objs.add(value); diff --git a/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java b/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java index 80c2d12..2e4dab3 100644 --- a/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java +++ b/src/redempt/redlib/config/instantiation/ConstructorInstantiator.java @@ -1,23 +1,22 @@ package redempt.redlib.config.instantiation; -import redempt.redlib.config.ConfigManager; +import redempt.redlib.config.ConfigField; import redempt.redlib.config.ConversionManager; import redempt.redlib.config.annotations.ConfigPath; -import redempt.redlib.config.data.DataHolder; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Parameter; -import java.util.Arrays; -import java.util.List; +import java.util.*; +import java.util.stream.IntStream; /** * An instantiator used for record types which passes in all necessary fields * @author Redempt */ public class ConstructorInstantiator implements Instantiator { - + /** * Attempts to create an Instantiator for a record type, or a class which has a constructor taking all its fields * in the same order they appear in the class @@ -25,14 +24,13 @@ public class ConstructorInstantiator implements Instantiator { * @param The type * @return An Instantiator */ - public static Instantiator create(Class clazz) { + public static Instantiator createDefault(Class clazz) { try { Field[] fields = clazz.getDeclaredFields(); Constructor constructor = clazz.getDeclaredConstructor(Arrays.stream(fields).map(Field::getType).toArray(Class[]::new)); return new ConstructorInstantiator(constructor); } catch (NoSuchMethodException e) { - e.printStackTrace(); - return null; + throw new IllegalStateException("Class '" + clazz.getName() + "' does not have a constructor that takes all of its fields in order"); } } @@ -43,6 +41,11 @@ private ConstructorInstantiator(Constructor constructor) { this.constructor = constructor; params = constructor.getParameters(); } + + private ConstructorInstantiator(Constructor constructor, int[] indices) { + this.constructor = constructor; + params = constructor.getParameters(); + } /** * Instantiates a new object using its constructor diff --git a/src/redempt/redlib/config/instantiation/Instantiator.java b/src/redempt/redlib/config/instantiation/Instantiator.java index 00476a0..04357ae 100644 --- a/src/redempt/redlib/config/instantiation/Instantiator.java +++ b/src/redempt/redlib/config/instantiation/Instantiator.java @@ -1,11 +1,13 @@ package redempt.redlib.config.instantiation; -import redempt.redlib.config.ConfigManager; import redempt.redlib.config.ConversionManager; +import redempt.redlib.config.annotations.ConfigConstructor; import redempt.redlib.config.annotations.ConfigMappable; -import redempt.redlib.config.data.DataHolder; +import java.lang.reflect.Constructor; +import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * A utility to instantiate objects from values loaded from config @@ -25,11 +27,12 @@ public static boolean isRecord(Class clazz) { */ public static Instantiator getInstantiator(Class clazz) { if (isRecord(clazz)) { - return ConstructorInstantiator.create(clazz); + return ConstructorInstantiator.createDefault(clazz); } if (clazz.isAnnotationPresent(ConfigMappable.class)) { - return new EmptyInstantiator(); - } + Optional> constructor = Arrays.stream(clazz.getConstructors()).filter(c -> c.isAnnotationPresent(ConfigConstructor.class)).findFirst(); + return constructor.map(value -> ConstructorInstantiator.createDefault(clazz)).orElseGet(EmptyInstantiator::new); + } throw new IllegalArgumentException("Cannot create instantiator for class which is not a record type and not annotated with ConfigMappable (" + clazz + ")"); }