From c926d979ddd93f1a6399bf7afac2d10eeb0521b8 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Fri, 7 May 2021 00:48:56 +0200 Subject: [PATCH] Added new value provider interface for Configurator --- src/zutil/ui/ConfigEnumValueProvider.java | 30 +++++ src/zutil/ui/Configurator.java | 149 +++++++++++++++------- src/zutil/ui/configurator.dynamic.tmpl | 6 +- src/zutil/ui/configurator.form.tmpl | 6 +- 4 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 src/zutil/ui/ConfigEnumValueProvider.java diff --git a/src/zutil/ui/ConfigEnumValueProvider.java b/src/zutil/ui/ConfigEnumValueProvider.java new file mode 100644 index 0000000..c748aeb --- /dev/null +++ b/src/zutil/ui/ConfigEnumValueProvider.java @@ -0,0 +1,30 @@ +package zutil.ui; + +/** + * A value provider that will give all Enum values + */ +public class ConfigEnumValueProvider implements Configurator.ConfigValueProvider { + private Class enumCLass; + + + public ConfigEnumValueProvider(Class enumCLass) { + this.enumCLass = enumCLass; + } + + + @Override + public String[] getPossibleValues() { + Object[] constants = enumCLass.getEnumConstants(); + String[] values = new String[constants.length]; + + for (int i = 0; i < constants.length; ++i) + values[i] = ((Enum) constants[i]).name(); + + return values; + } + + @Override + public Enum getValueObject(String value) { + return Enum.valueOf(enumCLass, value); + } +} \ No newline at end of file diff --git a/src/zutil/ui/Configurator.java b/src/zutil/ui/Configurator.java index 2be83b7..06bad08 100755 --- a/src/zutil/ui/Configurator.java +++ b/src/zutil/ui/Configurator.java @@ -31,6 +31,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; @@ -50,7 +51,7 @@ import java.util.logging.Logger; *

* External listener can be registered to be called before or after configuration changes * by implementing {@link PreConfigurationActionListener} or {@link PostConfigurationActionListener}. - * The configured object will autmatically be registered as a listener if it also implements + * The configured object will automatically be registered as a listener if it also implements * these interfaces. * *

@@ -61,25 +62,64 @@ import java.util.logging.Logger; public class Configurator { private static final Logger logger = LogUtil.getLogger(); + // ---------------------------------------------------- + // Public interfaces + // ---------------------------------------------------- + /** * Sets a field in a class as externally configurable. */ @Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection. @Target({ElementType.FIELD}) // This annotation can only be applied to class fields. - public @interface Configurable{ + public @interface Configurable { /** Nice name of this parameter **/ String value(); /** A longer human friendly description of the parameter **/ String description(); - /** Defines the order the parameters, in ascending order **/ + /** Defines the order the parameter, parameter with lowest order value will be shown first **/ int order() default Integer.MAX_VALUE; + /** Provide a custom set of values through a value provider that will be the choice the user will have. **/ + Class valueProvider(); + } + + /** + * Interface for providing a specific selection of values that a user is allowed to pick from for assignment to a object field. + * The class implementing this interface is required to have one of the below constructors for instantiation: + *

+     *     ** no constructors **
+     *     public XXX() {}
+     *     public XXX(Class fieldType) {}
+     *     public XXX(Class fieldType, Object fieldValue) {}
+     * 
+ * + * @param represents the value type that will be assigned to the target field. + */ + public interface ConfigValueProvider { + /** + * @return a array of all possible values that the user can select from. + */ + String[] getPossibleValues(); + + /** + * Convert the user selected value into the actual Object that should be assigned to the target field. + * + * @param value the string value that was selected by the user. + * @return a Object that will be assigned to the target field, note a exception will be thrown if the return type does not match the field. + */ + V getValueObject(String value); + } + + /** + * Defines supported configurable types + */ + public enum ConfigType { + STRING, NUMBER, BOOLEAN, SELECTION } - public enum ConfigType{ - STRING, INT, BOOLEAN, ENUM - } - + // ---------------------------------------------------- + // Configurator data and logic + // ---------------------------------------------------- private static HashMap classConf = new HashMap<>(); @@ -116,7 +156,7 @@ public class Configurator { if (f.isAnnotationPresent(Configurable.class)) { try { conf.add(new ConfigurationParam(f, obj)); - } catch (IllegalAccessException e) { + } catch (ReflectiveOperationException e) { logger.log(Level.SEVERE, null, e); } } @@ -233,49 +273,68 @@ public class Configurator { public static class ConfigurationParam implements Comparable{ - protected Field field; - protected String name; protected String niceName; protected String description; - protected ConfigType type; - protected Object value; protected int order; + protected Field field; + protected String name; + protected ConfigType type; + protected Object value; + protected ConfigValueProvider valueProvider; - protected ConfigurationParam(Field f, Object obj) throws IllegalAccessException { + + protected ConfigurationParam(Field f, Object obj) throws ReflectiveOperationException { field = f; field.setAccessible(true); name = field.getName(); + if (obj != null) value = field.get(obj); + if (field.isAnnotationPresent(Configurable.class)) { niceName = field.getAnnotation(Configurable.class).value(); description = field.getAnnotation(Configurable.class).description(); order = field.getAnnotation(Configurable.class).order(); - } - else { + valueProvider = getValueProviderInstance(field.getAnnotation(Configurable.class).valueProvider()); + } else { niceName = name; order = Integer.MAX_VALUE; } - if (f.getType() == String.class) type = ConfigType.STRING; - else if (f.getType() == int.class) type = ConfigType.INT; - else if (f.getType() == double.class) type = ConfigType.INT; - else if (f.getType() == boolean.class) type = ConfigType.BOOLEAN; - else if (f.getType().isEnum()) type = ConfigType.ENUM; - else - throw new IllegalArgumentException(f.getType() + " is not a supported configurable type"); + if (valueProvider != null) type = ConfigType.SELECTION; + else if (f.getType() == String.class) type = ConfigType.STRING; + else if (f.getType() == int.class) type = ConfigType.NUMBER; + else if (f.getType() == double.class) type = ConfigType.NUMBER; + else if (f.getType() == boolean.class) type = ConfigType.BOOLEAN; + else if (f.getType().isEnum()) { + type = ConfigType.SELECTION; + valueProvider = new ConfigEnumValueProvider((Class) f.getType()); + } else + throw new IllegalArgumentException(f.getType() + " is not a supported native configurable type, a value provided is required for arbitrary objects."); } - public String getName() { return name;} - public String getNiceName() { return niceName;} - public String getDescription() { return description;} - public ConfigType getType() { return type;} - public boolean isTypeString() { return type == ConfigType.STRING;} - public boolean isTypeInt() { return type == ConfigType.INT;} - public boolean isTypeBoolean() { return type == ConfigType.BOOLEAN;} - public boolean isTypeEnum() { return type == ConfigType.ENUM;} + private ConfigValueProvider getValueProviderInstance(Class valueProviderClass) throws ReflectiveOperationException { + for (Constructor constructor : valueProviderClass.getConstructors()) { + switch (constructor.getParameterCount()) { + case 0: return valueProviderClass.getDeclaredConstructor().newInstance(); + case 1: return valueProviderClass.getDeclaredConstructor(Class.class).newInstance(field.getType()); + case 2: return valueProviderClass.getDeclaredConstructor(Class.class, Object.class).newInstance(field.getType(), value); + } + } + + throw new NoSuchMethodException("Unable to find proper constructor inside " + valueProviderClass.getSimpleName()); + } + + public String getName() { return name; } + public String getNiceName() { return niceName; } + public String getDescription() { return description; } + public ConfigType getType() { return type; } + public boolean isTypeString() { return type == ConfigType.STRING; } + public boolean isTypeNumber() { return type == ConfigType.NUMBER; } + public boolean isTypeBoolean() { return type == ConfigType.BOOLEAN; } + public boolean isTypeSelection() { return type == ConfigType.SELECTION; } public String getString() { if (value == null) @@ -285,19 +344,15 @@ public class Configurator { public boolean getBoolean() { if (value == null || type != ConfigType.BOOLEAN) return false; - return (boolean)value; + return (boolean) value; } /** - * @return a String array with all enum possibilities or empty array if the type is not an enum + * @return a String array with all possible values that can be assigned or an empty array if any value within the type definition can be set. */ public String[] getPossibleValues() { - if (type == ConfigType.ENUM) { - Object[] constants = field.getType().getEnumConstants(); - String[] values = new String[constants.length]; - for (int i = 0; i < constants.length; ++i) - values[i] = ((Enum)constants[i]).name(); - return values; + if (valueProvider != null) { + return valueProvider.getPossibleValues(); } return new String[0]; } @@ -306,21 +361,23 @@ public class Configurator { * This method will set a value for the represented field, * to apply the change to the source object the method * {@link #applyConfiguration()} needs to be called + * + * @param selectedValue the value that was selected by user. */ - public void setValue(String v) { + public void setValue(String selectedValue) { switch(type) { case STRING: - value = v; break; - case INT: + value = selectedValue; break; + case NUMBER: if (field.getType() == double.class) - value = Double.parseDouble(v); + value = Double.parseDouble(selectedValue); else - value = Integer.parseInt(v); + value = Integer.parseInt(selectedValue); break; case BOOLEAN: - value = Boolean.parseBoolean(v); break; - case ENUM: - value = Enum.valueOf((Class)field.getType(), v); break; + value = Boolean.parseBoolean(selectedValue); break; + case SELECTION: + value = valueProvider.getValueObject(selectedValue); break; } } diff --git a/src/zutil/ui/configurator.dynamic.tmpl b/src/zutil/ui/configurator.dynamic.tmpl index e82ac76..2abd0b8 100755 --- a/src/zutil/ui/configurator.dynamic.tmpl +++ b/src/zutil/ui/configurator.dynamic.tmpl @@ -95,13 +95,13 @@ {{#.isTypeString()}}{{/#.isTypeString()}} - {{#.isTypeInt()}}{{/#.isTypeInt()}} + {{#.isTypeNumber()}}{{/#.isTypeNumber()}} {{#.isTypeBoolean()}}{{/#.isTypeBoolean()}} - {{#.isTypeEnum()}} + {{#.isTypeSelection()}} - {{/#.isTypeEnum()}} + {{/#.isTypeSelection()}} {{#.getDescription()}}
{{.getDescription()}}
diff --git a/src/zutil/ui/configurator.form.tmpl b/src/zutil/ui/configurator.form.tmpl index d97396a..7f9b75c 100755 --- a/src/zutil/ui/configurator.form.tmpl +++ b/src/zutil/ui/configurator.form.tmpl @@ -5,13 +5,13 @@ {{#.isTypeString()}}{{/#.isTypeString()}} - {{#.isTypeInt()}}{{/#.isTypeInt()}} + {{#.isTypeNumber()}}{{/#.isTypeNumber()}} {{#.isTypeBoolean()}}{{/#.isTypeBoolean()}} - {{#.isTypeEnum()}} + {{#.isTypeSelection()}} - {{/#.isTypeEnum()}} + {{/#.isTypeSelection()}} {{#.getDescription()}}
{{.getDescription()}}