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><prefix>.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}