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 io.nosqlbench.nb.api.errors.BasicError;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.math.BigDecimal;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ConfigModel implements NBConfigModel {
|
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 Param<?> lastAdded = null;
|
||||||
private final Class<?> ofType;
|
private final Class<?> ofType;
|
||||||
|
|
||||||
private ConfigModel(Class<?> ofType, Param<?>... params) {
|
private ConfigModel(Class<?> ofType, Param<?>... params) {
|
||||||
this.ofType = ofType;
|
this.ofType = ofType;
|
||||||
for (Param<?> param : params) {
|
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);
|
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) {
|
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;
|
lastAdded = null;
|
||||||
return this;
|
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() {
|
public NBConfigModel asReadOnly() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Param<?>> getElements() {
|
public Map<String, Param<?>> getNamedParams() {
|
||||||
return Collections.unmodifiableMap(elements);
|
return Collections.unmodifiableMap(paramsByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Param<?>> getParams() {
|
||||||
|
return new ArrayList<>((this.params));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -80,59 +52,51 @@ public class ConfigModel implements NBConfigModel {
|
|||||||
return ofType;
|
return ofType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static <T> T convertValueTo(String configName, String paramName, Object value, Class<T> type) {
|
||||||
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) {
|
|
||||||
try {
|
try {
|
||||||
if (type.isAssignableFrom(value.getClass())) {
|
if (type.isAssignableFrom(value.getClass())) {
|
||||||
return type.cast(value);
|
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;
|
Number number = (Number) value;
|
||||||
if (type.equals(Float.class)) {
|
// This series of double fake-outs is heinous, but it works to get around design
|
||||||
return number.floatValue();
|
// holes in Java generics while preserving some type inference for the caller.
|
||||||
} else if (type.equals(Integer.class)) {
|
// If you are reading this code and you can find a better way, please change it!
|
||||||
return number.intValue();
|
if (type.equals(Float.class) || type == float.class) {
|
||||||
} else if (type.equals(Double.class)) {
|
return (T) (Float) number.floatValue();
|
||||||
return number.doubleValue();
|
} else if (type.equals(Integer.class) || type == int.class) {
|
||||||
} else if (type.equals(Long.class)) {
|
return (T) (Integer) number.intValue();
|
||||||
return number.longValue();
|
} else if (type.equals(Double.class) || type == double.class) {
|
||||||
} else if (type.equals(Byte.class)) {
|
return (T) (Double) number.doubleValue();
|
||||||
return number.byteValue();
|
} else if (type.equals(Long.class) || type == long.class) {
|
||||||
} else if (type.equals(Short.class)) {
|
return (T) (Long) number.longValue();
|
||||||
return number.shortValue();
|
} 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 {
|
} else {
|
||||||
throw new RuntimeException("Number type " + type.getSimpleName() + " could " +
|
throw new RuntimeException("Number type " + type.getSimpleName() + " could " +
|
||||||
" not be converted from " + value.getClass().getSimpleName());
|
" 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) {
|
} 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
|
@Override
|
||||||
public NBConfiguration apply(Map<String, ?> config) {
|
public NBConfiguration apply(Map<String, ?> config) {
|
||||||
assertValidConfig(config);
|
assertValidConfig(config);
|
||||||
LinkedHashMap<String, Object> validConfig = new LinkedHashMap<>();
|
LinkedHashMap<String, Object> validConfig = new LinkedHashMap<>();
|
||||||
|
|
||||||
elements.forEach((k, v) -> {
|
for (Param<?> param : params) {
|
||||||
String name = v.getName();
|
Class<?> type = param.getType();
|
||||||
Class<?> type = v.getType();
|
List<String> found = new ArrayList<>();
|
||||||
Object cval = config.get(name);
|
String activename = null;
|
||||||
if (cval == null && v.isRequired()) {
|
Object cval = null;
|
||||||
cval = v.getDefaultValue();
|
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) {
|
if (cval != null) {
|
||||||
cval = convertValueTo(ofType.getSimpleName(), k, cval, type);
|
cval = convertValueTo(ofType.getSimpleName(), activename, cval, type);
|
||||||
validConfig.put(name, cval);
|
validConfig.put(activename, cval);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return new NBConfiguration(this.asReadOnly(), validConfig);
|
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) {
|
public ConfigModel validIfRegex(String s) {
|
||||||
Pattern regex = Pattern.compile(s);
|
Pattern regex = Pattern.compile(s);
|
||||||
lastAdded.setRegex(regex);
|
lastAdded.setRegex(regex);
|
||||||
return this;
|
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 class ConfigSuggestions {
|
||||||
|
|
||||||
public static Optional<String> getForParam(ConfigModel model, String param) {
|
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<>();
|
Map<Integer, Set<String>> suggestions = new HashMap<>();
|
||||||
for (String candidate : model.getElements().keySet()) {
|
for (String candidate : model.getNamedParams().keySet()) {
|
||||||
try {
|
try {
|
||||||
Integer distance = LevenshteinDistance.getDefaultInstance().apply(param, candidate);
|
Integer distance = LevenshteinDistance.getDefaultInstance().apply(param, candidate);
|
||||||
Set<String> strings = suggestions.computeIfAbsent(distance, d -> new HashSet<>());
|
Set<String> strings = suggestions.computeIfAbsent(distance, d -> new HashSet<>());
|
||||||
|
@ -1,50 +1,25 @@
|
|||||||
package io.nosqlbench.nb.api.config;
|
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 org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class ConfigModelTest {
|
public class ConfigModelTest {
|
||||||
|
|
||||||
@Test
|
@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 org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class ConfigElementTest {
|
public class ConfigElementTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegex() {
|
public void testRegex() {
|
||||||
Param<String> cfgmodel =
|
Param<String> cfgmodel = Param.defaultTo("testvar", "default").setRegex("WOO");
|
||||||
new Param<>("testvar",String.class,"testing a var",false,null).setRegex("WOO");
|
|
||||||
assertThat(cfgmodel.validate("WOO").isValid()).isTrue();
|
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.ConfigModel;
|
||||||
import io.nosqlbench.nb.api.config.standard.NBConfigModel;
|
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.Example;
|
||||||
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
|
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
|
||||||
import io.nosqlbench.nb.api.config.standard.NBMapConfigurable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -51,6 +52,6 @@ public class LoadElement implements Function<Object,Object>, NBMapConfigurable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NBConfigModel getConfigModel() {
|
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