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

ninja.leaping.configurate.hocon.HoconConfigurationLoader Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
/**
 * Configurate
 * Copyright (C) zml and Configurate contributors
 *
 * 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
 *
 *    http://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 ninja.leaping.configurate.hocon;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharSink;
import com.google.common.io.CharSource;
import com.typesafe.config.*;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode;
import ninja.leaping.configurate.loader.AbstractConfigurationLoader;
import ninja.leaping.configurate.loader.CommentHandler;
import ninja.leaping.configurate.loader.CommentHandlers;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;


/**
 * A loader for HOCON (Hodor)-formatted configurations, using the typesafe config library for parsing
 */
public class HoconConfigurationLoader extends AbstractConfigurationLoader {
    public static final Pattern CRLF_MATCH = Pattern.compile("\r\n?");
    private final ConfigRenderOptions render;
    private final ConfigParseOptions parse;

    public static class Builder extends AbstractConfigurationLoader.Builder {
        private ConfigRenderOptions render = ConfigRenderOptions.defaults()
                .setOriginComments(false)
                .setJson(false);
        private ConfigParseOptions parse = ConfigParseOptions.defaults();

        protected Builder() {
        }

        public ConfigRenderOptions getRenderOptions() {
            return render;
        }

        public ConfigParseOptions getParseOptions() {
            return parse;
        }

        @Override
        public Builder setFile(File file) {
            super.setFile(file);
            return this;
        }

        @Override
        public Builder setURL(URL url) {
            super.setURL(url);
            return this;
        }

        public Builder setRenderOptions(ConfigRenderOptions options) {
            this.render = options;
            return this;
        }

        public Builder setParseOptions(ConfigParseOptions options) {
            this.parse = options;
            return this;
        }

        public Builder setSource(CharSource source) {
            super.setSource(source);
            return this;
        }

        public Builder setSink(CharSink sink) {
            super.setSink(sink);
            return this;
        }

        @Override
        public AbstractConfigurationLoader.Builder setPreservesHeader(boolean preservesHeader) {
            return super.setPreservesHeader(preservesHeader);
        }

        @Override
        public HoconConfigurationLoader build() {
            return new HoconConfigurationLoader(source, sink, render, parse, preserveHeader);
        }
    }

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

    protected HoconConfigurationLoader(CharSource source, CharSink sink, ConfigRenderOptions render,
                                       ConfigParseOptions parse, boolean preservesHeader) {
        super(source, sink, new CommentHandler[] {CommentHandlers.HASH, CommentHandlers.DOUBLE_SLASH}, preservesHeader);
        this.render = render;
        this.parse = parse;
    }

    @Override
    public void loadInternal(CommentedConfigurationNode node, BufferedReader reader) throws IOException {
        Config hoconConfig = ConfigFactory.parseReader(reader, parse);
        for (Map.Entry ent : hoconConfig.root().entrySet()) {
            readConfigValue(ent.getValue(), node.getNode(ent.getKey()));
        }
    }

    private void readConfigValue(ConfigValue value, CommentedConfigurationNode node) {
        if (!value.origin().comments().isEmpty()) {
            node.setComment(CRLF_MATCH.matcher(Joiner.on('\n').join(value.origin().comments())).replaceAll("\n"));
        }
        switch (value.valueType()) {
            case OBJECT:
                for (Map.Entry ent : ((ConfigObject) value).entrySet()) {
                    readConfigValue(ent.getValue(), node.getNode(ent.getKey()));
                }
                break;
            case LIST:
                List values = (ConfigList) value;
                for (int i = 0; i < values.size(); ++i) {
                    readConfigValue(values.get(i), node.getNode(i));
                }
                break;
            case NULL:
                return;
            default:
                node.setValue(value.unwrapped());
        }
    }

    @Override
    protected void saveInternal(ConfigurationNode node, Writer writer) throws IOException {
        if (!node.hasMapChildren()) {
            if (node.getValue() == null) {
                writer.write(LINE_SEPARATOR);
                return;
            } else {
                throw new IOException("HOCON cannot write nodes not in map format!");
            }
        }
        final ConfigValue value = ConfigValueFactory.fromAnyRef(node.getValue(), "configurate-hocon");
        traverseForComments(value, node);
        final String renderedValue = value.render(render);
        writer.write(renderedValue);
    }


    @Override
    public CommentedConfigurationNode createEmptyNode(ConfigurationOptions options) {
        return SimpleCommentedConfigurationNode.root(options);
    }

    private void traverseForComments(ConfigValue value, ConfigurationNode node) throws IOException {
        potentialComment(value, node);
        switch (value.valueType()) {
            case OBJECT:
                for (Map.Entry ent : node.getChildrenMap().entrySet()) {
                    ConfigValue child = ((ConfigObject) value).get(ent.getKey().toString());
                    if (child != null) { // Accept the fact that nodes may disappear after the initial config value
                    // is generated
                        traverseForComments(child, ent.getValue());
                    }
                }
                break;
            case LIST:
                List nodes = node.getChildrenList();
                for (int i = 0; i < nodes.size(); ++i) {
                    traverseForComments(((ConfigList) value).get(i), nodes.get(i));
                }
                break;
        }
    }

    // -- Comment handling -- this might have to be updated as the hocon dep changes (But tests should detect this
    // breakage
    private static final Class VALUE_CLASS;
    private static final Class ORIGIN_CLASS;
    private static final Field VALUE_ORIGIN;
    private static final Method ORIGIN_SET_COMMENTS;
    static {
        try {
            VALUE_CLASS = Class.forName("com.typesafe.config.impl.AbstractConfigValue").asSubclass(ConfigValue.class);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }

        ORIGIN_CLASS = ConfigValueFactory.fromAnyRef("a").origin().getClass();
        try {
            VALUE_ORIGIN = VALUE_CLASS.getDeclaredField("origin");
            ORIGIN_SET_COMMENTS = ORIGIN_CLASS.getDeclaredMethod("setComments", List.class);
            VALUE_ORIGIN.setAccessible(true);
            ORIGIN_SET_COMMENTS.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new ExceptionInInitializerError(e);
        } catch (NoSuchMethodException e) {
            throw new ExceptionInInitializerError(e);
        }

    }
    private ConfigValue potentialComment(ConfigValue value, ConfigurationNode node) throws IOException {
        if (!(node instanceof CommentedConfigurationNode)) {
            return value;
        }
        CommentedConfigurationNode commentedNode = (CommentedConfigurationNode) node;
        Optional comment = commentedNode.getComment();
        if (!comment.isPresent()) {
            return value;
        }
        try {
            Object o = ORIGIN_SET_COMMENTS.invoke(value.origin(), ImmutableList.copyOf(LINE_SPLITTER.split(comment.get())));
            VALUE_ORIGIN.set(value, o);
        } catch (IllegalAccessException e) {
            throw new IOException("Unable to set comments for config value" + value);
        } catch (InvocationTargetException e) {
            throw new IOException("Unable to set comments for config value" + value);
        }
        return value;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy