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 }