001 package org.bukkit.configuration.file;
002
003 import java.io.File;
004 import java.io.FileNotFoundException;
005 import java.io.IOException;
006 import java.io.InputStream;
007 import java.io.Reader;
008 import java.util.Map;
009 import java.util.logging.Level;
010
011 import org.apache.commons.lang.Validate;
012 import org.bukkit.Bukkit;
013 import org.bukkit.configuration.Configuration;
014 import org.bukkit.configuration.ConfigurationSection;
015 import org.bukkit.configuration.InvalidConfigurationException;
016 import org.yaml.snakeyaml.DumperOptions;
017 import org.yaml.snakeyaml.Yaml;
018 import org.yaml.snakeyaml.error.YAMLException;
019 import org.yaml.snakeyaml.representer.Representer;
020
021 /**
022 * An implementation of {@link Configuration} which saves all files in Yaml.
023 * Note that this implementation is not synchronized.
024 */
025 public class YamlConfiguration extends FileConfiguration {
026 protected static final String COMMENT_PREFIX = "# ";
027 protected static final String BLANK_CONFIG = "{}\n";
028 private final DumperOptions yamlOptions = new DumperOptions();
029 private final Representer yamlRepresenter = new YamlRepresenter();
030 private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
031
032 @Override
033 public String saveToString() {
034 yamlOptions.setIndent(options().indent());
035 yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
036 yamlOptions.setAllowUnicode(SYSTEM_UTF);
037 yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
038
039 String header = buildHeader();
040 String dump = yaml.dump(getValues(false));
041
042 if (dump.equals(BLANK_CONFIG)) {
043 dump = "";
044 }
045
046 return header + dump;
047 }
048
049 @Override
050 public void loadFromString(String contents) throws InvalidConfigurationException {
051 Validate.notNull(contents, "Contents cannot be null");
052
053 Map<?, ?> input;
054 try {
055 input = (Map<?, ?>) yaml.load(contents);
056 } catch (YAMLException e) {
057 throw new InvalidConfigurationException(e);
058 } catch (ClassCastException e) {
059 throw new InvalidConfigurationException("Top level is not a Map.");
060 }
061
062 String header = parseHeader(contents);
063 if (header.length() > 0) {
064 options().header(header);
065 }
066
067 if (input != null) {
068 convertMapsToSections(input, this);
069 }
070 }
071
072 protected void convertMapsToSections(Map<?, ?> input, ConfigurationSection section) {
073 for (Map.Entry<?, ?> entry : input.entrySet()) {
074 String key = entry.getKey().toString();
075 Object value = entry.getValue();
076
077 if (value instanceof Map) {
078 convertMapsToSections((Map<?, ?>) value, section.createSection(key));
079 } else {
080 section.set(key, value);
081 }
082 }
083 }
084
085 protected String parseHeader(String input) {
086 String[] lines = input.split("\r?\n", -1);
087 StringBuilder result = new StringBuilder();
088 boolean readingHeader = true;
089 boolean foundHeader = false;
090
091 for (int i = 0; (i < lines.length) && (readingHeader); i++) {
092 String line = lines[i];
093
094 if (line.startsWith(COMMENT_PREFIX)) {
095 if (i > 0) {
096 result.append("\n");
097 }
098
099 if (line.length() > COMMENT_PREFIX.length()) {
100 result.append(line.substring(COMMENT_PREFIX.length()));
101 }
102
103 foundHeader = true;
104 } else if ((foundHeader) && (line.length() == 0)) {
105 result.append("\n");
106 } else if (foundHeader) {
107 readingHeader = false;
108 }
109 }
110
111 return result.toString();
112 }
113
114 @Override
115 protected String buildHeader() {
116 String header = options().header();
117
118 if (options().copyHeader()) {
119 Configuration def = getDefaults();
120
121 if ((def != null) && (def instanceof FileConfiguration)) {
122 FileConfiguration filedefaults = (FileConfiguration) def;
123 String defaultsHeader = filedefaults.buildHeader();
124
125 if ((defaultsHeader != null) && (defaultsHeader.length() > 0)) {
126 return defaultsHeader;
127 }
128 }
129 }
130
131 if (header == null) {
132 return "";
133 }
134
135 StringBuilder builder = new StringBuilder();
136 String[] lines = header.split("\r?\n", -1);
137 boolean startedHeader = false;
138
139 for (int i = lines.length - 1; i >= 0; i--) {
140 builder.insert(0, "\n");
141
142 if ((startedHeader) || (lines[i].length() != 0)) {
143 builder.insert(0, lines[i]);
144 builder.insert(0, COMMENT_PREFIX);
145 startedHeader = true;
146 }
147 }
148
149 return builder.toString();
150 }
151
152 @Override
153 public YamlConfigurationOptions options() {
154 if (options == null) {
155 options = new YamlConfigurationOptions(this);
156 }
157
158 return (YamlConfigurationOptions) options;
159 }
160
161 /**
162 * Creates a new {@link YamlConfiguration}, loading from the given file.
163 * <p>
164 * Any errors loading the Configuration will be logged and then ignored.
165 * If the specified input is not a valid config, a blank config will be
166 * returned.
167 * <p>
168 * The encoding used may follow the system dependent default.
169 *
170 * @param file Input file
171 * @return Resulting configuration
172 * @throws IllegalArgumentException Thrown if file is null
173 */
174 public static YamlConfiguration loadConfiguration(File file) {
175 Validate.notNull(file, "File cannot be null");
176
177 YamlConfiguration config = new YamlConfiguration();
178
179 try {
180 config.load(file);
181 } catch (FileNotFoundException ex) {
182 } catch (IOException ex) {
183 Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex);
184 } catch (InvalidConfigurationException ex) {
185 Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file , ex);
186 }
187
188 return config;
189 }
190
191 /**
192 * Creates a new {@link YamlConfiguration}, loading from the given stream.
193 * <p>
194 * Any errors loading the Configuration will be logged and then ignored.
195 * If the specified input is not a valid config, a blank config will be
196 * returned.
197 *
198 * @param stream Input stream
199 * @return Resulting configuration
200 * @throws IllegalArgumentException Thrown if stream is null
201 * @deprecated does not properly consider encoding
202 * @see #load(InputStream)
203 * @see #loadConfiguration(Reader)
204 */
205 @Deprecated
206 public static YamlConfiguration loadConfiguration(InputStream stream) {
207 Validate.notNull(stream, "Stream cannot be null");
208
209 YamlConfiguration config = new YamlConfiguration();
210
211 try {
212 config.load(stream);
213 } catch (IOException ex) {
214 Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex);
215 } catch (InvalidConfigurationException ex) {
216 Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex);
217 }
218
219 return config;
220 }
221
222
223 /**
224 * Creates a new {@link YamlConfiguration}, loading from the given reader.
225 * <p>
226 * Any errors loading the Configuration will be logged and then ignored.
227 * If the specified input is not a valid config, a blank config will be
228 * returned.
229 *
230 * @param reader input
231 * @return resulting configuration
232 * @throws IllegalArgumentException Thrown if stream is null
233 */
234 public static YamlConfiguration loadConfiguration(Reader reader) {
235 Validate.notNull(reader, "Stream cannot be null");
236
237 YamlConfiguration config = new YamlConfiguration();
238
239 try {
240 config.load(reader);
241 } catch (IOException ex) {
242 Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex);
243 } catch (InvalidConfigurationException ex) {
244 Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex);
245 }
246
247 return config;
248 }
249 }