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