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;
028import org.yaml.snakeyaml.Yaml;
029
030import java.io.BufferedReader;
031import java.io.Writer;
032import java.math.BigInteger;
033import java.sql.Timestamp;
034import java.util.Date;
035import java.util.Set;
036
037/**
038 * A loader for YAML-formatted configurations, using the SnakeYAML library for
039 * parsing and generation.
040 *
041 * @since 4.0.0
042 */
043public final class YamlConfigurationLoader extends AbstractConfigurationLoader<CommentedConfigurationNode> {
044
045    /**
046     * YAML native types from <a href="https://yaml.org/type/">YAML 1.1 Global tags</a>.
047     *
048     * <p>using SnakeYaml representation: https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-yaml-tags-and-java-types
049     */
050    private static final Set<Class<?>> NATIVE_TYPES = UnmodifiableCollections.toSet(
051            Boolean.class, Integer.class, Long.class, BigInteger.class, Double.class, // numeric
052            byte[].class, String.class, Date.class, java.sql.Date.class, Timestamp.class); // complex types
053
054    /**
055     * Creates a new {@link YamlConfigurationLoader} builder.
056     *
057     * @return a new builder
058     * @since 4.0.0
059     */
060    public static Builder builder() {
061        return new Builder();
062    }
063
064    /**
065     * Builds a {@link YamlConfigurationLoader}.
066     *
067     * @since 4.0.0
068     */
069    public static final class Builder extends AbstractConfigurationLoader.Builder<Builder, YamlConfigurationLoader> {
070        private final DumperOptions options = new DumperOptions();
071        private @Nullable NodeStyle style;
072
073        Builder() {
074            indent(4);
075            defaultOptions(o -> o.nativeTypes(NATIVE_TYPES));
076        }
077
078        /**
079         * Sets the level of indentation the resultant loader should use.
080         *
081         * @param indent the indent level
082         * @return this builder (for chaining)
083         * @since 4.0.0
084         */
085        public Builder indent(final int indent) {
086            this.options.setIndent(indent);
087            return this;
088        }
089
090        /**
091         * Gets the level of indentation to be used by the resultant loader.
092         *
093         * @return the indent level
094         * @since 4.0.0
095         */
096        public int indent() {
097            return this.options.getIndent();
098        }
099
100        /**
101         * Sets the node style the built loader should use.
102         *
103         * <dl><dt>Flow</dt>
104         * <dd>the compact, json-like representation.<br>
105         * Example: <code>
106         *     {value: [list, of, elements], another: value}
107         * </code></dd>
108         *
109         * <dt>Block</dt>
110         * <dd>expanded, traditional YAML<br>
111         * Example: <code>
112         *     value:
113         *     - list
114         *     - of
115         *     - elements
116         *     another: value
117         * </code></dd>
118         * </dl>
119         *
120         * <p>A {@code null} value will tell the loader to pick a value
121         * automatically based on the contents of each non-scalar node.</p>
122         *
123         * @param style the node style to use
124         * @return this builder (for chaining)
125         * @since 4.0.0
126         */
127        public Builder nodeStyle(final @Nullable NodeStyle style) {
128            this.style = style;
129            return this;
130        }
131
132        /**
133         * Gets the node style to be used by the resultant loader.
134         *
135         * @return the node style
136         * @since 4.0.0
137         */
138        public @Nullable NodeStyle nodeStyle() {
139            return this.style;
140        }
141
142        @Override
143        public YamlConfigurationLoader build() {
144            return new YamlConfigurationLoader(this);
145        }
146    }
147
148    private final ThreadLocal<Yaml> yaml;
149
150    private YamlConfigurationLoader(final Builder builder) {
151        super(builder, new CommentHandler[] {CommentHandlers.HASH});
152        final DumperOptions opts = builder.options;
153        opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style));
154        this.yaml = ThreadLocal.withInitial(() -> new ConfigurateYaml(opts));
155    }
156
157    @Override
158    protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) {
159        node.raw(this.yaml.get().load(reader));
160    }
161
162    @Override
163    protected void saveInternal(final ConfigurationNode node, final Writer writer) {
164        this.yaml.get().dump(node.raw(), writer);
165    }
166
167    @Override
168    public CommentedConfigurationNode createNode(final ConfigurationOptions options) {
169        return CommentedConfigurationNode.root(options);
170    }
171
172}