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 net.kyori.option.Option;
020import net.kyori.option.OptionSchema;
021import org.checkerframework.checker.nullness.qual.Nullable;
022import org.spongepowered.configurate.CommentedConfigurationNode;
023import org.spongepowered.configurate.ConfigurationNode;
024import org.spongepowered.configurate.ConfigurationOptions;
025import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
026import org.spongepowered.configurate.loader.CommentHandler;
027import org.spongepowered.configurate.loader.CommentHandlers;
028import org.spongepowered.configurate.util.UnmodifiableCollections;
029import org.yaml.snakeyaml.DumperOptions;
030import org.yaml.snakeyaml.LoaderOptions;
031import org.yaml.snakeyaml.Yaml;
032import org.yaml.snakeyaml.constructor.Constructor;
033import org.yaml.snakeyaml.representer.Representer;
034
035import java.io.BufferedReader;
036import java.io.Writer;
037import java.math.BigInteger;
038import java.sql.Timestamp;
039import java.util.Date;
040import java.util.Set;
041
042/**
043 * A loader for YAML-formatted configurations, using the SnakeYAML library for
044 * parsing and generation.
045 *
046 * @since 4.0.0
047 */
048public final class YamlConfigurationLoader extends AbstractConfigurationLoader<CommentedConfigurationNode> {
049
050    /**
051     * YAML native types from <a href="https://yaml.org/type/">YAML 1.1 Global tags</a>.
052     *
053     * <p>using SnakeYaml representation: https://bitbucket.org/snakeyaml/snakeyaml/wiki/Documentation#markdown-header-yaml-tags-and-java-types
054     */
055    private static final Set<Class<?>> NATIVE_TYPES = UnmodifiableCollections.toSet(
056            Boolean.class, Integer.class, Long.class, BigInteger.class, Double.class, // numeric
057            byte[].class, String.class, Date.class, java.sql.Date.class, Timestamp.class); // complex types
058
059    /**
060     * Creates a new {@link YamlConfigurationLoader} builder.
061     *
062     * @return a new builder
063     * @since 4.0.0
064     */
065    public static Builder builder() {
066        return new Builder();
067    }
068
069    /**
070     * Builds a {@link YamlConfigurationLoader}.
071     *
072     * <p>This builder supports the following options:</p>
073     * <dl>
074     *     <dt>&lt;prefix&gt;.yaml.node-style</dt>
075     *     <dd>Equivalent to {@link #nodeStyle(NodeStyle)}</dd>
076     * </dl>
077     *
078     * @since 4.0.0
079     */
080    public static final class Builder extends AbstractConfigurationLoader.Builder<Builder, YamlConfigurationLoader> {
081
082        private static final OptionSchema.Mutable UNSAFE_SCHEMA = OptionSchema.childSchema(AbstractConfigurationLoader.Builder.SCHEMA);
083
084        /**
085         * A schema of options available to configure the YAML loader.
086         *
087         * @since 4.2.0
088         */
089        public static final OptionSchema SCHEMA = UNSAFE_SCHEMA.frozenView();
090
091        /**
092         * The collection node style to use globally when emitting with
093         * this loader.
094         *
095         * @see #nodeStyle(NodeStyle)
096         * @since 4.2.0
097         */
098        public static final Option<NodeStyle> NODE_STYLE = UNSAFE_SCHEMA.enumOption("yaml:node_style", NodeStyle.class, null);
099
100        /**
101         * The indent size (in spaces) to use for documents emitted by
102         * the created loader.
103         *
104         * @see #indent(int)
105         * @since 4.2.0
106         */
107        public static final Option<Integer> INDENT = UNSAFE_SCHEMA.intOption("yaml:indent", 4);
108
109        private final DumperOptions options = new DumperOptions();
110
111        Builder() {
112            this.defaultOptions(o -> o.nativeTypes(NATIVE_TYPES));
113        }
114
115        @Override
116        protected OptionSchema optionSchema() {
117            return SCHEMA;
118        }
119
120        /**
121         * Sets the level of indentation the resultant loader should use.
122         *
123         * @param indent the indent level
124         * @return this builder (for chaining)
125         * @since 4.0.0
126         */
127        public Builder indent(final int indent) {
128            this.optionStateBuilder().value(INDENT, indent);
129            return this;
130        }
131
132        /**
133         * Gets the level of indentation to be used by the resultant loader.
134         *
135         * @return the indent level
136         * @since 4.0.0
137         */
138        public int indent() {
139            return this.optionState().value(INDENT);
140        }
141
142        /**
143         * Sets the node style the built loader should use.
144         *
145         * <dl><dt>Flow</dt>
146         * <dd>the compact, json-like representation.<br>
147         * Example: <code>
148         *     {value: [list, of, elements], another: value}
149         * </code></dd>
150         *
151         * <dt>Block</dt>
152         * <dd>expanded, traditional YAML<br>
153         * Example: <code>
154         *     value:
155         *     - list
156         *     - of
157         *     - elements
158         *     another: value
159         * </code></dd>
160         * </dl>
161         *
162         * <p>A {@code null} value will tell the loader to pick a value
163         * automatically based on the contents of each non-scalar node.</p>
164         *
165         * @param style the node style to use
166         * @return this builder (for chaining)
167         * @since 4.0.0
168         */
169        public Builder nodeStyle(final @Nullable NodeStyle style) {
170            this.optionStateBuilder().value(NODE_STYLE, style);
171            return this;
172        }
173
174        /**
175         * Gets the node style to be used by the resultant loader.
176         *
177         * @return the node style
178         * @since 4.0.0
179         */
180        public @Nullable NodeStyle nodeStyle() {
181            return this.optionState().value(NODE_STYLE);
182        }
183
184        @Override
185        public YamlConfigurationLoader build() {
186            return new YamlConfigurationLoader(this);
187        }
188    }
189
190    private final ThreadLocal<Yaml> yaml;
191
192    private YamlConfigurationLoader(final Builder builder) {
193        super(builder, new CommentHandler[] {CommentHandlers.HASH});
194        final LoaderOptions loaderOpts = new LoaderOptions()
195            .setAcceptTabs(true)
196            .setProcessComments(false);
197        loaderOpts.setCodePointLimit(Integer.MAX_VALUE);
198
199        final DumperOptions opts = builder.options;
200        opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.optionState().value(Builder.NODE_STYLE)));
201        opts.setIndent(builder.optionState().value(Builder.INDENT));
202        this.yaml = ThreadLocal.withInitial(() -> new Yaml(new Constructor(loaderOpts), new Representer(opts), opts, loaderOpts));
203    }
204
205    @Override
206    protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) {
207        node.raw(this.yaml.get().load(reader));
208    }
209
210    @Override
211    protected void saveInternal(final ConfigurationNode node, final Writer writer) {
212        this.yaml.get().dump(node.raw(), writer);
213    }
214
215    @Override
216    public CommentedConfigurationNode createNode(final ConfigurationOptions options) {
217        return CommentedConfigurationNode.root(options);
218    }
219
220}