Added new Configuration classes

By: Dinnerbone <dinnerbone@dinnerbone.com>
This commit is contained in:
Bukkit/Spigot
2011-09-19 20:36:44 +01:00
parent e99e39ee62
commit 6c7412d365
31 changed files with 3454 additions and 9 deletions

View File

@@ -0,0 +1,191 @@
package org.bukkit.configuration.file;
import com.google.common.io.Files;
import org.bukkit.configuration.InvalidConfigurationException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.MemoryConfiguration;
/**
* This is a base class for all File based implementations of {@link Configuration}
*/
public abstract class FileConfiguration extends MemoryConfiguration {
/**
* Creates an empty {@link FileConfiguration} with no default values.
*/
public FileConfiguration() {
super();
}
/**
* Creates an empty {@link FileConfiguration} using the specified {@link Configuration}
* as a source for all default values.
*
* @param defaults Default value provider
*/
public FileConfiguration(Configuration defaults) {
super(defaults);
}
/**
* Saves this {@link FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it will
* be overwritten. If it cannot be overwritten or created, an exception will be thrown.
*
* @param file File to save to.
* @throws IOException Thrown when the given file cannot be written to for any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(File file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
Files.createParentDirs(file);
String data = saveToString();
FileWriter writer = new FileWriter(file);
try {
writer.write(data);
} finally {
writer.close();
}
}
/**
* Saves this {@link FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it will
* be overwritten. If it cannot be overwritten or created, an exception will be thrown.
*
* @param file File to save to.
* @throws IOException Thrown when the given file cannot be written to for any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(String file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
save(new File(file));
}
/**
* Saves this {@link FileConfiguration} to a string, and returns it.
*
* @return String containing this configuration.
*/
public abstract String saveToString();
/**
* Loads this {@link FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed, leaving
* only settings and defaults, and the new values will be loaded from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be thrown.
*
* @param file File to load from.
* @throws FileNotFoundException Thrown when the given file cannot be opened.
* @throws IOException Thrown when the given file cannot be read.
* @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
load(new FileInputStream(file));
}
/**
* Loads this {@link FileConfiguration} from the specified stream.
* <p>
* All the values contained within this configuration will be removed, leaving
* only settings and defaults, and the new values will be loaded from the given stream.
*
* @param stream Stream to load from
* @throws IOException Thrown when the given file cannot be read.
* @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration.
* @throws IllegalArgumentException Thrown when stream is null.
*/
public void load(InputStream stream) throws IOException, InvalidConfigurationException {
if (stream == null) {
throw new IllegalArgumentException("Stream cannot be null");
}
InputStreamReader reader = new InputStreamReader(stream);
StringBuilder builder = new StringBuilder();
BufferedReader input = new BufferedReader(reader);
try {
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
builder.append('\n');
}
} finally {
input.close();
}
loadFromString(builder.toString());
}
/**
* Loads this {@link FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed, leaving
* only settings and defaults, and the new values will be loaded from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be thrown.
*
* @param file File to load from.
* @throws FileNotFoundException Thrown when the given file cannot be opened.
* @throws IOException Thrown when the given file cannot be read.
* @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(String file) throws FileNotFoundException, IOException, InvalidConfigurationException {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
load(new File(file));
}
/**
* Loads this {@link FileConfiguration} from the specified string, as opposed to from file.
* <p>
* All the values contained within this configuration will be removed, leaving
* only settings and defaults, and the new values will be loaded from the given string.
* <p>
* If the string is invalid in any way, an exception will be thrown.
*
* @param contents Contents of a Configuration to load.
* @throws InvalidConfigurationException Thrown if the specified string is invalid.
* @throws IllegalArgumentException Thrown if contents is null.
*/
public abstract void loadFromString(String contents) throws InvalidConfigurationException;
@Override
public FileConfigurationOptions options() {
if (options == null) {
options = new FileConfigurationOptions(this);
}
return (FileConfigurationOptions)options;
}
}

View File

@@ -0,0 +1,67 @@
package org.bukkit.configuration.file;
import org.bukkit.configuration.*;
/**
* Various settings for controlling the input and output of a {@link FileConfiguration}
*/
public class FileConfigurationOptions extends MemoryConfigurationOptions {
private String header = null;
protected FileConfigurationOptions(MemoryConfiguration configuration) {
super(configuration);
}
@Override
public FileConfiguration configuration() {
return (FileConfiguration)super.configuration();
}
@Override
public FileConfigurationOptions copyDefaults(boolean value) {
super.copyDefaults(value);
return this;
}
@Override
public FileConfigurationOptions pathSeparator(char value) {
super.pathSeparator(value);
return this;
}
/**
* Gets the header that will be applied to the top of the saved output.
* <p>
* This header will be commented out and applied directly at the top of the
* generated output of the {@link FileConfiguration}. It is not required to
* include a newline at the end of the header as it will automatically be applied,
* but you may include one if you wish for extra spacing.
* <p>
* Null is a valid value which will indicate that no header is to be applied.
* The default value is null.
*
* @return Header
*/
public String header() {
return header;
}
/**
* Sets the header that will be applied to the top of the saved output.
* <p>
* This header will be commented out and applied directly at the top of the
* generated output of the {@link FileConfiguration}. It is not required to
* include a newline at the end of the header as it will automatically be applied,
* but you may include one if you wish for extra spacing.
* <p>
* Null is a valid value which will indicate that no header is to be applied.
* The default value is null.
*
* @param value New header
* @return This object, for chaining
*/
public FileConfigurationOptions header(String value) {
this.header = value;
return this;
}
}

View File

@@ -0,0 +1,228 @@
package org.bukkit.configuration.file;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.representer.Representer;
/**
* An implementation of {@link Configuration} which saves all files in Yaml.
*/
public class YamlConfiguration extends FileConfiguration {
protected static final String COMMENT_PREFIX = "# ";
protected static final String BLANK_CONFIG = "{}\n";
private final DumperOptions yamlOptions = new DumperOptions();
private final Representer yamlRepresenter = new Representer();
private final Yaml yaml = new Yaml(new SafeConstructor(), yamlRepresenter, yamlOptions);
@Override
public String saveToString() {
Map<String, Object> output = new LinkedHashMap<String, Object>();
yamlOptions.setIndent(options().indent());
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
serializeValues(output, getValues(false));
String dump = yaml.dump(output);
if (dump.equals(BLANK_CONFIG)) {
dump = "";
}
return buildHeader() + dump;
}
@Override
public void loadFromString(String contents) throws InvalidConfigurationException {
if (contents == null) {
throw new IllegalArgumentException("Contents cannot be null");
}
Map<String, Object> input;
try {
input = (Map<String, Object>)yaml.load(contents);
} catch (Throwable ex) {
throw new InvalidConfigurationException("Specified contents is not a valid Configuration", ex);
}
deserializeValues(input, this);
}
protected void deserializeValues(Map<String, Object> input, ConfigurationSection section) throws InvalidConfigurationException {
if (input == null) {
return;
}
for (Map.Entry<String, Object> entry : input.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
Map<String, Object> subvalues;
try {
subvalues = (Map<String, Object>) value;
} catch (ClassCastException ex) {
throw new InvalidConfigurationException("Map found where type is not <String, Object>", ex);
}
if (subvalues.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
try {
ConfigurationSerializable serializable = ConfigurationSerialization.deserializeObject(subvalues);
section.set(entry.getKey(), serializable);
} catch (IllegalArgumentException ex) {
throw new InvalidConfigurationException("Could not deserialize object", ex);
}
} else {
ConfigurationSection subsection = section.createSection(entry.getKey());
deserializeValues(subvalues, subsection);
}
} else {
section.set(entry.getKey(), entry.getValue());
}
}
}
protected void serializeValues(Map<String, Object> output, Map<String, Object> input) {
if (input == null) {
return;
}
for (Map.Entry<String, Object> entry : input.entrySet()) {
Object value = entry.getValue();
if (value instanceof ConfigurationSection) {
ConfigurationSection subsection = (ConfigurationSection)entry.getValue();
Map<String, Object> subvalues = new LinkedHashMap<String, Object>();
serializeValues(subvalues, subsection.getValues(false));
value = subvalues;
} else if (value instanceof ConfigurationSerializable) {
ConfigurationSerializable serializable = (ConfigurationSerializable)value;
Map<String, Object> subvalues = new LinkedHashMap<String, Object>();
subvalues.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass()));
serializeValues(subvalues, serializable.serialize());
value = subvalues;
} else if ((!isPrimitiveWrapper(value)) && (!isNaturallyStorable(value))) {
throw new IllegalStateException("Configuration contains non-serializable values, cannot process");
}
if (value != null) {
output.put(entry.getKey(), value);
}
}
}
protected String buildHeader() {
String header = options().header();
if (header == null) {
return "";
}
StringBuilder builder = new StringBuilder();
String[] lines = header.split("\r?\n");
for (int i = 0; i < lines.length; i++) {
builder.append(COMMENT_PREFIX);
builder.append(lines[i]);
builder.append("\n");
}
return builder.toString();
}
@Override
public YamlConfigurationOptions options() {
if (options == null) {
options = new YamlConfigurationOptions(this);
}
return (YamlConfigurationOptions)options;
}
/**
* Creates a new {@link YamlConfiguration}, loading from the given file.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be returned.
*
* @param file Input file
* @return Resulting configuration
* @throws IllegalArgumentException Thrown is file is null
*/
public static YamlConfiguration loadConfiguration(File file) {
if (file == null) {
throw new IllegalArgumentException("File cannot be null");
}
YamlConfiguration config = new YamlConfiguration();
try {
config.load(file);
} catch (FileNotFoundException ex) {
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex);
} catch (InvalidConfigurationException ex) {
if (ex.getCause() instanceof YAMLException) {
Bukkit.getLogger().severe("Config file " + file + " isn't valid! " + ex.getCause());
} else if ((ex.getCause() == null) || (ex.getCause() instanceof ClassCastException)) {
Bukkit.getLogger().severe("Config file " + file + " isn't valid!");
} else {
Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file + ": " + ex.getCause().getClass(), ex);
}
}
return config;
}
/**
* Creates a new {@link YamlConfiguration}, loading from the given stream.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be returned.
*
* @param stream Input stream
* @return Resulting configuration
* @throws IllegalArgumentException Thrown is stream is null
*/
public static YamlConfiguration loadConfiguration(InputStream stream) {
if (stream == null) {
throw new IllegalArgumentException("Stream cannot be null");
}
YamlConfiguration config = new YamlConfiguration();
try {
config.load(stream);
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration", ex);
} catch (InvalidConfigurationException ex) {
if (ex.getCause() instanceof YAMLException) {
Bukkit.getLogger().severe("Config file isn't valid! " + ex.getCause());
} else if ((ex.getCause() == null) || (ex.getCause() instanceof ClassCastException)) {
Bukkit.getLogger().severe("Config file isn't valid!");
} else {
Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration: " + ex.getCause().getClass(), ex);
}
}
return config;
}
}

View File

@@ -0,0 +1,63 @@
package org.bukkit.configuration.file;
/**
* Various settings for controlling the input and output of a {@link YamlConfiguration}
*/
public class YamlConfigurationOptions extends FileConfigurationOptions {
private int indent = 2;
protected YamlConfigurationOptions(YamlConfiguration configuration) {
super(configuration);
}
@Override
public YamlConfiguration configuration() {
return (YamlConfiguration)super.configuration();
}
@Override
public YamlConfigurationOptions copyDefaults(boolean value) {
super.copyDefaults(value);
return this;
}
@Override
public YamlConfigurationOptions pathSeparator(char value) {
super.pathSeparator(value);
return this;
}
@Override
public YamlConfigurationOptions header(String value) {
super.header(value);
return this;
}
/**
* Gets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @return How much to indent by
*/
public int indent() {
return indent;
}
/**
* Sets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @param value New indent
* @return This object, for chaining
*/
public YamlConfigurationOptions indent(int value) {
if ((indent < 2) || (value > 9)) {
throw new IllegalArgumentException("Indent must be between 1 and 10 characters");
}
this.indent = value;
return this;
}
}