001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.spongepowered.configurate.yaml;
018
019import org.checkerframework.checker.nullness.qual.Nullable;
020import org.spongepowered.configurate.CommentedConfigurationNode;
021import org.spongepowered.configurate.ConfigurationNode;
022import org.spongepowered.configurate.ConfigurationOptions;
023import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
024import org.spongepowered.configurate.loader.CommentHandler;
025import org.spongepowered.configurate.loader.CommentHandlers;
026import org.spongepowered.configurate.util.UnmodifiableCollections;
027import org.yaml.snakeyaml.DumperOptions;
028
029import java.io.BufferedReader;
030import java.io.Writer;
031import java.math.BigInteger;
032import java.sql.Timestamp;
033import java.util.Date;
034import java.util.Set;
035
036/**
037 * A loader for YAML-formatted configurations, using the SnakeYAML library for
038 * parsing and generation.
039 *
040 * @since 4.0.0
041 */
042public final class YamlConfigurationLoader extends AbstractConfigurationLoader<CommentedConfigurationNode> {
043
044    /**
045     * YAML native types from <a href="https://yaml.org/type/">YAML 1.1 Global tags</a>.
046     *
047     * <p>using SnakeYaml representation: https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-yaml-tags-and-java-types
048     */
049    private static final Set<Class<?>> NATIVE_TYPES = UnmodifiableCollections.toSet(
050            Boolean.class, Integer.class, Long.class, BigInteger.class, Double.class, // numeric
051            byte[].class, String.class, Date.class, java.sql.Date.class, Timestamp.class); // complex types
052
053    /**
054     * Creates a new {@link YamlConfigurationLoader} builder.
055     *
056     * @return a new builder
057     * @since 4.0.0
058     */
059    public static Builder builder() {
060        return new Builder();
061    }
062
063    /**
064     * Builds a {@link YamlConfigurationLoader}.
065     *
066     * @since 4.0.0
067     */
068    public static final class Builder extends AbstractConfigurationLoader.Builder<Builder, YamlConfigurationLoader> {
069        private final DumperOptions options = new DumperOptions();
070        private @Nullable NodeStyle style;
071
072        Builder() {
073            indent(4);
074            defaultOptions(o -> o.nativeTypes(NATIVE_TYPES));
075        }
076
077        /**
078         * Sets the level of indentation the resultant loader should use.
079         *
080         * @param indent the indent level
081         * @return this builder (for chaining)
082         * @since 4.0.0
083         */
084        public Builder indent(final int indent) {
085            this.options.setIndent(indent);
086            return this;
087        }
088
089        /**
090         * Gets the level of indentation to be used by the resultant loader.
091         *
092         * @return the indent level
093         * @since 4.0.0
094         */
095        public int indent() {
096            return this.options.getIndent();
097        }
098
099        /**
100         * Sets the node style the built loader should use.
101         *
102         * <dl><dt>Flow</dt>
103         * <dd>the compact, json-like representation.<br>
104         * Example: <code>
105         *     {value: [list, of, elements], another: value}
106         * </code></dd>
107         *
108         * <dt>Block</dt>
109         * <dd>expanded, traditional YAML<br>
110         * Example: <code>
111         *     value:
112         *     - list
113         *     - of
114         *     - elements
115         *     another: value
116         * </code></dd>
117         * </dl>
118         *
119         * <p>A {@code null} value will tell the loader to pick a value
120         * automatically based on the contents of each non-scalar node.</p>
121         *
122         * @param style the node style to use
123         * @return this builder (for chaining)
124         * @since 4.0.0
125         */
126        public Builder nodeStyle(final @Nullable NodeStyle style) {
127            this.style = style;
128            return this;
129        }
130
131        /**
132         * Gets the node style to be used by the resultant loader.
133         *
134         * @return the node style
135         * @since 4.0.0
136         */
137        public @Nullable NodeStyle nodeStyle() {
138            return this.style;
139        }
140
141        @Override
142        public YamlConfigurationLoader build() {
143            return new YamlConfigurationLoader(this);
144        }
145    }
146
147    private final ThreadLocal<ConfigurateYaml> yaml;
148
149    private YamlConfigurationLoader(final Builder builder) {
150        super(builder, new CommentHandler[] {CommentHandlers.HASH});
151        final DumperOptions opts = builder.options;
152        opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style));
153        this.yaml = ThreadLocal.withInitial(() -> new ConfigurateYaml(opts));
154    }
155
156    @Override
157    protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) {
158        node.raw(this.yaml.get().loadConfigurate(reader));
159    }
160
161    @Override
162    protected void saveInternal(final ConfigurationNode node, final Writer writer) {
163        this.yaml.get().dump(node.raw(), writer);
164    }
165
166    @Override
167    public CommentedConfigurationNode createNode(final ConfigurationOptions options) {
168        return CommentedConfigurationNode.root(options);
169    }
170
171}