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

com.tencent.polaris.configuration.client.util.YamlParser Maven / Gradle / Ivy

/*
 * Tencent is pleased to support the open source community by making Polaris available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * 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 com.tencent.polaris.configuration.client.util;

import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.logging.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.representer.Representer;

/**
 * @author lepdou 2022-03-02
 */
public class YamlParser {

    private static final Logger LOGGER = LoggerFactory.getLogger(YamlParser.class);

    /**
     * Transform yaml content to properties
     */
    public Properties yamlToProperties(String yamlContent) {
        Yaml yaml = createYaml();
        final Properties result = new Properties();
        process((properties, map) -> result.putAll(properties), yaml, yamlContent);
        return result;
    }

    /**
     * Create the {@link Yaml} instance to use.
     */
    private Yaml createYaml() {
        LoaderOptions loadingConfig = new LoaderOptions();
        loadingConfig.setAllowDuplicateKeys(false);
        return new Yaml(new SafeConstructor(new LoaderOptions()), new Representer(new DumperOptions()), new DumperOptions(), loadingConfig);
    }

    private boolean process(MatchCallback callback, Yaml yaml, String content) {
        int count = 0;
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("[Config] Loading from YAML: " + content);
        }
        for (Object object : yaml.loadAll(content)) {
            if (object != null && process(asMap(object), callback)) {
                count++;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("[Config] Loaded " + count + " document" + (count > 1 ? "s" : "") + " from YAML resource: "
                    + content);
        }
        return (count > 0);
    }

    @SuppressWarnings("unchecked")
    private Map asMap(Object object) {
        // YAML can have numbers as keys
        Map result = new LinkedHashMap<>();
        if (!(object instanceof Map)) {
            // A document can be a text literal
            result.put("document", object);
            return result;
        }

        Map map = (Map) object;
        for (Map.Entry entry : map.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof Map) {
                value = asMap(value);
            }
            Object key = entry.getKey();
            if (key instanceof CharSequence) {
                result.put(key.toString(), value);
            } else {
                // It has to be a map key in this case
                result.put("[" + key.toString() + "]", value);
            }
        }
        return result;
    }

    private boolean process(Map map, MatchCallback callback) {
        Properties properties = new Properties();
        properties.putAll(getFlattenedMap(map));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("[Config] Merging document (no matchers set): " + map);
        }
        callback.process(properties, map);
        return true;
    }

    private Map getFlattenedMap(Map source) {
        Map result = new LinkedHashMap<>();
        buildFlattenedMap(result, source, null);
        return result;
    }

    private void buildFlattenedMap(Map result, Map source, String path) {
        for (Map.Entry entry : source.entrySet()) {
            String key = entry.getKey();
            if (!StringUtils.isBlank(path)) {
                if (key.startsWith("[")) {
                    key = path + key;
                } else {
                    key = path + '.' + key;
                }
            }
            Object value = entry.getValue();
            if (value instanceof String) {
                result.put(key, value);
            } else if (value instanceof Map) {
                // Need a compound key
                @SuppressWarnings("unchecked")
                Map map = (Map) value;
                buildFlattenedMap(result, map, key);
            } else if (value instanceof Collection) {
                // Need a compound key
                @SuppressWarnings("unchecked")
                Collection collection = (Collection) value;
                int count = 0;
                for (Object object : collection) {
                    buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
                }
            } else {
                result.put(key, (value != null ? value.toString() : ""));
            }
        }
    }

    private interface MatchCallback {

        void process(Properties properties, Map map);
    }

}