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

io.smallrye.config.ConfigMappingProvider Maven / Gradle / Ivy

package io.smallrye.config;

import static io.smallrye.config.ConfigMappingInterface.GroupProperty;
import static io.smallrye.config.ConfigMappingInterface.LeafProperty;
import static io.smallrye.config.ConfigMappingInterface.MapProperty;
import static io.smallrye.config.ConfigMappingInterface.MayBeOptionalProperty;
import static io.smallrye.config.ConfigMappingInterface.PrimitiveProperty;
import static io.smallrye.config.ConfigMappingInterface.Property;
import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass;
import static io.smallrye.config.ConfigMappingLoader.getConfigMappingInterface;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import org.eclipse.microprofile.config.spi.Converter;

import io.smallrye.common.constraint.Assert;
import io.smallrye.common.function.Functions;

/**
 *
 */
final class ConfigMappingProvider implements Serializable {
    private static final long serialVersionUID = 3977667610888849912L;

    /**
     * The do-nothing action is used when the matched property is eager.
     */
    private static final BiConsumer DO_NOTHING = Functions.discardingBiConsumer();
    private static final KeyMap> IGNORE_EVERYTHING;

    static {
        final KeyMap> map = new KeyMap<>();
        map.putRootValue(DO_NOTHING);
        IGNORE_EVERYTHING = map;
    }

    private final Map>> roots;
    private final KeyMap> matchActions;
    private final KeyMap defaultValues;
    private final boolean validateUnknown;

    ConfigMappingProvider(final Builder builder) {
        this.roots = new HashMap<>(builder.roots);
        final ArrayDeque currentPath = new ArrayDeque<>();
        KeyMap> matchActions = new KeyMap<>();
        KeyMap defaultValues = new KeyMap<>();
        for (Map.Entry>> entry : roots.entrySet()) {
            NameIterator rootNi = new NameIterator(entry.getKey());
            while (rootNi.hasNext()) {
                final String nextSegment = rootNi.getNextSegment();
                if (!nextSegment.isEmpty()) {
                    currentPath.add(nextSegment);
                }
                rootNi.next();
            }
            List> roots = entry.getValue();
            for (Class root : roots) {
                // construct the lazy match actions for each group
                BiFunction ef = new GetRootAction(root,
                        entry.getKey());
                processEagerGroup(currentPath, matchActions, defaultValues, getConfigMappingInterface(root), ef);
            }
            currentPath.clear();
        }
        for (String[] ignoredPath : builder.ignored) {
            int len = ignoredPath.length;
            KeyMap> found;
            if (ignoredPath[len - 1].equals("**")) {
                found = matchActions.findOrAdd(ignoredPath, 0, len - 1);
                found.putRootValue(DO_NOTHING);
                found.putAny(IGNORE_EVERYTHING);
            } else {
                found = matchActions.findOrAdd(ignoredPath);
                found.putRootValue(DO_NOTHING);
            }
        }
        this.matchActions = matchActions;
        this.defaultValues = defaultValues;
        this.validateUnknown = builder.validateUnknown;
    }

    static String skewer(Method method) {
        return skewer(method.getName());
    }

    static String skewer(String camelHumps) {
        return skewer(camelHumps, 0, camelHumps.length(), new StringBuilder());
    }

    static String skewer(String camelHumps, int start, int end, StringBuilder b) {
        assert !camelHumps.isEmpty() : "Method seems to have an empty name";
        int cp = camelHumps.codePointAt(start);
        b.appendCodePoint(Character.toLowerCase(cp));
        start += Character.charCount(cp);
        if (start == end) {
            // a lonely character at the end of the string
            return b.toString();
        }
        if (Character.isUpperCase(cp)) {
            // all-uppercase words need one code point of lookahead
            int nextCp = camelHumps.codePointAt(start);
            if (Character.isUpperCase(nextCp)) {
                // it's some kind of `WORD`
                for (;;) {
                    b.appendCodePoint(Character.toLowerCase(cp));
                    start += Character.charCount(cp);
                    cp = nextCp;
                    if (start == end) {
                        return b.toString();
                    }
                    nextCp = camelHumps.codePointAt(start);
                    // combine non-letters in with this name
                    if (Character.isLowerCase(nextCp)) {
                        b.append('-');
                        return skewer(camelHumps, start, end, b);
                    }
                }
                // unreachable
            } else {
                // it was the start of a `Word`; continue until we hit the end or an uppercase.
                b.appendCodePoint(nextCp);
                start += Character.charCount(nextCp);
                for (;;) {
                    if (start == end) {
                        return b.toString();
                    }
                    cp = camelHumps.codePointAt(start);
                    // combine non-letters in with this name
                    if (Character.isUpperCase(cp)) {
                        b.append('-');
                        return skewer(camelHumps, start, end, b);
                    }
                    b.appendCodePoint(cp);
                    start += Character.charCount(cp);
                }
                // unreachable
            }
            // unreachable
        } else {
            // it's some kind of `word`
            for (;;) {
                cp = camelHumps.codePointAt(start);
                // combine non-letters in with this name
                if (Character.isUpperCase(cp)) {
                    b.append('-');
                    return skewer(camelHumps, start, end, b);
                }
                b.appendCodePoint(cp);
                start += Character.charCount(cp);
                if (start == end) {
                    return b.toString();
                }
            }
            // unreachable
        }
        // unreachable
    }

    static final class ConsumeOneAndThen implements BiConsumer {
        private final BiConsumer delegate;

        ConsumeOneAndThen(final BiConsumer delegate) {
            this.delegate = delegate;
        }

        public void accept(final ConfigMappingContext context, final NameIterator nameIterator) {
            nameIterator.previous();
            delegate.accept(context, nameIterator);
            nameIterator.next();
        }
    }

    static final class ConsumeOneAndThenFn implements BiFunction {
        private final BiFunction delegate;

        ConsumeOneAndThenFn(final BiFunction delegate) {
            this.delegate = delegate;
        }

        public T apply(final ConfigMappingContext context, final NameIterator nameIterator) {
            nameIterator.previous();
            T result = delegate.apply(context, nameIterator);
            nameIterator.next();
            return result;
        }
    }

    private void processEagerGroup(final ArrayDeque currentPath,
            final KeyMap> matchActions, final KeyMap defaultValues,
            final ConfigMappingInterface group,
            final BiFunction getEnclosingFunction) {
        Class type = group.getInterfaceType();
        int pc = group.getPropertyCount();
        int pathLen = currentPath.size();
        HashSet usedProperties = new HashSet<>();
        for (int i = 0; i < pc; i++) {
            Property property = group.getProperty(i);
            String memberName = property.getMethod().getName();
            if (usedProperties.add(memberName)) {
                // process by property type
                if (!property.isParentPropertyName()) {
                    String propertyName = property.hasPropertyName() ? property.getPropertyName()
                            : skewer(property.getMethod());
                    NameIterator ni = new NameIterator(propertyName);
                    while (ni.hasNext()) {
                        currentPath.add(ni.getNextSegment());
                        ni.next();
                    }
                }
                if (property.isOptional()) {
                    // switch to lazy mode
                    MayBeOptionalProperty nestedProperty = property.asOptional().getNestedProperty();
                    if (nestedProperty.isGroup()) {
                        GroupProperty nestedGroup = nestedProperty.asGroup();
                        // on match, always create the outermost group, which recursively creates inner groups
                        GetOrCreateEnclosingGroupInGroup matchAction = new GetOrCreateEnclosingGroupInGroup(
                                getEnclosingFunction, group, nestedGroup);
                        GetFieldOfEnclosing ef = new GetFieldOfEnclosing(
                                nestedGroup.isParentPropertyName() ? getEnclosingFunction
                                        : new ConsumeOneAndThenFn<>(getEnclosingFunction),
                                type, memberName);
                        processLazyGroupInGroup(currentPath, matchActions, defaultValues, nestedGroup, ef, matchAction,
                                new HashSet<>());
                    } else if (nestedProperty.isLeaf()) {
                        LeafProperty leafProperty = nestedProperty.asLeaf();
                        if (leafProperty.hasDefaultValue()) {
                            defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue());
                        }
                        matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING);
                    }
                } else if (property.isGroup()) {
                    processEagerGroup(currentPath, matchActions, defaultValues, property.asGroup().getGroupType(),
                            new GetOrCreateEnclosingGroupInGroup(getEnclosingFunction, group, property.asGroup()));
                } else if (property.isPrimitive()) {
                    // already processed eagerly
                    PrimitiveProperty primitiveProperty = property.asPrimitive();
                    if (primitiveProperty.hasDefaultValue()) {
                        defaultValues.findOrAdd(currentPath).putRootValue(primitiveProperty.getDefaultValue());
                    }
                    matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING);
                } else if (property.isLeaf()) {
                    // already processed eagerly
                    LeafProperty leafProperty = property.asLeaf();
                    if (leafProperty.hasDefaultValue()) {
                        defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue());
                    }
                    // ignore with no error message
                    matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING);
                } else if (property.isMap()) {
                    // the enclosure of the map is this group
                    processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), getEnclosingFunction,
                            group);
                }
                while (currentPath.size() > pathLen) {
                    currentPath.removeLast();
                }
            }
        }
        int sc = group.getSuperTypeCount();
        for (int i = 0; i < sc; i++) {
            processEagerGroup(currentPath, matchActions, defaultValues, group.getSuperType(i), getEnclosingFunction);
        }
    }

    private void processLazyGroupInGroup(ArrayDeque currentPath,
            KeyMap> matchActions,
            KeyMap defaultValues,
            GroupProperty groupProperty,
            BiFunction getEnclosingFunction,
            BiConsumer matchAction, HashSet usedProperties) {
        ConfigMappingInterface group = groupProperty.getGroupType();
        int pc = group.getPropertyCount();
        int pathLen = currentPath.size();
        for (int i = 0; i < pc; i++) {
            Property property = group.getProperty(i);
            if (!property.isParentPropertyName()) {
                String propertyName = property.hasPropertyName() ? property.getPropertyName()
                        : skewer(property.getMethod());
                NameIterator ni = new NameIterator(propertyName);
                while (ni.hasNext()) {
                    currentPath.add(ni.getNextSegment());
                    ni.next();
                }
            }
            if (usedProperties.add(property.getMethod().getName())) {
                boolean optional = property.isOptional();
                if (optional && property.asOptional().getNestedProperty().isGroup()) {
                    GroupProperty nestedGroup = property.asOptional().getNestedProperty().asGroup();
                    GetOrCreateEnclosingGroupInGroup nestedMatchAction = new GetOrCreateEnclosingGroupInGroup(
                            property.isParentPropertyName() ? getEnclosingFunction
                                    : new ConsumeOneAndThenFn<>(getEnclosingFunction),
                            group, nestedGroup);
                    processLazyGroupInGroup(currentPath, matchActions, defaultValues, nestedGroup, nestedMatchAction,
                            nestedMatchAction, new HashSet<>());
                } else if (property.isGroup()) {
                    GroupProperty asGroup = property.asGroup();
                    GetOrCreateEnclosingGroupInGroup nestedEnclosingFunction = new GetOrCreateEnclosingGroupInGroup(
                            property.isParentPropertyName() ? getEnclosingFunction
                                    : new ConsumeOneAndThenFn<>(getEnclosingFunction),
                            group, asGroup);
                    BiConsumer nestedMatchAction;
                    nestedMatchAction = matchAction;
                    if (!property.isParentPropertyName()) {
                        nestedMatchAction = new ConsumeOneAndThen(nestedMatchAction);
                    }
                    processLazyGroupInGroup(currentPath, matchActions, defaultValues, asGroup, nestedEnclosingFunction,
                            nestedMatchAction, usedProperties);
                } else if (property.isLeaf() || property.isPrimitive()
                        || optional && property.asOptional().getNestedProperty().isLeaf()) {
                    BiConsumer actualAction;
                    if (!property.isParentPropertyName()) {
                        actualAction = new ConsumeOneAndThen(matchAction);
                    } else {
                        actualAction = matchAction;
                    }
                    matchActions.findOrAdd(currentPath).putRootValue(actualAction);
                    if (property.isPrimitive()) {
                        PrimitiveProperty primitiveProperty = property.asPrimitive();
                        if (primitiveProperty.hasDefaultValue()) {
                            defaultValues.findOrAdd(currentPath).putRootValue(primitiveProperty.getDefaultValue());
                        }
                    } else if (property.isLeaf()) {
                        LeafProperty leafProperty = property.asLeaf();
                        if (leafProperty.hasDefaultValue()) {
                            defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue());
                        }
                    } else {
                        LeafProperty leafProperty = property.asOptional().getNestedProperty().asLeaf();
                        if (leafProperty.hasDefaultValue()) {
                            defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue());
                        }
                    }
                } else if (property.isMap()) {
                    processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), getEnclosingFunction,
                            group);
                }
            }
            while (currentPath.size() > pathLen) {
                currentPath.removeLast();
            }
        }
        int sc = group.getSuperTypeCount();
        for (int i = 0; i < sc; i++) {
            processLazyGroupInGroup(currentPath, matchActions, defaultValues, groupProperty, getEnclosingFunction,
                    matchAction, usedProperties);
        }
    }

    private void processLazyMapInGroup(final ArrayDeque currentPath,
            final KeyMap> matchActions, final KeyMap defaultValues,
            final MapProperty property, BiFunction getEnclosingGroup,
            ConfigMappingInterface enclosingGroup) {
        GetOrCreateEnclosingMapInGroup getEnclosingMap = new GetOrCreateEnclosingMapInGroup(getEnclosingGroup, enclosingGroup,
                property);
        processLazyMap(currentPath, matchActions, defaultValues, property, getEnclosingMap, enclosingGroup);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void processLazyMap(final ArrayDeque currentPath,
            final KeyMap> matchActions, final KeyMap defaultValues,
            final MapProperty property, BiFunction> getEnclosingMap,
            ConfigMappingInterface enclosingGroup) {
        Property valueProperty = property.getValueProperty();
        Class> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null;
        Class keyRawType = property.getKeyRawType();

        currentPath.addLast("*");
        if (valueProperty.isLeaf()) {
            LeafProperty leafProperty = valueProperty.asLeaf();
            Class> valConvertWith = leafProperty.getConvertWith();
            Class valueRawType = leafProperty.getValueRawType();

            matchActions.findOrAdd(currentPath).putRootValue((mc, ni) -> {
                StringBuilder sb = mc.getStringBuilder();
                sb.setLength(0);
                sb.append(ni.getAllPreviousSegments());
                String configKey = sb.toString();
                Map map = getEnclosingMap.apply(mc, ni);
                String rawMapKey = ni.getPreviousSegment();
                Converter keyConv;
                SmallRyeConfig config = mc.getConfig();
                if (keyConvertWith != null) {
                    keyConv = mc.getConverterInstance(keyConvertWith);
                } else {
                    keyConv = config.requireConverter(keyRawType);
                }
                Object key = keyConv.convert(rawMapKey);
                Converter valueConv;
                if (valConvertWith != null) {
                    valueConv = mc.getConverterInstance(valConvertWith);
                } else {
                    valueConv = config.requireConverter(valueRawType);
                }
                ((Map) map).put(key, config.getValue(configKey, valueConv));
            });
        } else if (valueProperty.isMap()) {
            processLazyMap(currentPath, matchActions, defaultValues, valueProperty.asMap(), (mc, ni) -> {
                ni.previous();
                Map enclosingMap = getEnclosingMap.apply(mc, ni);
                ni.next();
                String rawMapKey = ni.getPreviousSegment();
                Converter keyConv;
                SmallRyeConfig config = mc.getConfig();
                if (keyConvertWith != null) {
                    keyConv = mc.getConverterInstance(keyConvertWith);
                } else {
                    keyConv = config.requireConverter(keyRawType);
                }
                Object key = keyConv.convert(rawMapKey);
                return (Map) ((Map) enclosingMap).computeIfAbsent(key, x -> new HashMap<>());
            }, enclosingGroup);
        } else {
            assert valueProperty.isGroup();
            final GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap(getEnclosingMap, property,
                    enclosingGroup, valueProperty.asGroup());
            processLazyGroupInGroup(currentPath, matchActions, defaultValues, valueProperty.asGroup(),
                    ef, ef, new HashSet<>());
        }
        currentPath.removeLast();
    }

    static class GetRootAction implements BiFunction {
        private final Class root;
        private final String rootPath;

        GetRootAction(final Class root, final String rootPath) {
            this.root = root;
            this.rootPath = rootPath;
        }

        public ConfigMappingObject apply(final ConfigMappingContext mc, final NameIterator ni) {
            return mc.getRoot(root, rootPath);
        }
    }

    static class GetOrCreateEnclosingGroupInGroup
            implements BiFunction,
            BiConsumer {
        private final BiFunction delegate;
        private final ConfigMappingInterface enclosingGroup;
        private final GroupProperty enclosedGroup;

        GetOrCreateEnclosingGroupInGroup(final BiFunction delegate,
                final ConfigMappingInterface enclosingGroup, final GroupProperty enclosedGroup) {
            this.delegate = delegate;
            this.enclosingGroup = enclosingGroup;
            this.enclosedGroup = enclosedGroup;
        }

        public ConfigMappingObject apply(final ConfigMappingContext context, final NameIterator ni) {
            ConfigMappingObject ourEnclosing = delegate.apply(context, ni);
            Class enclosingType = enclosingGroup.getInterfaceType();
            String methodName = enclosedGroup.getMethod().getName();
            ConfigMappingObject val = (ConfigMappingObject) context.getEnclosedField(enclosingType, methodName, ourEnclosing);
            if (val == null) {
                // it must be an optional group
                StringBuilder sb = context.getStringBuilder();
                sb.replace(0, sb.length(), ni.getAllPreviousSegments());
                val = (ConfigMappingObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType());
                context.registerEnclosedField(enclosingType, methodName, ourEnclosing, val);
            }
            return val;
        }

        public void accept(final ConfigMappingContext context, final NameIterator nameIterator) {
            apply(context, nameIterator);
        }
    }

    static class GetOrCreateEnclosingGroupInMap implements BiFunction,
            BiConsumer {
        final BiFunction> getEnclosingMap;
        final MapProperty enclosingMap;
        final ConfigMappingInterface enclosingGroup;
        private final GroupProperty enclosedGroup;

        GetOrCreateEnclosingGroupInMap(final BiFunction> getEnclosingMap,
                final MapProperty enclosingMap, final ConfigMappingInterface enclosingGroup,
                final GroupProperty enclosedGroup) {
            this.getEnclosingMap = getEnclosingMap;
            this.enclosingMap = enclosingMap;
            this.enclosingGroup = enclosingGroup;
            this.enclosedGroup = enclosedGroup;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        public ConfigMappingObject apply(final ConfigMappingContext context, final NameIterator ni) {
            ni.previous();
            Map ourEnclosing = getEnclosingMap.apply(context, ni);
            ni.next();
            String mapKey = ni.getPreviousSegment();
            Converter keyConverter = context.getKeyConverter(enclosingGroup.getInterfaceType(),
                    enclosingMap.getMethod().getName(), enclosingMap.getLevels() - 1);
            ConfigMappingObject val = (ConfigMappingObject) ourEnclosing.get(mapKey);
            if (val == null) {
                StringBuilder sb = context.getStringBuilder();
                sb.replace(0, sb.length(), ni.getAllPreviousSegments());
                Object convertedKey = keyConverter.convert(mapKey);
                ((Map) ourEnclosing).put(convertedKey,
                        val = (ConfigMappingObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType()));
            }
            return val;
        }

        public void accept(final ConfigMappingContext context, final NameIterator ni) {
            apply(context, ni);
        }
    }

    static class GetOrCreateEnclosingMapInGroup implements BiFunction>,
            BiConsumer {
        final BiFunction getEnclosingGroup;
        final ConfigMappingInterface enclosingGroup;
        final MapProperty property;

        GetOrCreateEnclosingMapInGroup(
                final BiFunction getEnclosingGroup,
                final ConfigMappingInterface enclosingGroup, final MapProperty property) {
            this.getEnclosingGroup = getEnclosingGroup;
            this.enclosingGroup = enclosingGroup;
            this.property = property;
        }

        public Map apply(final ConfigMappingContext context, final NameIterator ni) {
            boolean consumeName = !property.isParentPropertyName();
            if (consumeName)
                ni.previous();
            ConfigMappingObject ourEnclosing = getEnclosingGroup.apply(context, ni);
            if (consumeName)
                ni.next();
            Class enclosingType = enclosingGroup.getInterfaceType();
            String methodName = property.getMethod().getName();
            Map val = (Map) context.getEnclosedField(enclosingType, methodName, ourEnclosing);
            if (val == null) {
                // map is not yet constructed
                val = new HashMap<>();
                context.registerEnclosedField(enclosingType, methodName, ourEnclosing, val);
            }
            return val;
        }

        public void accept(final ConfigMappingContext context, final NameIterator ni) {
            apply(context, ni);
        }
    }

    static class GetFieldOfEnclosing implements BiFunction {
        private final BiFunction getEnclosingFunction;
        private final Class type;
        private final String memberName;

        GetFieldOfEnclosing(final BiFunction getEnclosingFunction,
                final Class type, final String memberName) {
            this.getEnclosingFunction = getEnclosingFunction;
            this.type = type;
            this.memberName = memberName;
        }

        public ConfigMappingObject apply(final ConfigMappingContext mc, final NameIterator ni) {
            ConfigMappingObject outer = getEnclosingFunction.apply(mc, ni);
            // eagerly populated groups will always exist
            return (ConfigMappingObject) mc.getEnclosedField(type, memberName, outer);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    static Map getOrCreateEnclosingMapInMap(
            ConfigMappingContext context, NameIterator ni,
            BiFunction> getEnclosingMap, ConfigMappingInterface enclosingGroup,
            MapProperty property) {
        ni.previous();
        Map ourEnclosing = getEnclosingMap.apply(context, ni);
        String mapKey = ni.getNextSegment();
        Converter keyConverter = context.getKeyConverter(enclosingGroup.getInterfaceType(), property.getMethod().getName(),
                property.getLevels() - 1);
        Object realKey = keyConverter.convert(mapKey);
        Map map = (Map) ourEnclosing.get(realKey);
        if (map == null) {
            map = new HashMap<>();
            ((Map) ourEnclosing).put(realKey, map);
        }
        ni.next();
        return map;
    }

    public static Builder builder() {
        return new Builder();
    }

    KeyMap getDefaultValues() {
        return defaultValues;
    }

    void mapConfiguration(SmallRyeConfig config) throws ConfigValidationException {
        mapConfiguration(config, config.getConfigMappings());
    }

    private void mapConfiguration(SmallRyeConfig config, ConfigMappings mappings) throws ConfigValidationException {
        if (roots.isEmpty()) {
            return;
        }

        Assert.checkNotNullParam("config", config);
        final ConfigMappingContext context = new ConfigMappingContext(config);
        // eagerly populate roots
        for (Map.Entry>> entry : roots.entrySet()) {
            String path = entry.getKey();
            List> roots = entry.getValue();
            for (Class root : roots) {
                StringBuilder sb = context.getStringBuilder();
                sb.replace(0, sb.length(), path);
                ConfigMappingObject group = (ConfigMappingObject) context.constructGroup(root);
                context.registerRoot(root, path, group);
            }
        }
        // lazily sweep
        for (String name : config.getPropertyNames()) {
            // filter properties in root
            if (!isPropertyInRoot(name)) {
                continue;
            }

            NameIterator ni = new NameIterator(name);
            BiConsumer action = matchActions.findRootValue(ni);
            if (action != null) {
                action.accept(context, ni);
            } else {
                if (validateUnknown) {
                    context.unknownConfigElement(name);
                }
            }
        }
        ArrayList problems = context.getProblems();
        if (!problems.isEmpty()) {
            throw new ConfigValidationException(problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS));
        }
        context.fillInOptionals();

        mappings.registerConfigMappings(context.getRootsMap());
    }

    private boolean isPropertyInRoot(String propertyName) {
        final Set registeredRoots = roots.keySet();
        for (String registeredRoot : registeredRoots) {
            if (propertyName.startsWith(registeredRoot)) {
                return true;
            }
        }
        return false;
    }

    public static final class Builder {
        final Map>> roots = new HashMap<>();
        final List ignored = new ArrayList<>();
        boolean validateUnknown = true;

        Builder() {
        }

        public Builder addRoot(String path, Class type) {
            Assert.checkNotNullParam("path", path);
            Assert.checkNotNullParam("type", type);
            roots.computeIfAbsent(path, k -> new ArrayList<>(4)).add(getConfigMappingClass(type));
            return this;
        }

        public Builder addIgnored(String path) {
            Assert.checkNotNullParam("path", path);
            ignored.add(path.split("\\."));
            return this;
        }

        public Builder validateUnknown(boolean validateUnknown) {
            this.validateUnknown = validateUnknown;
            return this;
        }

        public ConfigMappingProvider build() {
            return new ConfigMappingProvider(this);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy