All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.nosqlbench.nb.api.config.standard.ConfigModel Maven / Gradle / Ivy

Go to download

The top level API module for NoSQLBench. This module should have no internal module dependencies other than the mvn-default module. All modules within NoSQLBench can safely depend on this module with circular dependencies. This module provides cross-cutting code infrastracture, such as path utilities and ways of describing services used between modules. It is also the transitive aggregation point for system-wide library dependencies for logging and testing or similar needs.

There is a newer version: 5.17.0
Show newest version
package io.nosqlbench.nb.api.config.standard;

import io.nosqlbench.nb.api.errors.BasicError;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Pattern;

public class ConfigModel implements NBConfigModel {

    private final Map> paramsByName = new LinkedHashMap<>();
    private final List> params = new ArrayList<>();
    private Param lastAdded = null;
    private final Class ofType;

    private ConfigModel(Class ofType, Param... params) {
        this.ofType = ofType;
        for (Param param : params) {
            add(param);
        }
    }

    public static ConfigModel of(Class ofType, Param... params) {
        return new ConfigModel(ofType, params);
    }

    public  ConfigModel add(Param param) {
        this.params.add(param);
        for (String name : param.getNames()) {
            paramsByName.put(name, param);
        }
        lastAdded = null;
        return this;
    }

    public NBConfigModel asReadOnly() {
        return this;
    }

    @Override
    public Map> getNamedParams() {
        return Collections.unmodifiableMap(paramsByName);
    }

    @Override
    public List> getParams() {
        return new ArrayList<>((this.params));
    }

    @Override
    public Class getOf() {
        return ofType;
    }

    public static  T convertValueTo(String configName, String paramName, Object value, Class type) {
        try {
            if (type.isAssignableFrom(value.getClass())) {
                return type.cast(value);

            } else if (Number.class.isAssignableFrom(value.getClass())) { // A numeric value, and do we have a compatible target type?
                Number number = (Number) value;
                // 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) {
            throw e;
        }

        throw new RuntimeException(
            "While configuring " + paramName + " for " + configName + ", " +
                "Unable to convert " + value.getClass() + " to " +
                type.getCanonicalName()
        );
    }

    @Override
    public NBConfiguration extractConfig(Map sharedConfig) {
        LinkedHashMap extracted = new LinkedHashMap<>();
        for (String providedCfgField : sharedConfig.keySet()) {
            if (getNamedParams().containsKey(providedCfgField)) {
                extracted.put(providedCfgField, sharedConfig.remove(providedCfgField));
            }
        }
        return new NBConfiguration(this, extracted);
    }

    @Override
    public NBConfiguration extractConfig(NBConfiguration cfg) {
        return extractConfig(cfg.getMap());
    }

    private void assertDistinctSynonyms(Map config) {
        List 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 config) {
        assertValidConfig(config);
        LinkedHashMap validConfig = new LinkedHashMap<>();

        for (Param param : params) {
            Class type = param.getType();
            List found = new ArrayList<>();
            String activename = null;
            Object cval = null;
            for (String name : param.getNames()) {
                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(), activename, cval, type);
                validConfig.put(activename, cval);
            }
        }
        return new NBConfiguration(this.asReadOnly(), validConfig);
    }

    @Override
    public void assertValidConfig(Map 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 config) {
        // For each known configuration model ...
        for (Param param : params) {
            if (param.isRequired() && param.getDefaultValue() == null) {
                boolean provided = false;
                for (String name : param.getNames()) {
                    if (config.containsKey(name)) {
                        provided = true;
                        break;
                    }
                }
                if (!provided) {
                    throw new RuntimeException("A required config element named '" + param.getNames() +
                        "' and type '" + param.getType().getSimpleName() + "' was not found\n" +
                        "for configuring a " + getOf().getSimpleName());
                }

            }
        }
    }

    private void assertNoExtraneousFields(Map 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 ConfigModel add(NBConfigModel otherModel) {
        for (Param param : otherModel.getParams()) {
            add(param);
        }
        return this;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy