Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
1package org.bukkit.util.config;
2
3import org.yaml.snakeyaml.DumperOptions;
4import org.yaml.snakeyaml.Yaml;
5import org.yaml.snakeyaml.constructor.SafeConstructor;
6import org.yaml.snakeyaml.introspector.Property;
7import org.yaml.snakeyaml.nodes.*;
8import org.yaml.snakeyaml.reader.UnicodeReader;
9import org.yaml.snakeyaml.representer.Represent;
10import org.yaml.snakeyaml.representer.Representer;
11
12import java.io.*;
13import java.util.HashMap;
14import java.util.Map;
15
16/**
17 * YAML configuration loader. To use this class, construct it with path to
18 * a file and call its load() method. For specifying node paths in the
19 * various get*() methods, they support SK's path notation, allowing you to
20 * select child nodes by delimiting node names with periods.
21 *
22 * <p>
23 * For example, given the following configuration file:</p>
24 *
25 * <pre>members:
26 * - Hollie
27 * - Jason
28 * - Bobo
29 * - Aya
30 * - Tetsu
31 * worldguard:
32 * fire:
33 * spread: false
34 * blocks: [cloth, rock, glass]
35 * sturmeh:
36 * cool: false
37 * eats:
38 * babies: true</pre>
39 *
40 * <p>Calling code could access sturmeh's baby eating state by using
41 * <code>getBoolean("sturmeh.eats.babies", false)</code>. For lists, there are
42 * methods such as <code>getStringList</code> that will return a type safe list.
43 *
44 * <p>This class is currently incomplete. It is not yet possible to get a node.
45 * </p>
46 */
47public class Configuration extends ConfigurationNode {
48 private Yaml yaml;
49 private File file;
50 private String header = null;
51
52 public Configuration(File file) {
53 super(new HashMap<String, Object>());
54
55 DumperOptions options = new DumperOptions();
56
57 options.setIndent(4);
58 options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
59
60 yaml = new Yaml(new SafeConstructor(), new EmptyNullRepresenter(new DumperOptions()), options);
61
62 this.file = file;
63 }
64
65 /**
66 * Loads the configuration file. All errors are thrown away.
67 */
68 public void load() {
69 FileInputStream stream = null;
70
71 try {
72 stream = new FileInputStream(file);
73 read(yaml.load(new UnicodeReader(stream)));
74 } catch (IOException e) {
75 root = new HashMap<String, Object>();
76 } catch (ConfigurationException e) {
77 root = new HashMap<String, Object>();
78 } finally {
79 try {
80 if (stream != null) {
81 stream.close();
82 }
83 } catch (IOException e) {
84 }
85 }
86 }
87
88 /**
89 * Set the header for the file as a series of lines that are terminated
90 * by a new line sequence.
91 *
92 * @param headerLines header lines to prepend
93 */
94 public void setHeader(String... headerLines) {
95 StringBuilder header = new StringBuilder();
96
97 for (String line : headerLines) {
98 if (header.length() > 0) {
99 header.append("\r\n");
100 }
101 header.append(line);
102 }
103
104 setHeader(header.toString());
105 }
106
107 /**
108 * Set the header for the file. A header can be provided to prepend the
109 * YAML data output on configuration save. The header is
110 * printed raw and so must be manually commented if used. A new line will
111 * be appended after the header, however, if a header is provided.
112 *
113 * @param header header to prepend
114 */
115 public void setHeader(String header) {
116 this.header = header;
117 }
118
119 /**
120 * Return the set header.
121 *
122 * @return
123 */
124 public String getHeader() {
125 return header;
126 }
127
128 /**
129 * Saves the configuration to disk. All errors are clobbered.
130 *
131 * @param header header to prepend
132 * @return true if it was successful
133 */
134 public boolean save() {
135 FileOutputStream stream = null;
136
137 File parent = file.getParentFile();
138
139 if (parent != null) {
140 parent.mkdirs();
141 }
142
143 try {
144 stream = new FileOutputStream(file);
145 OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8");
146 if (header != null) {
147 writer.append(header);
148 writer.append("\r\n");
149 }
150 yaml.dump(root, writer);
151 return true;
152 } catch (IOException e) {
153 } finally {
154 try {
155 if (stream != null) {
156 stream.close();
157 }
158 } catch (IOException e) {
159 }
160 }
161
162 return false;
163 }
164
165 @SuppressWarnings("unchecked")
166 private void read(Object input) throws ConfigurationException {
167 try {
168 if (null == input) {
169 root = new HashMap<String, Object>();
170 } else {
171 root = (Map<String, Object>) input;
172 }
173 } catch (ClassCastException e) {
174 throw new ConfigurationException("Root document must be an key-value structure");
175 }
176 }
177
178 /**
179 * This method returns an empty ConfigurationNode for using as a
180 * default in methods that select a node from a node list.
181 *
182 * @return
183 */
184 public static ConfigurationNode getEmptyNode() {
185 return new ConfigurationNode(new HashMap<String, Object>());
186 }
187}
188
189class EmptyNullRepresenter extends Representer {
190
191 public EmptyNullRepresenter(DumperOptions options) {
192 this.nullRepresenter = new EmptyRepresentNull();
193 }
194
195 protected class EmptyRepresentNull implements Represent {
196 public Node representData(Object data) {
197 return representScalar(Tag.NULL, ""); // Changed "null" to "" so as to avoid writing nulls
198 }
199 }
200
201 // Code borrowed from snakeyaml (http://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue60/SkipBeanTest.java)
202 @Override
203 protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
204 NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
205 Node valueNode = tuple.getValueNode();
206 if (valueNode instanceof CollectionNode) {
207 // Removed null check
208 if (Tag.SEQ.equals(valueNode.getTag())) {
209 SequenceNode seq = (SequenceNode) valueNode;
210 if (seq.getValue().isEmpty()) {
211 return null; // skip empty lists
212 }
213 }
214 if (Tag.MAP.equals(valueNode.getTag())) {
215 MappingNode seq = (MappingNode) valueNode;
216 if (seq.getValue().isEmpty()) {
217 return null; // skip empty maps
218 }
219 }
220 }
221 return tuple;
222 }
223 // End of borrowed code
224}