mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
Config API updates
This commit is contained in:
parent
03d903f130
commit
b8a3c98aca
@ -2,21 +2,21 @@ package io.nosqlbench.nb.api.config.standard;
|
||||
|
||||
import io.nosqlbench.nb.api.errors.BasicError;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ConfigModel implements NBConfigModel {
|
||||
|
||||
private final LinkedHashMap<String, Param<?>> elements = new LinkedHashMap<>();
|
||||
private final Map<String, Param<?>> paramsByName = new LinkedHashMap<>();
|
||||
private final List<Param<?>> params = new ArrayList<>();
|
||||
private Param<?> lastAdded = null;
|
||||
private final Class<?> ofType;
|
||||
|
||||
private ConfigModel(Class<?> ofType, Param<?>... params) {
|
||||
this.ofType = ofType;
|
||||
for (Param<?> param : params) {
|
||||
this.elements.put(param.getName(), param);
|
||||
add(param);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,55 +24,27 @@ public class ConfigModel implements NBConfigModel {
|
||||
return new ConfigModel(ofType, params);
|
||||
}
|
||||
|
||||
public ConfigModel optional(String name, Class<?> clazz) {
|
||||
add(new Param<>(name, clazz, "", false, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel optional(String name, Class<?> clazz, String description) {
|
||||
add(new Param<>(name, clazz, description, false, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel required(String name, Class<?> clazz, String description) {
|
||||
add(new Param<>(name, clazz, description, true, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> ConfigModel add(Param<T> param) {
|
||||
this.elements.put(param.name, param);
|
||||
this.params.add(param);
|
||||
for (String name : param.names) {
|
||||
paramsByName.put(name, param);
|
||||
}
|
||||
lastAdded = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel required(String name, Class<?> clazz) {
|
||||
add(new Param<>(name, clazz, "", true, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel defaults(String name, Object defaultValue) {
|
||||
add(new Param<>(name, defaultValue.getClass(), "", true, defaultValue));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel defaults(String name, Object defaultValue, String description) {
|
||||
add(new Param<>(name, defaultValue.getClass(), description, true, defaultValue));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigModel describedAs(String descriptionOfLastElement) {
|
||||
lastAdded.setDescription(descriptionOfLastElement);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public NBConfigModel asReadOnly() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Param<?>> getElements() {
|
||||
return Collections.unmodifiableMap(elements);
|
||||
public Map<String, Param<?>> getNamedParams() {
|
||||
return Collections.unmodifiableMap(paramsByName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Param<?>> getParams() {
|
||||
return new ArrayList<>((this.params));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,59 +52,51 @@ public class ConfigModel implements NBConfigModel {
|
||||
return ofType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertValidConfig(Map<String, ?> config) {
|
||||
for (String configkey : config.keySet()) {
|
||||
Param<?> element = this.elements.get(configkey);
|
||||
if (element == null) {
|
||||
StringBuilder paramhelp = new StringBuilder(
|
||||
"Unknown config parameter in config model '" + configkey + "' " +
|
||||
"while configuring " + getOf().getSimpleName()
|
||||
+ ", possible parameter names are " + this.elements.keySet() + "."
|
||||
);
|
||||
|
||||
ConfigSuggestions.getForParam(this, configkey)
|
||||
.ifPresent(suggestion -> paramhelp.append(" ").append(suggestion));
|
||||
|
||||
throw new BasicError(paramhelp.toString());
|
||||
}
|
||||
Object value = config.get(configkey);
|
||||
Object testValue = convertValueTo(ofType.getSimpleName(), configkey, value, element.getType());
|
||||
}
|
||||
for (Param<?> element : elements.values()) {
|
||||
if (element.isRequired() && element.getDefaultValue() == null) {
|
||||
if (!config.containsKey(element.getName())) {
|
||||
throw new RuntimeException("A required config element named '" + element.getName() +
|
||||
"' and type '" + element.getType().getSimpleName() + "' was not found\n" +
|
||||
"for configuring a " + getOf().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertValueTo(String configName, String paramName, Object value, Class<?> type) {
|
||||
public static <T> T convertValueTo(String configName, String paramName, Object value, Class<T> type) {
|
||||
try {
|
||||
if (type.isAssignableFrom(value.getClass())) {
|
||||
return type.cast(value);
|
||||
} else if (Number.class.isAssignableFrom(type)
|
||||
&& Number.class.isAssignableFrom(value.getClass())) {
|
||||
|
||||
} else if (Number.class.isAssignableFrom(value.getClass())) { // A numeric value, and do we have a compatible target type?
|
||||
Number number = (Number) value;
|
||||
if (type.equals(Float.class)) {
|
||||
return number.floatValue();
|
||||
} else if (type.equals(Integer.class)) {
|
||||
return number.intValue();
|
||||
} else if (type.equals(Double.class)) {
|
||||
return number.doubleValue();
|
||||
} else if (type.equals(Long.class)) {
|
||||
return number.longValue();
|
||||
} else if (type.equals(Byte.class)) {
|
||||
return number.byteValue();
|
||||
} else if (type.equals(Short.class)) {
|
||||
return number.shortValue();
|
||||
// This series of double fake-outs is heinous, but it works to get around design
|
||||
// holes in Java generics while preserving some type inference for the caller.
|
||||
// If you are reading this code and you can find a better way, please change it!
|
||||
if (type.equals(Float.class) || type == float.class) {
|
||||
return (T) (Float) number.floatValue();
|
||||
} else if (type.equals(Integer.class) || type == int.class) {
|
||||
return (T) (Integer) number.intValue();
|
||||
} else if (type.equals(Double.class) || type == double.class) {
|
||||
return (T) (Double) number.doubleValue();
|
||||
} else if (type.equals(Long.class) || type == long.class) {
|
||||
return (T) (Long) number.longValue();
|
||||
} else if (type.equals(Byte.class) || type == byte.class) {
|
||||
return (T) (Byte) number.byteValue();
|
||||
} else if (type.equals(Short.class) || type == short.class) {
|
||||
return (T) (Short) number.shortValue();
|
||||
} else {
|
||||
throw new RuntimeException("Number type " + type.getSimpleName() + " could " +
|
||||
" not be converted from " + value.getClass().getSimpleName());
|
||||
}
|
||||
} else if (value instanceof CharSequence) { // A stringy type, and do we have a compatible target type?
|
||||
String string = ((CharSequence) value).toString();
|
||||
|
||||
if (type == int.class || type == Integer.class) {
|
||||
return (T) Integer.valueOf(string);
|
||||
} else if (type == char.class || type == Character.class && string.length() == 1) {
|
||||
return (T) (Character) string.charAt(0);
|
||||
} else if (type == long.class || type == Long.class) {
|
||||
return (T) Long.valueOf(string);
|
||||
} else if (type == float.class || type == Float.class) {
|
||||
return (T) Float.valueOf(string);
|
||||
} else if (type == double.class || type == Double.class) {
|
||||
return (T) Double.valueOf(string);
|
||||
} else if (type == BigDecimal.class) {
|
||||
return (T) BigDecimal.valueOf(Double.parseDouble(string));
|
||||
} else {
|
||||
throw new RuntimeException("CharSequence type " + type.getSimpleName() + " could " +
|
||||
" not be converted from " + value.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@ -146,30 +110,129 @@ public class ConfigModel implements NBConfigModel {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBConfiguration extract(Map<String, ?> sharedConfig) {
|
||||
LinkedHashMap<String, Object> extracted = new LinkedHashMap<>();
|
||||
for (String providedCfgField : sharedConfig.keySet()) {
|
||||
if (getNamedParams().containsKey(providedCfgField)) {
|
||||
extracted.put(providedCfgField, sharedConfig.remove(providedCfgField));
|
||||
}
|
||||
}
|
||||
return new NBConfiguration(this, extracted);
|
||||
}
|
||||
|
||||
private void assertDistinctSynonyms(Map<String, ?> config) {
|
||||
List<String> names = new ArrayList<>();
|
||||
for (Param<?> param : getParams()) {
|
||||
names.clear();
|
||||
for (String s : param.getNames()) {
|
||||
if (config.containsKey(s)) {
|
||||
names.add(s);
|
||||
}
|
||||
}
|
||||
if (names.size() > 1) {
|
||||
throw new NBConfigError("Multiple names for the same parameter were provided: " + names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBConfiguration apply(Map<String, ?> config) {
|
||||
assertValidConfig(config);
|
||||
LinkedHashMap<String, Object> validConfig = new LinkedHashMap<>();
|
||||
|
||||
elements.forEach((k, v) -> {
|
||||
String name = v.getName();
|
||||
Class<?> type = v.getType();
|
||||
Object cval = config.get(name);
|
||||
if (cval == null && v.isRequired()) {
|
||||
cval = v.getDefaultValue();
|
||||
for (Param<?> param : params) {
|
||||
Class<?> type = param.getType();
|
||||
List<String> found = new ArrayList<>();
|
||||
String activename = null;
|
||||
Object cval = null;
|
||||
for (String name : param.names) {
|
||||
if (config.containsKey(name)) {
|
||||
cval = config.get(name);
|
||||
activename = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cval == null && param.isRequired()) {
|
||||
cval = param.getDefaultValue(); // We know this will be valid. It was validated, correct?
|
||||
}
|
||||
if (cval != null) {
|
||||
cval = convertValueTo(ofType.getSimpleName(), k, cval, type);
|
||||
validConfig.put(name, cval);
|
||||
cval = convertValueTo(ofType.getSimpleName(), activename, cval, type);
|
||||
validConfig.put(activename, cval);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
return new NBConfiguration(this.asReadOnly(), validConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertValidConfig(Map<String, ?> config) {
|
||||
assertRequiredFields(config);
|
||||
assertNoExtraneousFields(config);
|
||||
assertDistinctSynonyms(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Param<?> getParam(String... names) {
|
||||
for (String name : names) {
|
||||
if (this.getNamedParams().containsKey(name)) {
|
||||
return this.getNamedParams().get(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ConfigModel validIfRegex(String s) {
|
||||
Pattern regex = Pattern.compile(s);
|
||||
lastAdded.setRegex(regex);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void assertRequiredFields(Map<String, ?> config) {
|
||||
// For each known configuration model ...
|
||||
for (Param<?> param : params) {
|
||||
if (param.isRequired() && param.getDefaultValue() == null) {
|
||||
boolean provided = false;
|
||||
for (String name : param.names) {
|
||||
if (config.containsKey(name)) {
|
||||
provided = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!provided) {
|
||||
throw new RuntimeException("A required config element named '" + param.names +
|
||||
"' and type '" + param.getType().getSimpleName() + "' was not found\n" +
|
||||
"for configuring a " + getOf().getSimpleName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoExtraneousFields(Map<String, ?> config) {
|
||||
// For each provided configuration element ...
|
||||
for (String configkey : config.keySet()) {
|
||||
Param<?> element = this.paramsByName.get(configkey);
|
||||
if (element == null) {
|
||||
StringBuilder paramhelp = new StringBuilder(
|
||||
"Unknown config parameter '" + configkey + "' in config model while configuring " + getOf().getSimpleName()
|
||||
+ ", possible parameter names are " + this.paramsByName.keySet() + "."
|
||||
);
|
||||
|
||||
ConfigSuggestions.getForParam(this, configkey)
|
||||
.ifPresent(suggestion -> paramhelp.append(" ").append(suggestion));
|
||||
|
||||
throw new BasicError(paramhelp.toString());
|
||||
}
|
||||
Object value = config.get(configkey);
|
||||
Object testValue = convertValueTo(ofType.getSimpleName(), configkey, value, element.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBConfigModel add(NBConfigModel otherModel) {
|
||||
for (Param<?> param : otherModel.getParams()) {
|
||||
add(param);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,22 @@ import java.util.stream.Collectors;
|
||||
public class ConfigSuggestions {
|
||||
|
||||
public static Optional<String> getForParam(ConfigModel model, String param) {
|
||||
return suggestAlternateCase(model,param)
|
||||
.or(() -> suggestAlternates(model,param));
|
||||
}
|
||||
|
||||
private static Optional<String> suggestAlternateCase(ConfigModel model, String param) {
|
||||
for (String cname : model.getNamedParams().keySet()) {
|
||||
if (cname.equalsIgnoreCase(param)) {
|
||||
return Optional.of("Did you mean '" + cname + "'?");
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> suggestAlternates(ConfigModel model, String param) {
|
||||
Map<Integer, Set<String>> suggestions = new HashMap<>();
|
||||
for (String candidate : model.getElements().keySet()) {
|
||||
for (String candidate : model.getNamedParams().keySet()) {
|
||||
try {
|
||||
Integer distance = LevenshteinDistance.getDefaultInstance().apply(param, candidate);
|
||||
Set<String> strings = suggestions.computeIfAbsent(distance, d -> new HashSet<>());
|
||||
|
@ -1,50 +1,25 @@
|
||||
package io.nosqlbench.nb.api.config;
|
||||
|
||||
import io.nosqlbench.nb.api.config.standard.ConfigModel;
|
||||
import io.nosqlbench.nb.api.config.standard.NBConfiguration;
|
||||
import io.nosqlbench.nb.api.config.standard.Param;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ConfigModelTest {
|
||||
|
||||
@Test
|
||||
void optional() {
|
||||
}
|
||||
public void testMultipleParams() {
|
||||
ConfigModel cm = ConfigModel.of(ConfigModelTest.class,
|
||||
Param.defaultTo(List.of("a","b"),"value").setRequired(false),
|
||||
Param.required("c",int.class));
|
||||
NBConfiguration cfg = cm.apply(Map.of("c", 232));
|
||||
assertThat(cfg.getOptional("a")).isEmpty();
|
||||
assertThat(cfg.get("c",int.class)).isEqualTo(232);
|
||||
|
||||
@Test
|
||||
void testOptional() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void required() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequired() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultto() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultto() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void asReadOnly() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getElements() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOf() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertValidConfig() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void apply() {
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,13 @@ package io.nosqlbench.nb.api.config.standard;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ConfigElementTest {
|
||||
|
||||
@Test
|
||||
public void testRegex() {
|
||||
Param<String> cfgmodel =
|
||||
new Param<>("testvar",String.class,"testing a var",false,null).setRegex("WOO");
|
||||
Param<String> cfgmodel = Param.defaultTo("testvar", "default").setRegex("WOO");
|
||||
assertThat(cfgmodel.validate("WOO").isValid()).isTrue();
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ package io.nosqlbench.virtdata.library.basics.shared.stateful;
|
||||
|
||||
import io.nosqlbench.nb.api.config.standard.ConfigModel;
|
||||
import io.nosqlbench.nb.api.config.standard.NBConfigModel;
|
||||
import io.nosqlbench.nb.api.config.standard.NBMapConfigurable;
|
||||
import io.nosqlbench.nb.api.config.standard.Param;
|
||||
import io.nosqlbench.virtdata.api.annotations.Example;
|
||||
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
|
||||
import io.nosqlbench.nb.api.config.standard.NBMapConfigurable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@ -51,6 +52,6 @@ public class LoadElement implements Function<Object,Object>, NBMapConfigurable {
|
||||
|
||||
@Override
|
||||
public NBConfigModel getConfigModel() {
|
||||
return ConfigModel.of(this.getClass()).optional("<mapname>", Map.class);
|
||||
return ConfigModel.of(this.getClass(), Param.optional("<mapname>", Map.class));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user