package org.bukkit.util.config; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.HashMap; import java.util.Map; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; import org.yaml.snakeyaml.introspector.Property; import org.yaml.snakeyaml.nodes.CollectionNode; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.reader.UnicodeReader; import org.yaml.snakeyaml.representer.Represent; import org.yaml.snakeyaml.representer.Representer; /** * YAML configuration loader. To use this class, construct it with path to * a file and call its load() method. For specifying node paths in the * various get*() methods, they support SK's path notation, allowing you to * select child nodes by delimiting node names with periods. * *

* For example, given the following configuration file:

* *
members:
 *     - Hollie
 *     - Jason
 *     - Bobo
 *     - Aya
 *     - Tetsu
 * worldguard:
 *     fire:
 *         spread: false
 *         blocks: [cloth, rock, glass]
 * sturmeh:
 *     cool: false
 *     eats:
 *         babies: true
* *

Calling code could access sturmeh's baby eating state by using * getBoolean("sturmeh.eats.babies", false). For lists, there are * methods such as getStringList that will return a type safe list. * *

This class is currently incomplete. It is not yet possible to get a node. *

* */ public class Configuration extends ConfigurationNode { private Yaml yaml; private File file; private String header = null; public Configuration(File file) { super(new HashMap()); DumperOptions options = new DumperOptions(); options.setIndent(4); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); yaml = new Yaml(new SafeConstructor(), new EmptyNullRepresenter(), options); this.file = file; } /** * Loads the configuration file. All errors are thrown away. */ public void load() { FileInputStream stream = null; try { stream = new FileInputStream(file); read(yaml.load(new UnicodeReader(stream))); } catch (IOException e) { root = new HashMap(); } catch (ConfigurationException e) { root = new HashMap(); } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) {} } } /** * Set the header for the file as a series of lines that are terminated * by a new line sequence. * * @param headerLines header lines to prepend */ public void setHeader(String... headerLines) { StringBuilder header = new StringBuilder(); for (String line : headerLines) { if (header.length() > 0) { header.append("\r\n"); } header.append(line); } setHeader(header.toString()); } /** * Set the header for the file. A header can be provided to prepend the * YAML data output on configuration save. The header is * printed raw and so must be manually commented if used. A new line will * be appended after the header, however, if a header is provided. * * @param header header to prepend */ public void setHeader(String header) { this.header = header; } /** * Return the set header. * * @return The header comment. */ public String getHeader() { return header; } /** * Saves the configuration to disk. All errors are clobbered. * * @return true if it was successful */ public boolean save() { FileOutputStream stream = null; File parent = file.getParentFile(); if (parent != null) { parent.mkdirs(); } try { stream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8"); if (header != null) { writer.append(header); writer.append("\r\n"); } yaml.dump(root, writer); return true; } catch (IOException e) {} finally { try { if (stream != null) { stream.close(); } } catch (IOException e) {} } return false; } @SuppressWarnings("unchecked") private void read(Object input) throws ConfigurationException { try { if (null == input) { root = new HashMap(); } else { root = (Map) input; } } catch (ClassCastException e) { throw new ConfigurationException("Root document must be an key-value structure"); } } /** * This method returns an empty ConfigurationNode for using as a * default in methods that select a node from a node list. * @return The empty node. */ public static ConfigurationNode getEmptyNode() { return new ConfigurationNode(new HashMap()); } } class EmptyNullRepresenter extends Representer { public EmptyNullRepresenter() { super(); this.nullRepresenter = new EmptyRepresentNull(); } protected class EmptyRepresentNull implements Represent { public Node representData(Object data) { return representScalar(Tag.NULL, ""); // Changed "null" to "" so as to avoid writing nulls } } // Code borrowed from snakeyaml (http://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue60/SkipBeanTest.java) @Override protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) { NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); Node valueNode = tuple.getValueNode(); if (valueNode instanceof CollectionNode) { // Removed null check if (Tag.SEQ.equals(valueNode.getTag())) { SequenceNode seq = (SequenceNode) valueNode; if (seq.getValue().isEmpty()) { return null; // skip empty lists } } if (Tag.MAP.equals(valueNode.getTag())) { MappingNode seq = (MappingNode) valueNode; if (seq.getValue().isEmpty()) { return null; // skip empty maps } } } return tuple; } // End of borrowed code }