summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pom.xml6
-rw-r--r--src/main/java/org/bukkit/OfflinePlayer.java3
-rw-r--r--src/main/java/org/bukkit/configuration/Configuration.java82
-rw-r--r--src/main/java/org/bukkit/configuration/ConfigurationOptions.java81
-rw-r--r--src/main/java/org/bukkit/configuration/ConfigurationSection.java560
-rw-r--r--src/main/java/org/bukkit/configuration/InvalidConfigurationException.java39
-rw-r--r--src/main/java/org/bukkit/configuration/MemoryConfiguration.java85
-rw-r--r--src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java27
-rw-r--r--src/main/java/org/bukkit/configuration/MemorySection.java667
-rw-r--r--src/main/java/org/bukkit/configuration/file/FileConfiguration.java191
-rw-r--r--src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java67
-rw-r--r--src/main/java/org/bukkit/configuration/file/YamlConfiguration.java228
-rw-r--r--src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java63
-rw-r--r--src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java24
-rw-r--r--src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java251
-rw-r--r--src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java21
-rw-r--r--src/main/java/org/bukkit/configuration/serialization/SerializableAs.java28
-rw-r--r--src/main/java/org/bukkit/inventory/ItemStack.java38
-rw-r--r--src/main/java/org/bukkit/plugin/Plugin.java25
-rw-r--r--src/main/java/org/bukkit/plugin/java/JavaPlugin.java45
-rw-r--r--src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java19
-rw-r--r--src/main/java/org/bukkit/util/BlockVector.java24
-rw-r--r--src/main/java/org/bukkit/util/Vector.java37
-rw-r--r--src/main/java/org/bukkit/util/config/Configuration.java7
-rw-r--r--src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java504
-rw-r--r--src/test/java/org/bukkit/configuration/ConfigurationTest.java135
-rw-r--r--src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java8
-rw-r--r--src/test/java/org/bukkit/configuration/MemorySectionTest.java8
-rw-r--r--src/test/java/org/bukkit/configuration/TestEnum.java1
-rw-r--r--src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java127
-rw-r--r--src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java62
31 files changed, 3454 insertions, 9 deletions
diff --git a/pom.xml b/pom.xml
index e1fbb2ad..26f4c38a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,5 +107,11 @@
<type>jar</type>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.9</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java
index 1a7599b4..f0d59c3a 100644
--- a/src/main/java/org/bukkit/OfflinePlayer.java
+++ b/src/main/java/org/bukkit/OfflinePlayer.java
@@ -1,9 +1,10 @@
package org.bukkit;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.AnimalTamer;
import org.bukkit.permissions.ServerOperator;
-public interface OfflinePlayer extends ServerOperator, AnimalTamer {
+public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable {
/**
* Checks if this player is currently online
*
diff --git a/src/main/java/org/bukkit/configuration/Configuration.java b/src/main/java/org/bukkit/configuration/Configuration.java
new file mode 100644
index 00000000..ed66d1b8
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/Configuration.java
@@ -0,0 +1,82 @@
+package org.bukkit.configuration;
+
+import java.util.Map;
+
+/**
+ * Represents a source of configurable options and settings
+ */
+public interface Configuration extends ConfigurationSection {
+ /**
+ * Sets the default value of the given path as provided.
+ * <p>
+ * If no source {@link Configuration} was provided as a default collection,
+ * then a new {@link MemoryConfiguration} will be created to hold the new default
+ * value.
+ * <p>
+ * If value is null, the value will be removed from the default Configuration source.
+ *
+ * @param path Path of the value to set.
+ * @param value Value to set the default to.
+ * @throws IllegalArgumentException Thrown if path is null.
+ */
+ public void addDefault(String path, Object value);
+
+ /**
+ * Sets the default values of the given paths as provided.
+ * <p>
+ * If no source {@link Configuration} was provided as a default collection,
+ * then a new {@link MemoryConfiguration} will be created to hold the new default
+ * values.
+ *
+ * @param defaults A map of Path->Values to add to defaults.
+ * @throws IllegalArgumentException Thrown if defaults is null.
+ */
+ public void addDefaults(Map<String, Object> defaults);
+
+ /**
+ * Sets the default values of the given paths as provided.
+ * <p>
+ * If no source {@link Configuration} was provided as a default collection,
+ * then a new {@link MemoryConfiguration} will be created to hold the new default
+ * value.
+ * <p>
+ * This method will not hold a reference to the specified Configuration, nor will it
+ * automatically update if that Configuration ever changes. If you require this,
+ * you should set the default source with {@link #setDefaults(org.bukkit.configuration.Configuration)}.
+ *
+ * @param defaults A configuration holding a list of defaults to copy.
+ * @throws IllegalArgumentException Thrown if defaults is null or this.
+ */
+ public void addDefaults(Configuration defaults);
+
+ /**
+ * Sets the source of all default values for this {@link Configuration}.
+ * <p>
+ * If a previous source was set, or previous default values were defined, then they will
+ * not be copied to the new source.
+ *
+ * @param defaults New source of default values for this configuration.
+ * @throws IllegalArgumentException Thrown if defaults is null or this.
+ */
+ public void setDefaults(Configuration defaults);
+
+ /**
+ * Gets the source {@link Configuration} for this configuration.
+ * <p>
+ * If no configuration source was set, but default values were added, then a
+ * {@link MemoryConfiguration} will be returned. If no source was set and no
+ * defaults were set, then this method will return null.
+ *
+ * @return Configuration source for default values, or null if none exist.
+ */
+ public Configuration getDefaults();
+
+ /**
+ * Gets the {@link ConfigurationOptions} for this {@link Configuration}.
+ * <p>
+ * All setters through this method are chainable.
+ *
+ * @return Options for this configuration
+ */
+ public ConfigurationOptions options();
+}
diff --git a/src/main/java/org/bukkit/configuration/ConfigurationOptions.java b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java
new file mode 100644
index 00000000..5392700f
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java
@@ -0,0 +1,81 @@
+package org.bukkit.configuration;
+
+/**
+ * Various settings for controlling the input and output of a {@link Configuration}
+ */
+public class ConfigurationOptions {
+ private char pathSeparator = '.';
+ private boolean copyDefaults = false;
+ private final Configuration configuration;
+
+ protected ConfigurationOptions(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ /**
+ * Returns the {@link Configuration} that this object is responsible for.
+ *
+ * @return Parent configuration
+ */
+ public Configuration configuration() {
+ return configuration;
+ }
+
+ /**
+ * Gets the char that will be used to separate {@link ConfigurationSection}s
+ * <p>
+ * This value does not affect how the {@link Configuration} is stored, only in
+ * how you access the data. The default value is '.'.
+ *
+ * @return Path separator
+ */
+ public char pathSeparator() {
+ return pathSeparator;
+ }
+
+ /**
+ * Sets the char that will be used to separate {@link ConfigurationSection}s
+ * <p>
+ * This value does not affect how the {@link Configuration} is stored, only in
+ * how you access the data. The default value is '.'.
+ *
+ * @param value Path separator
+ * @return This object, for chaining
+ */
+ public ConfigurationOptions pathSeparator(char value) {
+ this.pathSeparator = value;
+ return this;
+ }
+
+ /**
+ * Checks if the {@link Configuration} should copy values from its default {@link Configuration} directly.
+ * <p>
+ * If this is true, all values in the default Configuration will be directly copied,
+ * making it impossible to distinguish between values that were set and values that
+ * are provided by default. As a result, {@link ConfigurationSection#contains(java.lang.String)} will always
+ * return the same value as {@link ConfigurationSection#isSet(java.lang.String)}.
+ * The default value is false.
+ *
+ * @return Whether or not defaults are directly copied
+ */
+ public boolean copyDefaults() {
+ return copyDefaults;
+ }
+
+ /**
+ * Sets if the {@link Configuration} should copy values from its default {@link Configuration} directly.
+ * <p>
+ * If this is true, all values in the default Configuration will be directly copied,
+ * making it impossible to distinguish between values that were set and values that
+ * are provided by default. As a result, {@link ConfigurationSection#contains(java.lang.String)} will always
+ * return the same value as {@link ConfigurationSection#isSet(java.lang.String)}.
+ * The default value is false.
+ *
+ * @param value Whether or not defaults are directly copied
+ * @return This object, for chaining
+ */
+ public ConfigurationOptions copyDefaults(boolean value) {
+ this.copyDefaults = value;
+ return this;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/ConfigurationSection.java b/src/main/java/org/bukkit/configuration/ConfigurationSection.java
new file mode 100644
index 00000000..739a3359
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/ConfigurationSection.java
@@ -0,0 +1,560 @@
+package org.bukkit.configuration;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.util.Vector;
+import org.bukkit.inventory.ItemStack;
+
+/**
+ * Represents a section of a {@link Configuration}
+ */
+public interface ConfigurationSection {
+ /**
+ * Gets a set containing all keys in this section.
+ * <p>
+ * If deep is set to true, then this will contain all the keys within any child
+ * {@link ConfigurationSection}s (and their children, etc). These will be in a
+ * valid path notation for you to use.
+ * <p>
+ * If deep is set to false, then this will contain only the keys of any direct children,
+ * and not their own children.
+ *
+ * @param deep Whether or not to get a deep list, as opposed to a shallow list.
+ * @return Set of keys contained within this ConfigurationSection.
+ */
+ public Set<String> getKeys(boolean deep);
+
+ /**
+ * Gets a Map containing all keys and their values for this section.
+ * <p>
+ * If deep is set to true, then this will contain all the keys and values within
+ * any child {@link ConfigurationSection}s (and their children, etc). These
+ * keys will be in a valid path notation for you to use.
+ * <p>
+ * If deep is set to false, then this will contain only the keys and values of any
+ * direct children, and not their own children.
+ *
+ * @param deep Whether or not to get a deep list, as opposed to a shallow list.
+ * @return Map of keys and values of this section.
+ */
+ public Map<String, Object> getValues(boolean deep);
+
+ /**
+ * Checks if this {@link ConfigurationSection} contains the given path.
+ * <p>
+ * If the value for the requested path does not exist but a default value has
+ * been specified, this will return true.
+ *
+ * @param path Path to check for existence.
+ * @return True if this section contains the requested path, either via default or being set.
+ * @throws IllegalArgumentException Thrown when path is null.
+ */
+ public boolean contains(String path);
+
+ /**
+ * Checks if this {@link ConfigurationSection} has a value set for the given path.
+ * <p>
+ * If the value for the requested path does not exist but a default value has
+ * been specified, this will still return false.
+ *
+ * @param path Path to check for existence.
+ * @return True if this section contains the requested path, regardless of having a default.
+ * @throws IllegalArgumentException Thrown when path is null.
+ */
+ public boolean isSet(String path);
+
+ /**
+ * Gets the path of this {@link ConfigurationSection} from its root {@link Configuration}
+ * <p>
+ * For any {@link Configuration} themselves, this will return an empty string.
+ * <p>
+ * If the section is no longer contained within its root for any reason, such as
+ * being replaced with a different value, this may return null.
+ * <p>
+ * To retrieve the single name of this section, that is, the final part of the path
+ * returned by this method, you may use {@link #getName()}.
+ *
+ * @return Path of this section relative to its root
+ */
+ public String getCurrentPath();
+
+ /**
+ * Gets the name of this individual {@link ConfigurationSection}, in the path.
+ * <p>
+ * This will always be the final part of {@link #getCurrentPath()}, unless the
+ * section is orphaned.
+ *
+ * @return Name of this section
+ */
+ public String getName();
+
+ /**
+ * Gets the root {@link Configuration} that contains this {@link ConfigurationSection}
+ * <p>
+ * For any {@link Configuration} themselves, this will return its own object.
+ * <p>
+ * If the section is no longer contained within its root for any reason, such as
+ * being replaced with a different value, this may return null.
+ *
+ * @return Root configuration containing this section.
+ */
+ public Configuration getRoot();
+
+ /**
+ * Gets the parent {@link ConfigurationSection} that directly contains this
+ * {@link ConfigurationSection}.
+ * <p>
+ * For any {@link Configuration} themselves, this will return null.
+ * <p>
+ * If the section is no longer contained within its parent for any reason, such as
+ * being replaced with a different value, this may return null.
+ *
+ * @return Parent section containing this section.
+ */
+ public ConfigurationSection getParent();
+
+ /**
+ * Gets the requested Object by path.
+ * <p>
+ * If the Object does not exist but a default value has been specified, this
+ * will return the default value. If the Object does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the Object to get.
+ * @return Requested Object.
+ */
+ public Object get(String path);
+
+ /**
+ * Gets the requested Object by path, returning a default value if not found.
+ * <p>
+ * If the Object does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the Object to get.
+ * @return Requested Object.
+ */
+ public Object get(String path, Object def);
+
+ /**
+ * Sets the specified path to the given value.
+ * <p>
+ * If value is null, the entry will be removed. Any existing entry will be
+ * replaced, regardless of what the new value is.
+ * <p>
+ * Some implementations may have limitations on what you may store. See their
+ * individual javadocs for details. No implementations should allow you to store
+ * {@link Configuration}s or {@link ConfigurationSection}s, please use
+ * {@link #createSection(java.lang.String)} for that.
+ *
+ * @param path Path of the object to set.
+ * @param value New value to set the path to.
+ */
+ public void set(String path, Object value);
+
+ /**
+ * Creates an empty {@link ConfigurationSection} at the specified path.
+ * <p>
+ * Any value that was previously set at this path will be overwritten. If the
+ * previous value was itself a {@link ConfigurationSection}, it will be orphaned.
+ *
+ * @param path Path to create the section at.
+ * @return Newly created section
+ */
+ public ConfigurationSection createSection(String path);
+
+ // Primitives
+ /**
+ * Gets the requested String by path.
+ * <p>
+ * If the String does not exist but a default value has been specified, this
+ * will return the default value. If the String does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the String to get.
+ * @return Requested String.
+ */
+ public String getString(String path);
+
+ /**
+ * Gets the requested String by path, returning a default value if not found.
+ * <p>
+ * If the String does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the String to get.
+ * @return Requested String.
+ */
+ public String getString(String path, String def);
+
+ /**
+ * Checks if the specified path is a String.
+ * <p>
+ * If the path exists but is not a String, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a String and return
+ * appropriately.
+ *
+ * @param path Path of the String to check.
+ * @return Whether or not the specified path is a String.
+ */
+ public boolean isString(String path);
+
+
+ /**
+ * Gets the requested int by path.
+ * <p>
+ * If the int does not exist but a default value has been specified, this
+ * will return the default value. If the int does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the int to get.
+ * @return Requested int.
+ */
+ public int getInt(String path);
+
+ /**
+ * Gets the requested int by path, returning a default value if not found.
+ * <p>
+ * If the int does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the int to get.
+ * @return Requested int.
+ */
+ public int getInt(String path, int def);
+
+ /**
+ * Checks if the specified path is an int.
+ * <p>
+ * If the path exists but is not a int, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a int and return
+ * appropriately.
+ *
+ * @param path Path of the int to check.
+ * @return Whether or not the specified path is an int.
+ */
+ public boolean isInt(String path);
+
+
+ /**
+ * Gets the requested boolean by path.
+ * <p>
+ * If the boolean does not exist but a default value has been specified, this
+ * will return the default value. If the boolean does not exist and no default
+ * value was specified, this will return false.
+ *
+ * @param path Path of the boolean to get.
+ * @return Requested boolean.
+ */
+ public boolean getBoolean(String path);
+
+ /**
+ * Gets the requested boolean by path, returning a default value if not found.
+ * <p>
+ * If the boolean does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the boolean to get.
+ * @return Requested boolean.
+ */
+ public boolean getBoolean(String path, boolean def);
+
+ /**
+ * Checks if the specified path is a boolean.
+ * <p>
+ * If the path exists but is not a boolean, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a boolean and return
+ * appropriately.
+ *
+ * @param path Path of the boolean to check.
+ * @return Whether or not the specified path is a boolean.
+ */
+ public boolean isBoolean(String path);
+
+
+ /**
+ * Gets the requested double by path.
+ * <p>
+ * If the double does not exist but a default value has been specified, this
+ * will return the default value. If the double does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the double to get.
+ * @return Requested double.
+ */
+ public double getDouble(String path);
+
+ /**
+ * Gets the requested double by path, returning a default value if not found.
+ * <p>
+ * If the double does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the double to get.
+ * @return Requested double.
+ */
+ public double getDouble(String path, double def);
+
+ /**
+ * Checks if the specified path is a double.
+ * <p>
+ * If the path exists but is not a double, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a double and return
+ * appropriately.
+ *
+ * @param path Path of the double to check.
+ * @return Whether or not the specified path is a double.
+ */
+ public boolean isDouble(String path);
+
+
+ /**
+ * Gets the requested long by path.
+ * <p>
+ * If the long does not exist but a default value has been specified, this
+ * will return the default value. If the long does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the long to get.
+ * @return Requested long.
+ */
+ public long getLong(String path);
+
+ /**
+ * Gets the requested long by path, returning a default value if not found.
+ * <p>
+ * If the long does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the long to get.
+ * @return Requested long.
+ */
+ public long getLong(String path, long def);
+
+ /**
+ * Checks if the specified path is a long.
+ * <p>
+ * If the path exists but is not a long, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a long and return
+ * appropriately.
+ *
+ * @param path Path of the long to check.
+ * @return Whether or not the specified path is a long.
+ */
+ public boolean isLong(String path);
+
+
+
+ // Java
+ /**
+ * Gets the requested List by path.
+ * <p>
+ * If the List does not exist but a default value has been specified, this
+ * will return the default value. If the List does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the List to get.
+ * @return Requested List.
+ */
+ public List getList(String path);
+
+ /**
+ * Gets the requested List by path, returning a default value if not found.
+ * <p>
+ * If the List does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the List to get.
+ * @return Requested List.
+ */
+ public List getList(String path, List def);
+
+ /**
+ * Checks if the specified path is a List.
+ * <p>
+ * If the path exists but is not a List, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a List and return
+ * appropriately.
+ *
+ * @param path Path of the List to check.
+ * @return Whether or not the specified path is a List.
+ */
+ public boolean isList(String path);
+
+
+
+ // Bukkit
+ /**
+ * Gets the requested Vector by path.
+ * <p>
+ * If the Vector does not exist but a default value has been specified, this
+ * will return the default value. If the Vector does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the Vector to get.
+ * @return Requested Vector.
+ */
+ public Vector getVector(String path);
+
+ /**
+ * Gets the requested Vector by path, returning a default value if not found.
+ * <p>
+ * If the Vector does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the Vector to get.
+ * @return Requested Vector.
+ */
+ public Vector getVector(String path, Vector def);
+
+ /**
+ * Checks if the specified path is a Vector.
+ * <p>
+ * If the path exists but is not a Vector, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a Vector and return
+ * appropriately.
+ *
+ * @param path Path of the Vector to check.
+ * @return Whether or not the specified path is a Vector.
+ */
+ public boolean isVector(String path);
+
+
+ /**
+ * Gets the requested OfflinePlayer by path.
+ * <p>
+ * If the OfflinePlayer does not exist but a default value has been specified, this
+ * will return the default value. If the OfflinePlayer does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the OfflinePlayer to get.
+ * @return Requested OfflinePlayer.
+ */
+ public OfflinePlayer getOfflinePlayer(String path);
+
+ /**
+ * Gets the requested OfflinePlayer by path, returning a default value if not found.
+ * <p>
+ * If the OfflinePlayer does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the OfflinePlayer to get.
+ * @return Requested OfflinePlayer.
+ */
+ public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def);
+
+ /**
+ * Checks if the specified path is an OfflinePlayer.
+ * <p>
+ * If the path exists but is not a OfflinePlayer, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a OfflinePlayer and return
+ * appropriately.
+ *
+ * @param path Path of the OfflinePlayer to check.
+ * @return Whether or not the specified path is an OfflinePlayer.
+ */
+ public boolean isOfflinePlayer(String path);
+
+
+ /**
+ * Gets the requested ItemStack by path.
+ * <p>
+ * If the ItemStack does not exist but a default value has been specified, this
+ * will return the default value. If the ItemStack does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the ItemStack to get.
+ * @return Requested ItemStack.
+ */
+ public ItemStack getItemStack(String path);
+
+ /**
+ * Gets the requested ItemStack by path, returning a default value if not found.
+ * <p>
+ * If the ItemStack does not exist then the specified default value will returned
+ * regardless of if a default has been identified in the root {@link Configuration}.
+ *
+ * @param path Path of the ItemStack to get.
+ * @return Requested ItemStack.
+ */
+ public ItemStack getItemStack(String path, ItemStack def);
+
+ /**
+ * Checks if the specified path is an ItemStack.
+ * <p>
+ * If the path exists but is not a ItemStack, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a ItemStack and return
+ * appropriately.
+ *
+ * @param path Path of the ItemStack to check.
+ * @return Whether or not the specified path is an ItemStack.
+ */
+ public boolean isItemStack(String path);
+
+
+ /**
+ * Gets the requested ConfigurationSection by path.
+ * <p>
+ * If the ConfigurationSection does not exist but a default value has been specified, this
+ * will return the default value. If the ConfigurationSection does not exist and no default
+ * value was specified, this will return null.
+ *
+ * @param path Path of the ConfigurationSection to get.
+ * @return Requested ConfigurationSection.
+ */
+ public ConfigurationSection getConfigurationSection(String path);
+
+ /**
+ * Checks if the specified path is a ConfigurationSection.
+ * <p>
+ * If the path exists but is not a ConfigurationSection, this will return false. If the path does not
+ * exist, this will return false. If the path does not exist but a default value
+ * has been specified, this will check if that default value is a ConfigurationSection and return
+ * appropriately.
+ *
+ * @param path Path of the ConfigurationSection to check.
+ * @return Whether or not the specified path is a ConfigurationSection.
+ */
+ public boolean isConfigurationSection(String path);
+
+ /**
+ * Gets the equivalent {@link ConfigurationSection} from the default {@link Configuration} defined in {@link #getRoot()}.
+ * <p>
+ * If the root contains no defaults, or the defaults doesn't contain a value
+ * for this path, or the value at this path is not a {@link ConfigurationSection} then
+ * this will return null.
+ *
+ * @return Equivalent section in root configuration
+ */
+ public ConfigurationSection getDefaultSection();
+
+ /**
+ * Sets the default value in the root at the given path as provided.
+ * <p>
+ * If no source {@link Configuration} was provided as a default collection,
+ * then a new {@link MemoryConfiguration} will be created to hold the new default
+ * value.
+ * <p>
+ * If value is null, the value will be removed from the default Configuration source.
+ * <p>
+ * If the value as returned by {@link #getDefaultSection()} is null,
+ * then this will create a new section at the path, replacing anything that
+ * may have existed there previously.
+ *
+ * @param path Path of the value to set.
+ * @param value Value to set the default to.
+ * @throws IllegalArgumentException Thrown if path is null.
+ */
+ public void addDefault(String path, Object value);
+}
diff --git a/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java
new file mode 100644
index 00000000..3057712f
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java
@@ -0,0 +1,39 @@
+package org.bukkit.configuration;
+
+/**
+ * Exception thrown when attempting to load an invalid {@link Configuration}
+ */
+public class InvalidConfigurationException extends Exception {
+ /**
+ * Creates a new instance of InvalidConfigurationException without a message or cause.
+ */
+ public InvalidConfigurationException() {}
+
+ /**
+ * Constructs an instance of InvalidConfigurationException with the specified message.
+ *
+ * @param msg The details of the exception.
+ */
+ public InvalidConfigurationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of InvalidConfigurationException with the specified cause.
+ *
+ * @param cause The cause of the exception.
+ */
+ public InvalidConfigurationException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs an instance of InvalidConfigurationException with the specified message and cause.
+ *
+ * @param cause The cause of the exception.
+ * @param msg The details of the exception.
+ */
+ public InvalidConfigurationException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/MemoryConfiguration.java b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java
new file mode 100644
index 00000000..5fffc593
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java
@@ -0,0 +1,85 @@
+package org.bukkit.configuration;
+
+import java.util.Map;
+
+/**
+ * This is a {@link Configuration} implementation that does not save or load
+ * from any source, and stores all values in memory only.
+ * This is useful for temporary Configurations for providing defaults.
+ */
+public class MemoryConfiguration extends MemorySection implements Configuration {
+ protected Configuration defaults;
+ protected MemoryConfigurationOptions options;
+
+ /**
+ * Creates an empty {@link MemoryConfiguration} with no default values.
+ */
+ public MemoryConfiguration() {}
+
+ /**
+ * Creates an empty {@link MemoryConfiguration} using the specified {@link Configuration}
+ * as a source for all default values.
+ *
+ * @param defaults Default value provider
+ * @throws IllegalArgumentException Thrown if defaults is null
+ */
+ public MemoryConfiguration(Configuration defaults) {
+ this.defaults = defaults;
+ }
+
+ @Override
+ public void addDefault(String path, Object value) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path may not be null");
+ }
+
+ if (defaults == null) {
+ defaults = new MemoryConfiguration();
+ }
+
+ defaults.set(path, value);
+ }
+
+ public void addDefaults(Map<String, Object> defaults) {
+ if (defaults == null) {
+ throw new IllegalArgumentException("Defaults may not be null");
+ }
+
+ for (Map.Entry<String, Object> entry : defaults.entrySet()) {
+ addDefault(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public void addDefaults(Configuration defaults) {
+ if (defaults == null) {
+ throw new IllegalArgumentException("Defaults may not be null");
+ }
+
+ addDefaults(defaults.getValues(true));
+ }
+
+ public void setDefaults(Configuration defaults) {
+ if (defaults == null) {
+ throw new IllegalArgumentException("Defaults may not be null");
+ }
+
+ this.defaults = defaults;
+ }
+
+ public Configuration getDefaults() {
+ return defaults;
+ }
+
+ @Override
+ public ConfigurationSection getParent() {
+ return null;
+ }
+
+ public MemoryConfigurationOptions options() {
+ if (options == null) {
+ options = new MemoryConfigurationOptions(this);
+ }
+
+ return options;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java
new file mode 100644
index 00000000..93d3e50c
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java
@@ -0,0 +1,27 @@
+package org.bukkit.configuration;
+
+/**
+ * Various settings for controlling the input and output of a {@link MemoryConfiguration}
+ */
+public class MemoryConfigurationOptions extends ConfigurationOptions {
+ protected MemoryConfigurationOptions(MemoryConfiguration configuration) {
+ super(configuration);
+ }
+
+ @Override
+ public MemoryConfiguration configuration() {
+ return (MemoryConfiguration)super.configuration();
+ }
+
+ @Override
+ public MemoryConfigurationOptions copyDefaults(boolean value) {
+ super.copyDefaults(value);
+ return this;
+ }
+
+ @Override
+ public MemoryConfigurationOptions pathSeparator(char value) {
+ super.pathSeparator(value);
+ return this;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/MemorySection.java b/src/main/java/org/bukkit/configuration/MemorySection.java
new file mode 100644
index 00000000..4709e71b
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/MemorySection.java
@@ -0,0 +1,667 @@
+package org.bukkit.configuration;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.bukkit.Location;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.material.MaterialData;
+import org.bukkit.util.Vector;
+
+/**
+ * A type of {@link ConfigurationSection} that is stored in memory.
+ */
+public class MemorySection implements ConfigurationSection {
+ protected final Map<String, Object> map = new LinkedHashMap<String, Object>();
+ private final Configuration root;
+ private final ConfigurationSection parent;
+ private final String path;
+ private final String fullPath;
+
+ /**
+ * Creates an empty MemorySection for use as a root {@link Configuration} section.
+ * <p>
+ * Note that calling this without being yourself a {@link Configuration} will throw an
+ * exception!
+ *
+ * @throws IllegalStateException Thrown if this is not a {@link Configuration} root.
+ */
+ protected MemorySection() {
+ if (!(this instanceof Configuration)) {
+ throw new IllegalStateException("Cannot contruct a root MemorySection when not a Configuration");
+ }
+
+ this.path = "";
+ this.fullPath = "";
+ this.parent = null;
+ this.root = (Configuration)this;
+ }
+
+ /**
+ * Creates an empty MemorySection with the specified parent and path.
+ *
+ * @param parent Parent section that contains this own section.
+ * @param path Path that you may access this section from via the root {@link Configuration}.
+ * @throws IllegalArgumentException Thrown is parent or path is null, or if parent contains no root Configuration.
+ */
+ protected MemorySection(ConfigurationSection parent, String path) {
+ if (parent == null) {
+ throw new IllegalArgumentException("Parent cannot be null");
+ }
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ this.path = path;
+ this.parent = parent;
+ this.root = parent.getRoot();
+
+ if (root == null) {
+ throw new IllegalArgumentException("Path cannot be orphaned");
+ }
+
+ this.fullPath = createPath(parent, path);
+ }
+
+ public Set<String> getKeys(boolean deep) {
+ Set<String> result = new LinkedHashSet<String>();
+
+ if (getRoot().options().copyDefaults()) {
+ ConfigurationSection defaults = getDefaultSection();
+
+ if (defaults != null) {
+ result.addAll(defaults.getKeys(deep));
+ }
+ }
+
+ mapChildrenKeys(result, this, deep);
+
+ return result;
+ }
+
+ public Map<String, Object> getValues(boolean deep) {
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
+
+ if (getRoot().options().copyDefaults()) {
+ ConfigurationSection defaults = getDefaultSection();
+
+ if (defaults != null) {
+ result.putAll(defaults.getValues(deep));
+ }
+ }
+
+ mapChildrenValues(result, this, deep);
+
+ return result;
+ }
+
+ public boolean contains(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ return get(path) != null;
+ }
+
+ public boolean isSet(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ if (getRoot().options().copyDefaults()) {
+ return contains(path);
+ } else {
+ return get(path, null) != null;
+ }
+ }
+
+ public String getCurrentPath() {
+ return fullPath;
+ }
+
+ public String getName() {
+ return path;
+ }
+
+ public Configuration getRoot() {
+ return root;
+ }
+
+ public ConfigurationSection getParent() {
+ return parent;
+ }
+
+ public void addDefault(String path, Object value) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ if (root == null) {
+ throw new IllegalStateException("Cannot set default on orphaned section");
+ } else {
+ root.addDefault(createPath(this, path), value);
+ }
+ }
+
+ public ConfigurationSection getDefaultSection() {
+ if (getRoot() == null) {
+ return null;
+ }
+
+ Configuration defaults = getRoot().getDefaults();
+
+ if (defaults != null) {
+ if (defaults.isConfigurationSection(getCurrentPath())) {
+ return defaults.getConfigurationSection(getCurrentPath());
+ }
+ }
+
+ return null;
+ }
+
+ public void set(String path, Object value) {
+ String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator())));
+ ConfigurationSection section = this;
+
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ } else if (path.length() == 0) {
+ throw new IllegalArgumentException("Cannot set to an empty path");
+ }
+
+ for (int i = 0; i < split.length - 1; i++) {
+ ConfigurationSection last = section;
+
+ section = last.getConfigurationSection(split[i]);
+
+ if (section == null) {
+ section = last.createSection(split[i]);
+ }
+ }
+
+ String key = split[split.length - 1];
+
+ if (section == this) {
+ map.put(key, prepForStorage(value));
+ } else {
+ section.set(key, value);
+ }
+ }
+
+ public Object get(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ return get(path, getDefault(path));
+ }
+
+ public Object get(String path, Object def) {
+ Object result = null;
+ String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator())));
+ ConfigurationSection section = this;
+
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ } else if (path.length() == 0) {
+ return this;
+ }
+
+ for (int i = 0; (i < split.length - 1) && (section != null); i++) {
+ section = getConfigurationSection(split[i]);
+ }
+
+ String key = split[split.length - 1];
+
+ if (section == this) {
+ result = map.get(key);
+ } else if (section != null) {
+ result = section.get(key);
+ }
+
+ return (result == null) ? def : result;
+ }
+
+ public ConfigurationSection createSection(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ } else if (path.length() == 0) {
+ throw new IllegalArgumentException("Cannot create section at empty path");
+ }
+
+ String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator())));
+ ConfigurationSection section = this;
+
+ for (int i = 0; i < split.length - 1; i++) {
+ ConfigurationSection last = section;
+
+ section = getConfigurationSection(split[i]);
+
+ if (section == null) {
+ section = last.createSection(split[i]);
+ }
+ }
+
+ String key = split[split.length - 1];
+
+ if (section == this) {
+ ConfigurationSection result = new MemorySection(this, key);
+ map.put(key, result);
+ return result;
+ } else {
+ return section.createSection(key);
+ }
+ }
+
+ // Primitives
+ public String getString(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getString(path, (def instanceof String) ? (String)def : null);
+ }
+
+ public String getString(String path, String def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof String) ? (String)val : def;
+ }
+
+ public boolean isString(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof String;
+ }
+
+ public int getInt(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getInt(path, (def instanceof Integer) ? (Integer)def : 0);
+ }
+
+ public int getInt(String path, int def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof Integer) ? (Integer)val : def;
+ }
+
+ public boolean isInt(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof Integer;
+ }
+
+ public boolean getBoolean(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getBoolean(path, (def instanceof Boolean) ? (Boolean)def : false);
+ }
+
+ public boolean getBoolean(String path, boolean def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof Boolean) ? (Boolean)val : def;
+ }
+
+ public boolean isBoolean(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof Boolean;
+ }
+
+ public double getDouble(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getDouble(path, (def instanceof Double) ? (Double)def : 0);
+ }
+
+ public double getDouble(String path, double def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof Double) ? (Double)val : def;
+ }
+
+ public boolean isDouble(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof Double;
+ }
+
+ public long getLong(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getLong(path, (def instanceof Long) ? (Long)def : 0);
+ }
+
+ public long getLong(String path, long def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof Long) ? (Long)val : def;
+ }
+
+ public boolean isLong(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof Long;
+ }
+
+// Java
+ public List getList(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getList(path, (def instanceof List) ? (List)def : null);
+ }
+
+ public List getList(String path, List def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof List) ? (List)val : def;
+ }
+
+ public boolean isList(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof List;
+ }
+
+// Bukkit
+ public Vector getVector(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getVector(path, (def instanceof Vector) ? (Vector)def : null);
+ }
+
+ public Vector getVector(String path, Vector def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof Vector) ? (Vector)val : def;
+ }
+
+ public boolean isVector(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof Vector;
+ }
+
+ public OfflinePlayer getOfflinePlayer(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getOfflinePlayer(path, (def instanceof OfflinePlayer) ? (OfflinePlayer)def : null);
+ }
+
+ public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof OfflinePlayer) ? (OfflinePlayer)val : def;
+ }
+
+ public boolean isOfflinePlayer(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof OfflinePlayer;
+ }
+
+ public ItemStack getItemStack(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object def = getDefault(path);
+ return getItemStack(path, (def instanceof ItemStack) ? (ItemStack)def : null);
+ }
+
+ public ItemStack getItemStack(String path, ItemStack def) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, def);
+ return (val instanceof ItemStack) ? (ItemStack)val : def;
+ }
+
+ public boolean isItemStack(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof ItemStack;
+ }
+
+ public ConfigurationSection getConfigurationSection(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path, getDefault(path));
+ return (val instanceof ConfigurationSection) ? (ConfigurationSection)val : null;
+ }
+
+ public boolean isConfigurationSection(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Object val = get(path);
+ return val instanceof ConfigurationSection;
+ }
+
+ protected Object prepForStorage(Object input) {
+ if (input == null) {
+ throw new IllegalArgumentException("Cannot store null");
+ }
+
+ if (isPrimitiveWrapper(input) || isNaturallyStorable(input)) {
+ return input;
+ } else if (input instanceof ConfigurationSerializable) {
+ return input;
+ }
+
+ throw new IllegalArgumentException("Cannot store " + input + " into " + this + ", unsupported class");
+ }
+
+ protected boolean isPrimitiveWrapper(Object input) {
+ return input instanceof Integer || input instanceof Boolean ||
+ input instanceof Character || input instanceof Byte ||
+ input instanceof Short || input instanceof Double ||
+ input instanceof Long || input instanceof Float;
+ }
+
+ protected boolean isNaturallyStorable(Object input) {
+ return input instanceof List || input instanceof Iterable ||
+ input instanceof String || input instanceof File ||
+ input instanceof Enum;
+ }
+
+ protected Object getDefault(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("Path cannot be null");
+ }
+
+ Configuration defaults = root.getDefaults();
+ return (defaults == null) ? null : defaults.get(createPath(this, path));
+ }
+
+ protected void mapChildrenKeys(Set<String> output, ConfigurationSection section, boolean deep) {
+ if (section instanceof MemorySection) {
+ MemorySection sec = (MemorySection)section;
+
+ for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
+ output.add(createPath(section, entry.getKey(), this));
+
+ if ((deep) && (entry.getValue() instanceof ConfigurationSection)) {
+ ConfigurationSection subsection = (ConfigurationSection)entry.getValue();
+ mapChildrenKeys(output, subsection, deep);
+ }
+ }
+ } else {
+ Set<String> keys = section.getKeys(deep);
+
+ for (String key : keys) {
+ output.add(createPath(section, key, this));
+ }
+ }
+ }
+
+ protected void mapChildrenValues(Map<String, Object> output, ConfigurationSection section, boolean deep) {
+ if (section instanceof MemorySection) {
+ MemorySection sec = (MemorySection)section;
+
+ for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
+ output.put(createPath(section, entry.getKey(), this), entry.getValue());
+
+ if (entry.getValue() instanceof ConfigurationSection) {
+ if (deep) {
+ mapChildrenValues(output, (ConfigurationSection)entry.getValue(), deep);
+ }
+ }
+ }
+ } else {
+ Map<String, Object> values = section.getValues(deep);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ output.put(createPath(section, entry.getKey(), this), entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * Creates a full path to the given {@link ConfigurationSection} from its root {@link Configuration}.
+ * <p>
+ * You may use this method for any given {@link ConfigurationSection}, not only {@link MemorySection}.
+ *
+ * @param section Section to create a path for.
+ * @param key Name of the specified section.
+ * @return Full path of the section from its root.
+ */
+ public static String createPath(ConfigurationSection section, String key) {
+ return createPath(section, key, (section == null) ? null : section.getRoot());
+ }
+
+
+ /**
+ * Creates a relative path to the given {@link ConfigurationSection} from the given relative section.
+ * <p>
+ * You may use this method for any given {@link ConfigurationSection}, not only {@link MemorySection}.
+ *
+ * @param section Section to create a path for.
+ * @param key Name of the specified section.
+ * @param relativeTo Section to create the path relative to.
+ * @return Full path of the section from its root.
+ */
+ public static String createPath(ConfigurationSection section, String key, ConfigurationSection relativeTo) {
+ StringBuilder builder = new StringBuilder();
+
+ if (section != null) {
+ for (ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) {
+ if (builder.length() > 0) {
+ builder.insert(0, section.getRoot().options().pathSeparator());
+ }
+
+ builder.insert(0, parent.getName());
+ }
+ }
+
+ if ((key != null) && (key.length() > 0)) {
+ if (builder.length() > 0) {
+ builder.append(section.getRoot().options().pathSeparator());
+ }
+
+ builder.append(key);
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append(getClass().getSimpleName());
+ builder.append("[path='");
+ builder.append(getCurrentPath());
+ builder.append("', root='");
+ builder.append(root.getClass().getSimpleName());
+ builder.append("']");
+
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/file/FileConfiguration.java b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java
new file mode 100644
index 00000000..41ec8a0d
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java
new file mode 100644
index 00000000..80427204
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java
@@ -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;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java
new file mode 100644
index 00000000..2ce6fcba
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java
@@ -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;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java
new file mode 100644
index 00000000..eee75fb1
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java
@@ -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;
+ }
+}
diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java
new file mode 100644
index 00000000..6e2af873
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java
@@ -0,0 +1,24 @@
+package org.bukkit.configuration.serialization;
+
+import java.util.Map;
+
+/**
+ * Represents an object that may be serialized.
+ * <p>
+ * These objects MUST implement one of the following, in addition to the methods
+ * as defined by this interface:
+ * - A static method "deserialize" that accepts a single {@link Map<String, Object>} and returns the class.
+ * - A static method "valueOf" that accepts a single {@link Map<String, Object>} and returns the class.
+ * - A constructor that accepts a single {@link Map<String, Object>}.
+ */
+public interface ConfigurationSerializable {
+ /**
+ * Creates a Map representation of this class.
+ * <p>
+ * This class must provide a method to restore this class, as defined in the
+ * {@link ConfigurationSerializable} interface javadocs.
+ *
+ * @return Map containing the current state of this class
+ */
+ public Map<String, Object> serialize();
+}
diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java
new file mode 100644
index 00000000..ce594136
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java
@@ -0,0 +1,251 @@
+package org.bukkit.configuration.serialization;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.util.BlockVector;
+import org.bukkit.util.Vector;
+
+/**
+ * Utility class for storing and retrieving classes for {@link Configuration}.
+ */
+public class ConfigurationSerialization {
+ public static final String SERIALIZED_TYPE_KEY = "==";
+ private final Class<? extends ConfigurationSerializable> clazz;
+ private static Map<String, Class<? extends ConfigurationSerializable>> aliases = new HashMap<String, Class<? extends ConfigurationSerializable>>();
+
+ static {
+ registerClass(Vector.class);
+ registerClass(BlockVector.class);
+ }
+
+ protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) {
+ this.clazz = clazz;
+ }
+
+ protected Method getMethod(String name, boolean isStatic) {
+ try {
+ Method method = clazz.getDeclaredMethod(name, Map.class);
+
+ if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) {
+ return null;
+ }
+ if (Modifier.isStatic(method.getModifiers()) != isStatic) {
+ return null;
+ }
+
+ return method;
+ } catch (NoSuchMethodException ex) {
+ return null;
+ } catch (SecurityException ex) {
+ return null;
+ }
+ }
+
+ protected Constructor<? extends ConfigurationSerializable> getConstructor() {
+ try {
+ return clazz.getConstructor(Map.class);
+ } catch (NoSuchMethodException ex) {
+ return null;
+ } catch (SecurityException ex) {
+ return null;
+ }
+ }
+
+ protected ConfigurationSerializable deserializeViaMethod(Method method, Map<String, Object> args) {
+ try {
+ ConfigurationSerializable result = (ConfigurationSerializable)method.invoke(null, args);
+
+ if (result == null) {
+ Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null");
+ } else {
+ return result;
+ }
+ } catch (Throwable ex) {
+ Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization", ex);
+ }
+
+ return null;
+ }
+
+ protected ConfigurationSerializable deserializeViaCtor(Constructor<? extends ConfigurationSerializable> ctor, Map<String, Object> args) {
+ try {
+ return ctor.newInstance(args);
+ } catch (Throwable ex) {
+ Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization", ex);
+ }
+
+ return null;
+ }
+
+ public ConfigurationSerializable deserialize(Map<String, Object> args) {
+ if (args == null) {
+ throw new IllegalArgumentException("Args must not be null");
+ }
+
+ ConfigurationSerializable result = null;
+ Method method = null;
+
+ if (result == null) {
+ method = getMethod("deserialize", true);
+
+ if (method != null) {
+ result = deserializeViaMethod(method, args);
+ }
+ }
+
+ if (result == null) {
+ method = getMethod("valueOf", true);
+
+ if (method != null) {
+ result = deserializeViaMethod(method, args);
+ }
+ }
+
+ if (result == null) {
+ Constructor<? extends ConfigurationSerializable> constructor = getConstructor();
+
+ if (constructor != null) {
+ result = deserializeViaCtor(constructor, args);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Attempts to deserialize the given arguments into a new instance of the given class.
+ * <p>
+ * The class must implement {@link ConfigurationSerializable}, including the extra methods
+ * as specified in the javadoc of ConfigurationSerializable.
+ * <p>
+ * If a new instance could not be made, an example being the class not fully implementing
+ * the interface, null will be returned.
+ *
+ * @param args Arguments for deserialization
+ * @param clazz Class to deserialize into
+ * @return New instance of the specified class
+ */
+ public static ConfigurationSerializable deserializeObject(Map<String, Object> args, Class<? extends ConfigurationSerializable> clazz) {
+ return new ConfigurationSerialization(clazz).deserialize(args);
+ }
+
+ /**
+ * Attempts to deserialize the given arguments into a new instance of the given class.
+ * <p>
+ * The class must implement {@link ConfigurationSerializable}, including the extra methods
+ * as specified in the javadoc of ConfigurationSerializable.
+ * <p>
+ * If a new instance could not be made, an example being the class not fully implementing
+ * the interface, null will be returned.
+ *
+ * @param args Arguments for deserialization
+ * @return New instance of the specified class
+ */
+ public static ConfigurationSerializable deserializeObject(Map<String, Object> args) {
+ Class<? extends ConfigurationSerializable> clazz = null;
+
+ if (args.containsKey(SERIALIZED_TYPE_KEY)) {
+ try {
+ String alias = (String)args.get(SERIALIZED_TYPE_KEY);
+
+ if (alias == null) {
+ throw new IllegalArgumentException("Specified class does not exist ('" + alias + ")'");
+ } else {
+ clazz = getClassByAlias(alias);
+ }
+ } catch (ClassCastException ex) {
+ ex.fillInStackTrace();
+ throw ex;
+ }
+ } else {
+ throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')");
+ }
+
+ return new ConfigurationSerialization(clazz).deserialize(args);
+ }
+
+ /**
+ * Registers the given {@link ConfigurationSerializable} class by its alias
+ *
+ * @param clazz Class to register
+ */
+ public static void registerClass(Class<? extends ConfigurationSerializable> clazz) {
+ DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class);
+
+ if (delegate == null ) {
+ registerClass(clazz, getAlias(clazz));
+ registerClass(clazz, clazz.getName());
+ }
+ }
+
+ /**
+ * Registers the given alias to the specified {@link ConfigurationSerializable} class
+ *
+ * @param clazz Class to register
+ * @param alias Alias to register as
+ */
+ public static void registerClass(Class<? extends ConfigurationSerializable> clazz, String alias) {
+ aliases.put(alias, clazz);
+ }
+
+ /**
+ * Unregisters the specified alias to a {@link ConfigurationSerializable}
+ *
+ * @param alias Alias to unregister
+ */
+ public static void unregisterClass(String alias) {
+ aliases.remove(alias);
+ }
+
+ /**
+ * Unregisters any aliases for the specified {@link ConfigurationSerializable} class
+ *
+ * @param clazz Class to unregister
+ */
+ public static void unregisterClass(Class<? extends ConfigurationSerializable> clazz) {
+ while (aliases.values().remove(clazz));
+ }
+
+ /**
+ * Attempts to get a registered {@link ConfigurationSerializable} class by its alias
+ *
+ * @param alias Alias of the serializable
+ * @return Registered class, or null if not found
+ */
+ public static Class<? extends ConfigurationSerializable> getClassByAlias(String alias) {
+ return aliases.get(alias);
+ }
+
+ /**
+ * Gets the correct alias for the given {@link ConfigurationSerializable} class
+ *
+ * @param clazz Class to get alias for
+ * @return Alias to use for the class
+ */
+ public static String getAlias(Class<? extends ConfigurationSerializable> clazz) {
+ DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class);
+
+ if (delegate != null) {
+ if ((delegate.value() == null) || (delegate.value() == clazz)) {
+ delegate = null;
+ } else {
+ return getAlias(delegate.value());
+ }
+ }
+
+ if (delegate == null) {
+ SerializableAs alias = clazz.getAnnotation(SerializableAs.class);
+
+ if ((alias != null) && (alias.value() != null)) {
+ return alias.value();
+ }
+ }
+
+ return clazz.getName();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java
new file mode 100644
index 00000000..9e38f1a0
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java
@@ -0,0 +1,21 @@
+package org.bukkit.configuration.serialization;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Represents a {@link ConfigurationSerializable} that will delegate all deserialization to another
+ * {@link ConfigurationSerializable}
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DelegateDeserialization {
+ /**
+ * Which class should be used as a delegate for this classes deserialization
+ *
+ * @return Delegate class
+ */
+ public Class<? extends ConfigurationSerializable> value();
+}
diff --git a/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java
new file mode 100644
index 00000000..adfc0aac
--- /dev/null
+++ b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java
@@ -0,0 +1,28 @@
+package org.bukkit.configuration.serialization;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Represents an "alias" that a {@link ConfigurationSerializable} may be stored as.
+ * If this is not present on a {@link ConfigurationSerializable} class, it will use the
+ * fully qualified name of the class.
+ * <p>
+ * Using this annotation on any other class than a {@link ConfigurationSerializable} will
+ * have no effect.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SerializableAs {
+ /**
+ * This is the name your class will be stored and retrieved as.
+ * <p>
+ * This name MUST be unique. We recommend using names such as "MyPluginThing" instead of
+ * "Thing".
+ *
+ * @return Name to serialize the class as.
+ */
+ public String value();
+}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 61d43c18..b948ccdb 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -1,12 +1,16 @@
package org.bukkit.inventory;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.bukkit.Material;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.material.MaterialData;
/**
* Represents a stack of items
*/
-public class ItemStack {
+public class ItemStack implements Serializable, ConfigurationSerializable {
private int type;
private int amount = 0;
private MaterialData data = null;
@@ -208,4 +212,36 @@ public class ItemStack {
hash = hash * 7 + 23 * getAmount(); // too bad these are mutable values... Q_Q
return hash;
}
+
+ public Map<String, Object> serialize() {
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
+
+ result.put("type", getType());
+
+ if (durability != 0) {
+ result.put("damage", durability);
+ }
+
+ if (amount != 1) {
+ result.put("amount", amount);
+ }
+
+ return result;
+ }
+
+ public static ItemStack deserialize(Map<String, Object> args) {
+ Material type = Material.getMaterial((String)args.get("type"));
+ short damage = 0;
+ int amount = 1;
+
+ if (args.containsKey("damage")) {
+ damage = (Short)args.get("damage");
+ }
+
+ if (args.containsKey("amount")) {
+ amount = (Integer)args.get("amount");
+ }
+
+ return new ItemStack(type, amount, damage);
+ }
}
diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java
index 69ced916..37244293 100644
--- a/src/main/java/org/bukkit/plugin/Plugin.java
+++ b/src/main/java/org/bukkit/plugin/Plugin.java
@@ -2,8 +2,10 @@ package org.bukkit.plugin;
import com.avaje.ebean.EbeanServer;
import java.io.File;
+import java.io.InputStream;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
+import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.util.config.Configuration;
@@ -33,6 +35,29 @@ public interface Plugin extends CommandExecutor {
* @return The configuration
*/
public Configuration getConfiguration();
+
+ /**
+ * Gets a {@link FileConfiguration} for this plugin, read through "config.yml"
+ * <p>
+ * If there is a default config.yml embedded in this plugin, it will be provided
+ * as a default for this Configuration.
+ *
+ * @return Plugin configuration
+ */
+ public FileConfiguration getConfig();
+
+ /**
+ * Gets an embedded resource in this plugin
+ *
+ * @param filename Filename of the resource
+ * @return File if found, otherwise null
+ */
+ public InputStream getResource(String filename);
+
+ /**
+ * Saves the {@link FileConfiguration} retrievable by {@link #getConfig()}.
+ */
+ public void saveConfig();
/**
* Gets the associated PluginLoader responsible for this plugin
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
index 93dc08cc..2645b83b 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
@@ -7,17 +7,27 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.ddl.DdlGenerator;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.util.config.Configuration;
+import org.yaml.snakeyaml.error.YAMLException;
/**
* Represents a Java plugin
@@ -34,6 +44,8 @@ public abstract class JavaPlugin implements Plugin {
private Configuration config = null;
private boolean naggable = true;
private EbeanServer ebean = null;
+ private FileConfiguration newConfig = null;
+ private File configFile = null;
public JavaPlugin() {}
@@ -99,10 +111,32 @@ public abstract class JavaPlugin implements Plugin {
* the configuration file will have no values.
*
* @return The configuration.
+ * @deprecated See the new
*/
+ @Deprecated
public Configuration getConfiguration() {
return config;
}
+
+ public FileConfiguration getConfig() {
+ return newConfig;
+ }
+
+ public void saveConfig() {
+ try {
+ newConfig.save(configFile);
+ } catch (IOException ex) {
+ Logger.getLogger(JavaPlugin.class.getName()).log(Level.SEVERE, "Could not save config to " + configFile, ex);
+ }
+ }
+
+ public InputStream getResource(String filename) {
+ if (filename == null) {
+ throw new IllegalArgumentException("Filename cannot be null");
+ }
+
+ return getClassLoader().getResourceAsStream(filename);
+ }
/**
* Returns the ClassLoader which holds this plugin
@@ -153,8 +187,17 @@ public abstract class JavaPlugin implements Plugin {
this.description = description;
this.dataFolder = dataFolder;
this.classLoader = classLoader;
- this.config = new Configuration(new File(dataFolder, "config.yml"));
+ this.configFile = new File(dataFolder, "config.yml");
+ this.config = new Configuration(configFile);
this.config.load();
+ this.newConfig = YamlConfiguration.loadConfiguration(configFile);
+
+ InputStream defConfigStream = getResource("config.yml");
+ if (defConfigStream != null) {
+ YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
+
+ newConfig.setDefaults(defConfig);
+ }
if (description.isDatabaseEnabled()) {
ServerConfig db = new ServerConfig();
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
index ad69872e..bf9379bd 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
@@ -15,6 +15,9 @@ import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.bukkit.Server;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+import org.bukkit.configuration.serialization.SerializableAs;
import org.bukkit.event.CustomEventListener;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
@@ -229,6 +232,20 @@ public class JavaPluginLoader implements PluginLoader {
public void setClass(final String name, final Class<?> clazz) {
if (!classes.containsKey(name)) {
classes.put(name, clazz);
+
+ if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
+ Class<? extends ConfigurationSerializable> serializable = (Class<? extends ConfigurationSerializable>)clazz;
+ ConfigurationSerialization.registerClass(serializable);
+ }
+ }
+ }
+
+ public void removeClass(String name) {
+ Class<?> clazz = classes.remove(name);
+
+ if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
+ Class<? extends ConfigurationSerializable> serializable = (Class<? extends ConfigurationSerializable>)clazz;
+ ConfigurationSerialization.unregisterClass(serializable);
}
}
@@ -973,7 +990,7 @@ public class JavaPluginLoader implements PluginLoader {
Set<String> names = loader.getClasses();
for (String name : names) {
- classes.remove(name);
+ removeClass(name);
}
}
}
diff --git a/src/main/java/org/bukkit/util/BlockVector.java b/src/main/java/org/bukkit/util/BlockVector.java
index 61932922..06d0b9cf 100644
--- a/src/main/java/org/bukkit/util/BlockVector.java
+++ b/src/main/java/org/bukkit/util/BlockVector.java
@@ -1,13 +1,15 @@
package org.bukkit.util;
+import java.util.Map;
+import org.bukkit.configuration.serialization.SerializableAs;
+
/**
* A vector with a hash function that floors the X, Y, Z components, a la
* BlockVector in WorldEdit. BlockVectors can be used in hash sets and
* hash maps. Be aware that BlockVectors are mutable, but it is important
* that BlockVectors are never changed once put into a hash set or hash map.
- *
- * @author sk89q
*/
+@SerializableAs("BlockVector")
public class BlockVector extends Vector {
/**
@@ -109,4 +111,22 @@ public class BlockVector extends Vector {
v.z = z;
return v;
}
+
+ public static BlockVector deserialize(Map<String, Object> args) {
+ double x = 0;
+ double y = 0;
+ double z = 0;
+
+ if (args.containsKey("x")) {
+ x = (Double)args.get("x");
+ }
+ if (args.containsKey("y")) {
+ y = (Double)args.get("y");
+ }
+ if (args.containsKey("z")) {
+ z = (Double)args.get("z");
+ }
+
+ return new BlockVector(x, y, z);
+ }
}
diff --git a/src/main/java/org/bukkit/util/Vector.java b/src/main/java/org/bukkit/util/Vector.java
index 194f4ac3..d8e2289e 100644
--- a/src/main/java/org/bukkit/util/Vector.java
+++ b/src/main/java/org/bukkit/util/Vector.java
@@ -1,18 +1,21 @@
package org.bukkit.util;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Random;
import org.bukkit.Location;
import org.bukkit.World;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.SerializableAs;
/**
* Represents a mutable vector. Because the components of Vectors are mutable,
* storing Vectors long term may be dangerous if passing code modifies the
* Vector later. If you want to keep around a Vector, it may be wise to call
* <code>clone()</code> in order to get a copy.
- *
- * @author sk89q
*/
-public class Vector implements Cloneable {
+@SerializableAs("Vector")
+public class Vector implements Cloneable, ConfigurationSerializable {
private static final long serialVersionUID = -2657651106777219169L;
private static Random random = new Random();
@@ -635,4 +638,32 @@ public class Vector implements Cloneable {
public static Vector getRandom() {
return new Vector(random.nextDouble(), random.nextDouble(), random.nextDouble());
}
+
+ public Map<String, Object> serialize() {
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
+
+ result.put("x", getX());
+ result.put("y", getY());
+ result.put("z", getZ());
+
+ return result;
+ }
+
+ public static Vector deserialize(Map<String, Object> args) {
+ double x = 0;
+ double y = 0;
+ double z = 0;
+
+ if (args.containsKey("x")) {
+ x = (Double)args.get("x");
+ }
+ if (args.containsKey("y")) {
+ y = (Double)args.get("y");
+ }
+ if (args.containsKey("z")) {
+ z = (Double)args.get("z");
+ }
+
+ return new Vector(x, y, z);
+ }
}
diff --git a/src/main/java/org/bukkit/util/config/Configuration.java b/src/main/java/org/bukkit/util/config/Configuration.java
index bc9a465f..2259452e 100644
--- a/src/main/java/org/bukkit/util/config/Configuration.java
+++ b/src/main/java/org/bukkit/util/config/Configuration.java
@@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
+import org.bukkit.configuration.file.YamlConfiguration;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
@@ -52,12 +53,18 @@ import org.yaml.snakeyaml.representer.Representer;
* <p>This class is currently incomplete. It is not yet possible to get a node.
* </p>
*
+ * @deprecated See {@link YamlConfiguration}
*/
+@Deprecated
public class Configuration extends ConfigurationNode {
private Yaml yaml;
private File file;
private String header = null;
+ /**
+ * @deprecated See {@link YamlConfiguration}
+ */
+ @Deprecated
public Configuration(File file) {
super(new HashMap<String, Object>());
diff --git a/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java
new file mode 100644
index 00000000..144fba3a
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java
@@ -0,0 +1,504 @@
+package org.bukkit.configuration;
+
+import org.bukkit.Material;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public abstract class ConfigurationSectionTest {
+ public abstract ConfigurationSection getConfigurationSection();
+
+ @Test
+ public void testGetKeys() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("key", true);
+ section.set("subsection.subkey", true);
+ section.set("subsection.subkey2", true);
+ section.set("subsection.subsubsection.key", true);
+ section.set("key2", true);
+
+ assertArrayEquals(new String[] {"key", "subsection", "key2"}, section.getKeys(false).toArray());
+ assertArrayEquals(new String[] {"key", "subsection", "subsection.subkey", "subsection.subkey2", "subsection.subsubsection", "subsection.subsubsection.key", "key2"}, section.getKeys(true).toArray());
+ assertArrayEquals(new String[] {"subkey", "subkey2", "subsubsection", "subsubsection.key"}, section.getConfigurationSection("subsection").getKeys(true).toArray());
+ }
+
+ @Test
+ public void testGetKeysWithDefaults() {
+ ConfigurationSection section = getConfigurationSection();
+ section.getRoot().options().copyDefaults(true);
+
+ section.set("key", true);
+ section.addDefault("subsection.subkey", true);
+ section.addDefault("subsection.subkey2", true);
+ section.addDefault("subsection.subsubsection.key", true);
+ section.addDefault("key2", true);
+
+ assertArrayEquals(new String[] {"subsection", "key2", "key"}, section.getKeys(false).toArray());
+ assertArrayEquals(new String[] {"subsection", "subsection.subkey", "subsection.subkey2", "subsection.subsubsection", "subsection.subsubsection.key", "key2", "key"}, section.getKeys(true).toArray());
+ assertArrayEquals(new String[] {"subkey", "subkey2", "subsubsection", "subsubsection.key"}, section.getConfigurationSection("subsection").getKeys(true).toArray());
+ }
+
+ @Test
+ public void testGetValues() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("bool", true);
+ section.set("subsection.string", "test");
+ section.set("subsection.long", Long.MAX_VALUE);
+ section.set("int", 42);
+
+ Map<String, Object> shallowValues = section.getValues(false);
+ assertArrayEquals(new String[] {"bool", "subsection", "int"}, shallowValues.keySet().toArray());
+ assertArrayEquals(new Object[] {true, section.getConfigurationSection("subsection"), 42}, shallowValues.values().toArray());
+
+ Map<String, Object> deepValues = section.getValues(true);
+ assertArrayEquals(new String[] {"bool", "subsection", "subsection.string", "subsection.long", "int"}, deepValues.keySet().toArray());
+ assertArrayEquals(new Object[] {true, section.getConfigurationSection("subsection"), "test", Long.MAX_VALUE, 42}, deepValues.values().toArray());
+ }
+
+ @Test
+ public void testGetValuesWithDefaults() {
+ ConfigurationSection section = getConfigurationSection();
+ section.getRoot().options().copyDefaults(true);
+
+ section.set("bool", true);
+ section.set("subsection.string", "test");
+ section.addDefault("subsection.long", Long.MAX_VALUE);
+ section.addDefault("int", 42);
+
+ Map<String, Object> shallowValues = section.getValues(false);
+ assertArrayEquals(new String[] {"subsection", "int", "bool"}, shallowValues.keySet().toArray());
+ assertArrayEquals(new Object[] {section.getConfigurationSection("subsection"), 42, true}, shallowValues.values().toArray());
+
+ Map<String, Object> deepValues = section.getValues(true);
+ assertArrayEquals(new String[] {"subsection", "subsection.long", "int", "bool", "subsection.string"}, deepValues.keySet().toArray());
+ assertArrayEquals(new Object[] {section.getConfigurationSection("subsection"), Long.MAX_VALUE, 42, true, "test"}, deepValues.values().toArray());
+ }
+
+ @Test
+ public void testContains() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("exists", true);
+
+ assertTrue(section.contains("exists"));
+ assertFalse(section.contains("doesnt-exist"));
+ }
+
+ @Test
+ public void testIsSet() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("notDefault", true);
+ section.getRoot().addDefault("default", true);
+ section.getRoot().addDefault("defaultAndSet", true);
+ section.set("defaultAndSet", true);
+
+ assertTrue(section.isSet("notDefault"));
+ assertFalse(section.isSet("default"));
+ assertTrue(section.isSet("defaultAndSet"));
+ }
+
+ @Test
+ public void testGetCurrentPath() {
+ ConfigurationSection section = getConfigurationSection();
+
+ assertEquals(section.getName(), section.getCurrentPath());
+ }
+
+ @Test
+ public void testGetName() {
+ ConfigurationSection section = getConfigurationSection().createSection("subsection");
+
+ assertEquals("subsection", section.getName());
+ assertEquals("", section.getRoot().getName());
+ }
+
+ @Test
+ public void testGetRoot() {
+ ConfigurationSection section = getConfigurationSection();
+
+ assertNotNull(section.getRoot());
+ assertTrue(section.getRoot().contains(section.getCurrentPath()));
+ }
+
+ @Test
+ public void testGetParent() {
+ ConfigurationSection section = getConfigurationSection();
+ ConfigurationSection subsection = section.createSection("subsection");
+
+ assertEquals(section.getRoot(), section.getParent());
+ assertEquals(section, subsection.getParent());
+ }
+
+ @Test
+ public void testGet_String() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("exists", "hello world");
+
+ assertEquals("hello world", section.getString("exists"));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGet_String_Object() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("exists", "Set Value");
+
+ assertEquals("Set Value", section.get("exists", "Default Value"));
+ assertEquals("Default Value", section.get("doesntExist", "Default Value"));
+ }
+
+ @Test
+ public void testSet() {
+ ConfigurationSection section = getConfigurationSection();
+
+ section.set("exists", "hello world");
+ assertEquals("hello world", section.get("exists"));
+ }
+
+ @Test
+ public void testCreateSection() {
+ ConfigurationSection section = getConfigurationSection();
+ ConfigurationSection subsection = section.createSection("subsection");
+
+ assertEquals("subsection", subsection.getName());
+ }
+
+ @Test
+ public void testGetString_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ String value = "Hello World";
+
+ section.set(key, value);
+
+ assertEquals(value, section.getString(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetString_String_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ String value = "Hello World";
+ String def = "Default Value";
+
+ section.set(key, value);
+
+ assertEquals(value, section.getString(key, def));
+ assertEquals(def, section.getString("doesntExist", def));
+ }
+
+ @Test
+ public void testIsString() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ String value = "Hello World";
+
+ section.set(key, value);
+
+ assertTrue(section.isString(key));
+ assertFalse(section.isString("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetInt_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ int value = Integer.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getInt(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetInt_String_Int() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ int value = Integer.MAX_VALUE;
+ int def = Integer.MIN_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getInt(key, def));
+ assertEquals(def, section.getInt("doesntExist", def));
+ }
+
+ @Test
+ public void testIsInt() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ int value = Integer.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertTrue(section.isInt(key));
+ assertFalse(section.isInt("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetBoolean_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ boolean value = true;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getBoolean(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetBoolean_String_Boolean() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ boolean value = true;
+ boolean def = false;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getBoolean(key, def));
+ assertEquals(def, section.getBoolean("doesntExist", def));
+ }
+
+ @Test
+ public void testIsBoolean() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ boolean value = true;
+
+ section.set(key, value);
+
+ assertTrue(section.isBoolean(key));
+ assertFalse(section.isBoolean("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetDouble_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ double value = Double.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getDouble(key), 1);
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetDouble_String_Double() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ double value = Double.MAX_VALUE;
+ double def = Double.MIN_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getDouble(key, def), 1);
+ assertEquals(def, section.getDouble("doesntExist", def), 1);
+ }
+
+ @Test
+ public void testIsDouble() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ double value = Double.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertTrue(section.isDouble(key));
+ assertFalse(section.isDouble("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetLong_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ long value = Long.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getLong(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetLong_String_Long() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ long value = Long.MAX_VALUE;
+ long def = Long.MIN_VALUE;
+
+ section.set(key, value);
+
+ assertEquals(value, section.getLong(key, def));
+ assertEquals(def, section.getLong("doesntExist", def));
+ }
+
+ @Test
+ public void testIsLong() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ long value = Long.MAX_VALUE;
+
+ section.set(key, value);
+
+ assertTrue(section.isLong(key));
+ assertFalse(section.isLong("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetList_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ List value = Arrays.asList("One", "Two", "Three");
+
+ section.set(key, value);
+
+ assertEquals(value, section.getList(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetList_String_List() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ List value = Arrays.asList("One", "Two", "Three");
+ List def = Arrays.asList("A", "B", "C");
+
+ section.set(key, value);
+
+ assertEquals(value, section.getList(key, def));
+ assertEquals(def, section.getList("doesntExist", def));
+ }
+
+ @Test
+ public void testIsList() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ List value = Arrays.asList("One", "Two", "Three");
+
+ section.set(key, value);
+
+ assertTrue(section.isList(key));
+ assertFalse(section.isList("doesntExist"));
+ }
+
+
+ @Test
+ public void testGetVector_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5);
+
+ section.set(key, value);
+
+ assertEquals(value, section.getVector(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetVector_String_Vector() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5);
+ Vector def = new Vector(100, Double.MIN_VALUE, Double.MAX_VALUE);
+
+ section.set(key, value);
+
+ assertEquals(value, section.getVector(key, def));
+ assertEquals(def, section.getVector("doesntExist", def));
+ }
+
+ @Test
+ public void testIsVector() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5);
+
+ section.set(key, value);
+
+ assertTrue(section.isVector(key));
+ assertFalse(section.isVector("doesntExist"));
+ }
+
+ @Test
+ public void testGetItemStack_String() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ ItemStack value = new ItemStack(Material.WOOD, 50, (short)2);
+
+ section.set(key, value);
+
+ assertEquals(value, section.getItemStack(key));
+ assertNull(section.getString("doesntExist"));
+ }
+
+ @Test
+ public void testGetItemStack_String_ItemStack() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ ItemStack value = new ItemStack(Material.WOOD, 50, (short)2);
+ ItemStack def = new ItemStack(Material.STONE, 1);
+
+ section.set(key, value);
+
+ assertEquals(value, section.getItemStack(key, def));
+ assertEquals(def, section.getItemStack("doesntExist", def));
+ }
+
+ @Test
+ public void testIsItemStack() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+ ItemStack value = new ItemStack(Material.WOOD, 50, (short)2);
+
+ section.set(key, value);
+
+ assertTrue(section.isItemStack(key));
+ assertFalse(section.isItemStack("doesntExist"));
+ }
+
+ @Test
+ public void testGetConfigurationSection() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+
+ ConfigurationSection subsection = section.createSection(key);
+
+ assertEquals(subsection, section.getConfigurationSection(key));
+ }
+
+ @Test
+ public void testIsConfigurationSection() {
+ ConfigurationSection section = getConfigurationSection();
+ String key = "exists";
+
+ ConfigurationSection subsection = section.createSection(key);
+
+ assertTrue(section.isConfigurationSection(key));
+ assertFalse(section.isConfigurationSection("doesntExist"));
+ }
+
+ public enum TestEnum {
+ HELLO,
+ WORLD,
+ BANANAS
+ }
+} \ No newline at end of file
diff --git a/src/test/java/org/bukkit/configuration/ConfigurationTest.java b/src/test/java/org/bukkit/configuration/ConfigurationTest.java
new file mode 100644
index 00000000..0f2af733
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/ConfigurationTest.java
@@ -0,0 +1,135 @@
+package org.bukkit.configuration;
+
+import java.util.LinkedHashMap;
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.util.Vector;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public abstract class ConfigurationTest {
+ public abstract Configuration getConfig();
+
+ public Map<String, Object> getTestValues() {
+ HashMap<String, Object> result = new LinkedHashMap<String, Object>();
+
+ result.put("integer", Integer.MIN_VALUE);
+ result.put("string", "String Value");
+ result.put("long", Long.MAX_VALUE);
+ result.put("true-boolean", true);
+ result.put("false-boolean", false);
+ result.put("vector", new Vector(12345.67, 64, -12345.6789));
+ result.put("list", Arrays.asList(1, 2, 3, 4, 5));
+
+ return result;
+ }
+
+ /**
+ * Test of addDefault method, of class Configuration.
+ */
+ @Test
+ public void testAddDefault() {
+ Configuration config = getConfig();
+ Map<String, Object> values = getTestValues();
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ String path = entry.getKey();
+ Object object = entry.getValue();
+
+ config.addDefault(path, object);
+
+ assertEquals(object, config.get(path));
+ assertTrue(config.contains(path));
+ assertFalse(config.isSet(path));
+ assertTrue(config.getDefaults().isSet(path));
+ }
+ }
+
+ /**
+ * Test of addDefaults method, of class Configuration.
+ */
+ @Test
+ public void testAddDefaults_Map() {
+ Configuration config = getConfig();
+ Map<String, Object> values = getTestValues();
+
+ config.addDefaults(values);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ String path = entry.getKey();
+ Object object = entry.getValue();
+
+ assertEquals(object, config.get(path));
+ assertTrue(config.contains(path));
+ assertFalse(config.isSet(path));
+ assertTrue(config.getDefaults().isSet(path));
+ }
+ }
+
+ /**
+ * Test of addDefaults method, of class Configuration.
+ */
+ @Test
+ public void testAddDefaults_Configuration() {
+ Configuration config = getConfig();
+ Map<String, Object> values = getTestValues();
+ Configuration defaults = getConfig();
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ defaults.set(entry.getKey(), entry.getValue());
+ }
+
+ config.addDefaults(defaults);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ String path = entry.getKey();
+ Object object = entry.getValue();
+
+ assertEquals(object, config.get(path));
+ assertTrue(config.contains(path));
+ assertFalse(config.isSet(path));
+ assertTrue(config.getDefaults().isSet(path));
+ }
+ }
+
+ /**
+ * Test of setDefaults method, of class Configuration.
+ */
+ @Test
+ public void testSetDefaults() {
+ Configuration config = getConfig();
+ Map<String, Object> values = getTestValues();
+ Configuration defaults = getConfig();
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ defaults.set(entry.getKey(), entry.getValue());
+ }
+
+ config.setDefaults(defaults);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ String path = entry.getKey();
+ Object object = entry.getValue();
+
+ assertEquals(object, config.get(path));
+ assertTrue(config.contains(path));
+ assertFalse(config.isSet(path));
+ assertTrue(config.getDefaults().isSet(path));
+ }
+ }
+
+ /**
+ * Test of getDefaults method, of class Configuration.
+ */
+ @Test
+ public void testGetDefaults() {
+ Configuration config = getConfig();
+ Configuration defaults = getConfig();
+
+ config.setDefaults(defaults);
+
+ assertEquals(defaults, config.getDefaults());
+ }
+} \ No newline at end of file
diff --git a/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java b/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java
new file mode 100644
index 00000000..3de0ce92
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java
@@ -0,0 +1,8 @@
+package org.bukkit.configuration;
+
+public class MemoryConfigurationTest extends ConfigurationTest {
+ @Override
+ public Configuration getConfig() {
+ return new MemoryConfiguration();
+ }
+}
diff --git a/src/test/java/org/bukkit/configuration/MemorySectionTest.java b/src/test/java/org/bukkit/configuration/MemorySectionTest.java
new file mode 100644
index 00000000..be7768ab
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/MemorySectionTest.java
@@ -0,0 +1,8 @@
+package org.bukkit.configuration;
+
+public class MemorySectionTest extends ConfigurationSectionTest {
+ @Override
+ public ConfigurationSection getConfigurationSection() {
+ return new MemoryConfiguration().createSection("section");
+ }
+}
diff --git a/src/test/java/org/bukkit/configuration/TestEnum.java b/src/test/java/org/bukkit/configuration/TestEnum.java
new file mode 100644
index 00000000..2d8655ad
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/TestEnum.java
@@ -0,0 +1 @@
+package org.bukkit.configuration;
diff --git a/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java b/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java
new file mode 100644
index 00000000..99eba4e1
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java
@@ -0,0 +1,127 @@
+package org.bukkit.configuration.file;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.util.Map;
+import java.util.Scanner;
+import org.bukkit.configuration.MemoryConfigurationTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
+public abstract class FileConfigurationTest extends MemoryConfigurationTest {
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ @Override
+ public abstract FileConfiguration getConfig();
+
+ public abstract String getTestValuesString();
+
+ @Test
+ public void testSave_File() throws Exception {
+ FileConfiguration config = getConfig();
+ File file = testFolder.newFile("test.config");
+
+ for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
+ config.set(entry.getKey(), entry.getValue());
+ }
+
+ config.save(file);
+
+ assertTrue(file.isFile());
+ }
+
+ @Test
+ public void testSave_String() throws Exception {
+ FileConfiguration config = getConfig();
+ File file = testFolder.newFile("test.config");
+
+ for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
+ config.set(entry.getKey(), entry.getValue());
+ }
+
+ config.save(file.getAbsolutePath());
+
+ assertTrue(file.isFile());
+ }
+
+ @Test
+ public void testSaveToString() {
+ FileConfiguration config = getConfig();
+
+ for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
+ config.set(entry.getKey(), entry.getValue());
+ }
+
+ String result = config.saveToString();
+ String expected = getTestValuesString();
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLoad_File() throws Exception {
+ FileConfiguration config = getConfig();
+ File file = testFolder.newFile("test.config");
+ BufferedWriter writer = new BufferedWriter(new FileWriter(file));
+ String saved = getTestValuesString();
+ Map<String, Object> values = getTestValues();
+
+ try {
+ writer.write(saved);
+ } finally {
+ writer.close();
+ }
+
+ config.load(file);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ assertEquals(entry.getValue(), config.get(entry.getKey()));
+ }
+
+ assertEquals(values.keySet(), config.getKeys(true));
+ }
+
+ @Test
+ public void testLoad_String() throws Exception {
+ FileConfiguration config = getConfig();
+ File file = testFolder.newFile("test.config");
+ BufferedWriter writer = new BufferedWriter(new FileWriter(file));
+ String saved = getTestValuesString();
+ Map<String, Object> values = getTestValues();
+
+ try {
+ writer.write(saved);
+ } finally {
+ writer.close();
+ }
+
+ config.load(file.getAbsolutePath());
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ assertEquals(entry.getValue(), config.get(entry.getKey()));
+ }
+
+ assertEquals(values.keySet(), config.getKeys(true));
+ }
+
+ @Test
+ public void testLoadFromString() throws Exception {
+ FileConfiguration config = getConfig();
+ Map<String, Object> values = getTestValues();
+ String saved = getTestValuesString();
+
+ config.loadFromString(saved);
+
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ assertEquals(entry.getValue(), config.get(entry.getKey()));
+ }
+
+ assertEquals(values.keySet(), config.getKeys(true));
+ }
+}
diff --git a/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java b/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java
new file mode 100644
index 00000000..1bd27e1c
--- /dev/null
+++ b/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java
@@ -0,0 +1,62 @@
+package org.bukkit.configuration.file;
+
+import java.util.Map;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class YamlConfigurationTest extends FileConfigurationTest {
+ @Override
+ public YamlConfiguration getConfig() {
+ return new YamlConfiguration();
+ }
+
+ @Override
+ public String getTestValuesString() {
+ return "integer: -2147483648\n" +
+ "string: String Value\n" +
+ "long: 9223372036854775807\n" +
+ "true-boolean: true\n" +
+ "false-boolean: false\n" +
+ "vector:\n" +
+ " ==: Vector\n" +
+ " x: 12345.67\n" +
+ " y: 64.0\n" +
+ " z: -12345.6789\n" +
+ "list:\n" +
+ "- 1\n" +
+ "- 2\n" +
+ "- 3\n" +
+ "- 4\n" +
+ "- 5\n";
+ }
+
+ @Test
+ public void testSaveToStringWithheader() {
+ YamlConfiguration config = getConfig();
+ config.options().header("This is a sample\nheader.");
+
+ for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
+ config.set(entry.getKey(), entry.getValue());
+ }
+
+ String result = config.saveToString();
+ String expected = "# This is a sample\n# header.\n" + getTestValuesString();
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testSaveToStringWithIndent() {
+ YamlConfiguration config = getConfig();
+ config.options().indent(9);
+
+ config.set("section.key", 1);
+
+ String result = config.saveToString();
+ String expected = "section:\n key: 1\n";
+
+ System.out.println(result);
+
+ assertEquals(expected, result);
+ }
+}