Added new value provider interface for Configurator
This commit is contained in:
parent
372c02f5fe
commit
c926d979dd
4 changed files with 139 additions and 52 deletions
30
src/zutil/ui/ConfigEnumValueProvider.java
Normal file
30
src/zutil/ui/ConfigEnumValueProvider.java
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package zutil.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A value provider that will give all Enum values
|
||||||
|
*/
|
||||||
|
public class ConfigEnumValueProvider implements Configurator.ConfigValueProvider<Enum> {
|
||||||
|
private Class<Enum> enumCLass;
|
||||||
|
|
||||||
|
|
||||||
|
public ConfigEnumValueProvider(Class<Enum> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -50,7 +51,7 @@ import java.util.logging.Logger;
|
||||||
* <p>
|
* <p>
|
||||||
* External listener can be registered to be called before or after configuration changes
|
* External listener can be registered to be called before or after configuration changes
|
||||||
* by implementing {@link PreConfigurationActionListener} or {@link PostConfigurationActionListener}.
|
* 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.
|
* these interfaces.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -61,25 +62,64 @@ import java.util.logging.Logger;
|
||||||
public class Configurator<T> {
|
public class Configurator<T> {
|
||||||
private static final Logger logger = LogUtil.getLogger();
|
private static final Logger logger = LogUtil.getLogger();
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// Public interfaces
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a field in a class as externally configurable.
|
* Sets a field in a class as externally configurable.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection.
|
@Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection.
|
||||||
@Target({ElementType.FIELD}) // This annotation can only be applied to class fields.
|
@Target({ElementType.FIELD}) // This annotation can only be applied to class fields.
|
||||||
public @interface Configurable{
|
public @interface Configurable {
|
||||||
/** Nice name of this parameter **/
|
/** Nice name of this parameter **/
|
||||||
String value();
|
String value();
|
||||||
/** A longer human friendly description of the parameter **/
|
/** A longer human friendly description of the parameter **/
|
||||||
String description();
|
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;
|
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<? extends ConfigValueProvider> 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:
|
||||||
|
* <pre>
|
||||||
|
* ** no constructors **
|
||||||
|
* public XXX() {}
|
||||||
|
* public XXX(Class fieldType) {}
|
||||||
|
* public XXX(Class fieldType, Object fieldValue) {}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param <V> represents the value type that will be assigned to the target field.
|
||||||
|
*/
|
||||||
|
public interface ConfigValueProvider<V> {
|
||||||
|
/**
|
||||||
|
* @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<Class, ConfigurationParam[]> classConf = new HashMap<>();
|
private static HashMap<Class, ConfigurationParam[]> classConf = new HashMap<>();
|
||||||
|
|
||||||
|
|
@ -116,7 +156,7 @@ public class Configurator<T> {
|
||||||
if (f.isAnnotationPresent(Configurable.class)) {
|
if (f.isAnnotationPresent(Configurable.class)) {
|
||||||
try {
|
try {
|
||||||
conf.add(new ConfigurationParam(f, obj));
|
conf.add(new ConfigurationParam(f, obj));
|
||||||
} catch (IllegalAccessException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
logger.log(Level.SEVERE, null, e);
|
logger.log(Level.SEVERE, null, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,49 +273,68 @@ public class Configurator<T> {
|
||||||
|
|
||||||
|
|
||||||
public static class ConfigurationParam implements Comparable<ConfigurationParam>{
|
public static class ConfigurationParam implements Comparable<ConfigurationParam>{
|
||||||
protected Field field;
|
|
||||||
protected String name;
|
|
||||||
protected String niceName;
|
protected String niceName;
|
||||||
protected String description;
|
protected String description;
|
||||||
protected ConfigType type;
|
|
||||||
protected Object value;
|
|
||||||
protected int order;
|
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 = f;
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
name = field.getName();
|
name = field.getName();
|
||||||
|
|
||||||
if (obj != null)
|
if (obj != null)
|
||||||
value = field.get(obj);
|
value = field.get(obj);
|
||||||
|
|
||||||
if (field.isAnnotationPresent(Configurable.class)) {
|
if (field.isAnnotationPresent(Configurable.class)) {
|
||||||
niceName = field.getAnnotation(Configurable.class).value();
|
niceName = field.getAnnotation(Configurable.class).value();
|
||||||
description = field.getAnnotation(Configurable.class).description();
|
description = field.getAnnotation(Configurable.class).description();
|
||||||
order = field.getAnnotation(Configurable.class).order();
|
order = field.getAnnotation(Configurable.class).order();
|
||||||
}
|
valueProvider = getValueProviderInstance(field.getAnnotation(Configurable.class).valueProvider());
|
||||||
else {
|
} else {
|
||||||
niceName = name;
|
niceName = name;
|
||||||
order = Integer.MAX_VALUE;
|
order = Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.getType() == String.class) type = ConfigType.STRING;
|
if (valueProvider != null) type = ConfigType.SELECTION;
|
||||||
else if (f.getType() == int.class) type = ConfigType.INT;
|
else if (f.getType() == String.class) type = ConfigType.STRING;
|
||||||
else if (f.getType() == double.class) type = ConfigType.INT;
|
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() == boolean.class) type = ConfigType.BOOLEAN;
|
||||||
else if (f.getType().isEnum()) type = ConfigType.ENUM;
|
else if (f.getType().isEnum()) {
|
||||||
else
|
type = ConfigType.SELECTION;
|
||||||
throw new IllegalArgumentException(f.getType() + " is not a supported configurable type");
|
valueProvider = new ConfigEnumValueProvider((Class<Enum>) 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;}
|
private ConfigValueProvider getValueProviderInstance(Class<? extends ConfigValueProvider> valueProviderClass) throws ReflectiveOperationException {
|
||||||
public String getNiceName() { return niceName;}
|
for (Constructor constructor : valueProviderClass.getConstructors()) {
|
||||||
public String getDescription() { return description;}
|
switch (constructor.getParameterCount()) {
|
||||||
public ConfigType getType() { return type;}
|
case 0: return valueProviderClass.getDeclaredConstructor().newInstance();
|
||||||
public boolean isTypeString() { return type == ConfigType.STRING;}
|
case 1: return valueProviderClass.getDeclaredConstructor(Class.class).newInstance(field.getType());
|
||||||
public boolean isTypeInt() { return type == ConfigType.INT;}
|
case 2: return valueProviderClass.getDeclaredConstructor(Class.class, Object.class).newInstance(field.getType(), value);
|
||||||
public boolean isTypeBoolean() { return type == ConfigType.BOOLEAN;}
|
}
|
||||||
public boolean isTypeEnum() { return type == ConfigType.ENUM;}
|
}
|
||||||
|
|
||||||
|
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() {
|
public String getString() {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
|
@ -285,19 +344,15 @@ public class Configurator<T> {
|
||||||
public boolean getBoolean() {
|
public boolean getBoolean() {
|
||||||
if (value == null || type != ConfigType.BOOLEAN)
|
if (value == null || type != ConfigType.BOOLEAN)
|
||||||
return false;
|
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() {
|
public String[] getPossibleValues() {
|
||||||
if (type == ConfigType.ENUM) {
|
if (valueProvider != null) {
|
||||||
Object[] constants = field.getType().getEnumConstants();
|
return valueProvider.getPossibleValues();
|
||||||
String[] values = new String[constants.length];
|
|
||||||
for (int i = 0; i < constants.length; ++i)
|
|
||||||
values[i] = ((Enum<?>)constants[i]).name();
|
|
||||||
return values;
|
|
||||||
}
|
}
|
||||||
return new String[0];
|
return new String[0];
|
||||||
}
|
}
|
||||||
|
|
@ -306,21 +361,23 @@ public class Configurator<T> {
|
||||||
* This method will set a value for the represented field,
|
* This method will set a value for the represented field,
|
||||||
* to apply the change to the source object the method
|
* to apply the change to the source object the method
|
||||||
* {@link #applyConfiguration()} needs to be called
|
* {@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) {
|
switch(type) {
|
||||||
case STRING:
|
case STRING:
|
||||||
value = v; break;
|
value = selectedValue; break;
|
||||||
case INT:
|
case NUMBER:
|
||||||
if (field.getType() == double.class)
|
if (field.getType() == double.class)
|
||||||
value = Double.parseDouble(v);
|
value = Double.parseDouble(selectedValue);
|
||||||
else
|
else
|
||||||
value = Integer.parseInt(v);
|
value = Integer.parseInt(selectedValue);
|
||||||
break;
|
break;
|
||||||
case BOOLEAN:
|
case BOOLEAN:
|
||||||
value = Boolean.parseBoolean(v); break;
|
value = Boolean.parseBoolean(selectedValue); break;
|
||||||
case ENUM:
|
case SELECTION:
|
||||||
value = Enum.valueOf((Class<? extends Enum>)field.getType(), v); break;
|
value = valueProvider.getValueObject(selectedValue); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,13 +95,13 @@
|
||||||
<label for="input-{{.getName()}}" class="form-label">{{.getNiceName()}}:</label>
|
<label for="input-{{.getName()}}" class="form-label">{{.getNiceName()}}:</label>
|
||||||
|
|
||||||
{{#.isTypeString()}}<input type="text" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeString()}}
|
{{#.isTypeString()}}<input type="text" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeString()}}
|
||||||
{{#.isTypeInt()}}<input type="number" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeInt()}}
|
{{#.isTypeNumber()}}<input type="number" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeNumber()}}
|
||||||
{{#.isTypeBoolean()}}<input type="checkbox" id="input-{{.getName()}}" name="{{.getName()}}" value="true">{{/#.isTypeBoolean()}}
|
{{#.isTypeBoolean()}}<input type="checkbox" id="input-{{.getName()}}" name="{{.getName()}}" value="true">{{/#.isTypeBoolean()}}
|
||||||
{{#.isTypeEnum()}}
|
{{#.isTypeSelection()}}
|
||||||
<select class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">
|
<select class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">
|
||||||
{{#.getPossibleValues()}}<option>{{.}}</option>{{/.getPossibleValues()}}
|
{{#.getPossibleValues()}}<option>{{.}}</option>{{/.getPossibleValues()}}
|
||||||
</select>
|
</select>
|
||||||
{{/#.isTypeEnum()}}
|
{{/#.isTypeSelection()}}
|
||||||
|
|
||||||
{{#.getDescription()}}
|
{{#.getDescription()}}
|
||||||
<div class="form-text">{{.getDescription()}}</div>
|
<div class="form-text">{{.getDescription()}}</div>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
<label for="input-{{.getName()}}" class="form-label">{{.getNiceName()}}:</label>
|
<label for="input-{{.getName()}}" class="form-label">{{.getNiceName()}}:</label>
|
||||||
|
|
||||||
{{#.isTypeString()}}<input type="text" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeString()}}
|
{{#.isTypeString()}}<input type="text" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeString()}}
|
||||||
{{#.isTypeInt()}}<input type="number" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeInt()}}
|
{{#.isTypeNumber()}}<input type="number" class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">{{/#.isTypeNumber()}}
|
||||||
{{#.isTypeBoolean()}}<input type="checkbox" id="input-{{.getName()}}" name="{{.getName()}}" value="true">{{/#.isTypeBoolean()}}
|
{{#.isTypeBoolean()}}<input type="checkbox" id="input-{{.getName()}}" name="{{.getName()}}" value="true">{{/#.isTypeBoolean()}}
|
||||||
{{#.isTypeEnum()}}
|
{{#.isTypeSelection()}}
|
||||||
<select class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">
|
<select class="form-control" id="input-{{.getName()}}" name="{{.getName()}}">
|
||||||
{{#.getPossibleValues()}}<option>{{.}}</option>{{/.getPossibleValues()}}
|
{{#.getPossibleValues()}}<option>{{.}}</option>{{/.getPossibleValues()}}
|
||||||
</select>
|
</select>
|
||||||
{{/#.isTypeEnum()}}
|
{{/#.isTypeSelection()}}
|
||||||
|
|
||||||
{{#.getDescription()}}
|
{{#.getDescription()}}
|
||||||
<div class="form-text">{{.getDescription()}}</div>
|
<div class="form-text">{{.getDescription()}}</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue