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    }