Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
at develop 224 lines 6.8 kB view raw
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}