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

io.micronaut.context.env.DefaultConfigurationPath Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.context.env;

import io.micronaut.context.annotation.ConfigurationReader;
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.PropertyResolver;
import io.micronaut.inject.BeanDefinition;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Implementation of {@link ConfigurationPath}.
 *
 * @since 4.0.0
 */
final class DefaultConfigurationPath implements ConfigurationPath {
    private final LinkedList list = new LinkedList<>();
    private String computedPrefix;
    private boolean hasDynamicSegments = false;

    DefaultConfigurationPath() {
        recomputeState();
    }

    @Override
    public boolean hasDynamicSegments() {
        return hasDynamicSegments ||
            kind() == ConfigurationSegment.ConfigurationKind.NAME ||
            kind() == ConfigurationSegment.ConfigurationKind.INDEX;
    }

    @Override
    public ConfigurationPath parent() {
        int i = list.size();
        if (i > 1) {
            DefaultConfigurationPath configurationPath = new DefaultConfigurationPath();
            configurationPath.list.addAll(list.subList(0, i - 1));
            configurationPath.hasDynamicSegments = hasDynamicSegments;
            configurationPath.recomputeState();
            return configurationPath;
        }
        return null;
    }

    @Override
    public ConfigurationPath copy() {
        DefaultConfigurationPath newPath = new DefaultConfigurationPath();
        newPath.list.addAll(this.list);
        newPath.computedPrefix = computedPrefix;
        newPath.hasDynamicSegments = hasDynamicSegments;
        return newPath;
    }

    @Override
    public String prefix() {
        return computedPrefix;
    }

    @Override
    public String path() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.path();
        } else {
            return StringUtils.EMPTY_STRING;
        }
    }

    @Override
    public String primary() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.primary();
        }
        return null;
    }

    @Override
    public boolean isNotEmpty() {
        return !list.isEmpty();
    }

    @Override
    public String resolveValue(String value) {
        return value.replace(path(), prefix());
    }

    @Override
    public Class configurationType() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.type();
        }
        return null;
    }

    @Override
    public String name() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.name();
        }
        return null;
    }

    @Override
    public int index() {
        Iterator i = list.descendingIterator();
        while (i.hasNext()) {
            ConfigurationSegment s = i.next();
            if (s.kind() == ConfigurationSegment.ConfigurationKind.INDEX) {
                return s.index();
            }
        }
        return -1;
    }

    @Override
    public String simpleName() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.simpleName();
        }
        return null;
    }

    @Override
    public void traverseResolvableSegments(PropertyResolver propertyResolver, Consumer callback) {
        if (hasDynamicSegments) {
            // match a path pattern like foo.*.bar.*
            Collection> variableValues = propertyResolver.getPropertyPathMatches(path());
            for (List variables : variableValues) {
                ConfigurationPath newPath = replaceVariables(variables);
                traversePath(newPath, propertyResolver, callback);
            }

        } else {
            // simple case just traverse entries
            traversePath(this, propertyResolver, callback);
        }
    }

    @SuppressWarnings("java:S1301")
    private ConfigurationPath replaceVariables(List variables) {
        int varIndex = 0;
        DefaultConfigurationPath newPath = new DefaultConfigurationPath();
        newPath.hasDynamicSegments = true;
        for (ConfigurationSegment configurationSegment : list) {
            switch (configurationSegment.kind()) {
                case NAME, INDEX -> {
                    if (varIndex < variables.size()) {
                        ConfigurationSegment.ConfigurationKind kind = newPath.kind();
                        switch (kind) {
                            case LIST ->
                                newPath.pushConfigurationSegment(Integer.parseInt(variables.get(varIndex++)));
                            case MAP ->
                                newPath.pushConfigurationSegment(variables.get(varIndex++));
                            default ->
                                newPath.pushConfigurationSegment(configurationSegment);
                        }
                    } else {
                        newPath.pushConfigurationSegment(configurationSegment);
                    }
                }
                default -> newPath.pushConfigurationSegment(configurationSegment);
            }
        }

        return newPath;
    }

    private static void traversePath(ConfigurationPath thisPath, PropertyResolver propertyResolver, Consumer callback) {
        ConfigurationSegment.ConfigurationKind kind = thisPath.kind();
        switch (kind) {
            case MAP -> {
                Collection entries = propertyResolver.getPropertyEntries(thisPath.prefix());
                for (String key : entries) {
                    ConfigurationPath newPath = thisPath.copy();
                    newPath.pushConfigurationSegment(key);
                    callback.accept(newPath);
                }
            }
            case LIST -> {
                List entries = propertyResolver.getProperty(thisPath.prefix(), List.class, Collections.emptyList());
                for (int i = 0; i < entries.size(); i++) {
                    Object o = entries.get(i);
                    if (o != null) {
                        ConfigurationPath newPath = thisPath.copy();
                        newPath.pushConfigurationSegment(i);
                        callback.accept(newPath);
                    }
                }
            }
            case NAME, INDEX -> {
                ConfigurationPath parent = thisPath.parent();
                if (parent != null) {
                    traversePath(parent, propertyResolver, callback);
                }
            }
            default -> {
                if (propertyResolver.containsProperties(thisPath.prefix())) {
                    callback.accept(thisPath);
                }
            }
        }
    }

    @Override
    public ConfigurationSegment.ConfigurationKind kind() {
        ConfigurationSegment segment = peekLast();
        if (segment != null) {
            return segment.kind();
        }
        return ConfigurationSegment.ConfigurationKind.ROOT;
    }

    @Override
    public ConfigurationSegment peekLast() {
        return list.peekLast();
    }

    @Override
    public boolean isWithin(String prefix) {
        return prefix != null && prefix.startsWith(path());
    }

    @Override
    public void pushEachPropertyRoot(BeanDefinition beanDefinition) {
        if (!beanDefinition.getBeanType().equals(configurationType())) {

            if (kind() != ConfigurationSegment.ConfigurationKind.ROOT) {
                this.hasDynamicSegments = true;
            }

            boolean isList = beanDefinition.booleanValue(EachProperty.class, "list").orElse(false);
            String prefix = beanDefinition.stringValue(ConfigurationReader.class, ConfigurationReader.PREFIX).orElse(null);
            if (prefix != null) {
                String currentPath = path();
                if (!prefix.startsWith(currentPath)) {
                    throw new IllegalStateException("Invalid configuration properties nesting for path [" + prefix + "]. Expected: " + currentPath);
                }

                String resolvedPrefix = prefix;
                if (!currentPath.equals(StringUtils.EMPTY_STRING) && !prefix.equals(currentPath)) {
                    resolvedPrefix = prefix.substring(currentPath.length() + 1);
                }

                String property = resolvedPrefix.substring(0, resolvedPrefix.length() - (isList ? 3 : 2));
                String primaryName = beanDefinition.stringValue(EachProperty.class, "primary").orElse(null);

                list.add(new DefaultConfigurationSegment(
                    beanDefinition.getBeanType(),
                    property,
                    prefix,
                    isList ? ConfigurationSegment.ConfigurationKind.LIST : ConfigurationSegment.ConfigurationKind.MAP,
                    null,
                    null,
                    primaryName,
                    -1
                ));
                recomputeState();
            }
        }
    }

    @Override
    public void pushConfigurationReader(BeanDefinition beanDefinition) {
        if (!beanDefinition.getBeanType().equals(configurationType())) {

            if (kind() != ConfigurationSegment.ConfigurationKind.ROOT) {
                this.hasDynamicSegments = true;
            }

            String prefix = beanDefinition.stringValue(ConfigurationReader.class, ConfigurationReader.PREFIX).orElse(null);
            if (prefix != null) {
                String currentPath = path();
                if (!prefix.startsWith(currentPath)) {
                    throw new IllegalStateException("Invalid configuration properties nesting for path [" + prefix + "]. Expected: " + currentPath);
                }
                String p = prefix.substring(currentPath.length() + 1);
                list.add(new DefaultConfigurationSegment(
                    beanDefinition.getBeanType(),
                    p,
                    prefix,
                    ConfigurationSegment.ConfigurationKind.ROOT,
                    name(),
                    simpleName(),
                    primary(),
                    -1
                ));
                recomputeState();
            }
        }
    }

    @Override
    public void pushConfigurationSegment(ConfigurationSegment configurationSegment) {
        ConfigurationSegment.ConfigurationKind kind = configurationSegment.kind();
        switch (kind) {
            case NAME ->
                pushConfigurationSegment(configurationSegment.name());
            case INDEX ->
                pushConfigurationSegment(configurationSegment.index());
            case ROOT ->
                list.add(new DefaultConfigurationSegment(
                    configurationSegment.type(),
                    configurationSegment.prefix(),
                    configurationSegment.path(),
                    ConfigurationSegment.ConfigurationKind.ROOT,
                    name(), // inherit name
                    simpleName(),
                    primary(),  // inherit name
                    index() // inherit the index
                ));
            default ->
                list.add(configurationSegment);
        }

        recomputeState();
    }

    @Override
    public void pushConfigurationSegment(String name) {
        String primary = primary();
        ConfigurationSegment.ConfigurationKind kind = kind();
        String p = switch (kind) {
            case MAP -> path();
            case LIST ->
                throw new IllegalStateException("Illegal @EachProperty nesting encountered. Lists require numerical entries.");
            default ->
                throw new IllegalStateException("Illegal @EachProperty nesting, expecting a nested named not another configuration reader or name.");
        };
        String qualifiedName = computeName(name);
        list.add(new DefaultConfigurationSegment(
            configurationType(),
            name,
            p,
            ConfigurationSegment.ConfigurationKind.NAME,
            qualifiedName,
            name,
            primary,
            -1
        ));
        recomputeState();
    }

    @Override
    public void pushConfigurationSegment(int index) {
        ConfigurationSegment.ConfigurationKind kind = kind();
        String p = switch (kind) {
            case MAP ->
                throw new IllegalStateException("Illegal @EachProperty nesting encountered. Maps require key entries.");
            case LIST -> path();
            default ->
                throw new IllegalStateException("Illegal @EachProperty nesting, expecting a nested named not another configuration reader or name.");
        };
        String primary = primary();
        String strIndex = String.valueOf(index);
        String qualifiedName = computeName(strIndex);
        list.add(new DefaultConfigurationSegment(
            configurationType(),
            "["+ strIndex + "]",
            p,
            ConfigurationSegment.ConfigurationKind.INDEX,
            qualifiedName,
            strIndex,
            primary,
            index
        ));
        recomputeState();
    }

    private String computeName(String simpleName) {
        String qualifiedName = null;
        Iterator i = list.descendingIterator();
        while (i.hasNext()) {
            qualifiedName = i.next().name();
            if (qualifiedName != null) {
                break;
            }
        }
        if (qualifiedName != null) {
            qualifiedName = qualifiedName + "-" + simpleName;
        } else {
            qualifiedName = simpleName;
        }
        return qualifiedName;
    }

    private void recomputeState() {
        StringBuilder str = new StringBuilder();
        Iterator i = list.iterator();
        ConfigurationSegment previous = null;
        while (i.hasNext()) {
            ConfigurationSegment configurationSegment = i.next();
            if (configurationSegment.kind() == ConfigurationSegment.ConfigurationKind.INDEX) {
                str.append('[').append(configurationSegment.index()).append(']');
            } else {
                if (previous != null) {
                    str.append('.');
                }
                str.append(configurationSegment);
            }
            previous = configurationSegment;
        }
        computedPrefix = str.toString();
    }

    @Override
    public ConfigurationSegment removeLast() {
        try {
            return list.removeLast();
        } finally {
            recomputeState();
        }
    }

    @Override
    public String toString() {
        return computedPrefix;
    }

    @NotNull
    @Override
    public Iterator iterator() {
        return list.iterator();
    }

    record DefaultConfigurationSegment(
        Class type,
        String prefix,
        String path,
        ConfigurationKind kind,
        String name,
        String simpleName,
        String primary,
        int index) implements ConfigurationSegment {

        @Override
        public int length() {
            return prefix.length();
        }

        @Override
        public char charAt(int index) {
            return prefix.charAt(index);
        }

        @NotNull
        @Override
        public CharSequence subSequence(int start, int end) {
            return prefix.subSequence(start, end);
        }

        @Override
        public String toString() {
            return prefix;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy