From ee27cc07f026155e4743b26c7b945a65510b0e60 Mon Sep 17 00:00:00 2001 From: Dinnerbone Date: Mon, 19 Sep 2011 20:36:44 +0100 Subject: Added new Configuration classes --- src/main/java/org/bukkit/OfflinePlayer.java | 3 +- .../org/bukkit/configuration/Configuration.java | 82 +++ .../bukkit/configuration/ConfigurationOptions.java | 81 +++ .../bukkit/configuration/ConfigurationSection.java | 560 +++++++++++++++++ .../InvalidConfigurationException.java | 39 ++ .../bukkit/configuration/MemoryConfiguration.java | 85 +++ .../configuration/MemoryConfigurationOptions.java | 27 + .../org/bukkit/configuration/MemorySection.java | 667 +++++++++++++++++++++ .../configuration/file/FileConfiguration.java | 191 ++++++ .../file/FileConfigurationOptions.java | 67 +++ .../configuration/file/YamlConfiguration.java | 228 +++++++ .../file/YamlConfigurationOptions.java | 63 ++ .../serialization/ConfigurationSerializable.java | 24 + .../serialization/ConfigurationSerialization.java | 251 ++++++++ .../serialization/DelegateDeserialization.java | 21 + .../serialization/SerializableAs.java | 28 + src/main/java/org/bukkit/inventory/ItemStack.java | 38 +- src/main/java/org/bukkit/plugin/Plugin.java | 25 + .../java/org/bukkit/plugin/java/JavaPlugin.java | 45 +- .../org/bukkit/plugin/java/JavaPluginLoader.java | 19 +- src/main/java/org/bukkit/util/BlockVector.java | 24 +- src/main/java/org/bukkit/util/Vector.java | 37 +- .../java/org/bukkit/util/config/Configuration.java | 7 + .../configuration/ConfigurationSectionTest.java | 504 ++++++++++++++++ .../bukkit/configuration/ConfigurationTest.java | 135 +++++ .../configuration/MemoryConfigurationTest.java | 8 + .../bukkit/configuration/MemorySectionTest.java | 8 + .../java/org/bukkit/configuration/TestEnum.java | 1 + .../configuration/file/FileConfigurationTest.java | 127 ++++ .../configuration/file/YamlConfigurationTest.java | 62 ++ 30 files changed, 3448 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/bukkit/configuration/Configuration.java create mode 100644 src/main/java/org/bukkit/configuration/ConfigurationOptions.java create mode 100644 src/main/java/org/bukkit/configuration/ConfigurationSection.java create mode 100644 src/main/java/org/bukkit/configuration/InvalidConfigurationException.java create mode 100644 src/main/java/org/bukkit/configuration/MemoryConfiguration.java create mode 100644 src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java create mode 100644 src/main/java/org/bukkit/configuration/MemorySection.java create mode 100644 src/main/java/org/bukkit/configuration/file/FileConfiguration.java create mode 100644 src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java create mode 100644 src/main/java/org/bukkit/configuration/file/YamlConfiguration.java create mode 100644 src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java create mode 100644 src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java create mode 100644 src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java create mode 100644 src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java create mode 100644 src/main/java/org/bukkit/configuration/serialization/SerializableAs.java create mode 100644 src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java create mode 100644 src/test/java/org/bukkit/configuration/ConfigurationTest.java create mode 100644 src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java create mode 100644 src/test/java/org/bukkit/configuration/MemorySectionTest.java create mode 100644 src/test/java/org/bukkit/configuration/TestEnum.java create mode 100644 src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java create mode 100644 src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java (limited to 'src') 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 defaults); + + /** + * Sets the default values of the given paths as provided. + *

+ * 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. + *

+ * 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}. + *

+ * 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. + *

+ * 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}. + *

+ * 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 + *

+ * 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 + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 getKeys(boolean deep); + + /** + * Gets a Map containing all keys and their values for this section. + *

+ * 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. + *

+ * 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 getValues(boolean deep); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + *

+ * 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. + *

+ * 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} + *

+ * For any {@link Configuration} themselves, this will return an empty string. + *

+ * 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. + *

+ * 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. + *

+ * 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} + *

+ * For any {@link Configuration} themselves, this will return its own object. + *

+ * 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}. + *

+ * For any {@link Configuration} themselves, this will return null. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * If value is null, the entry will be removed. Any existing entry will be + * replaced, regardless of what the new value is. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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()}. + *

+ * 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. + *

+ * 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. + *

+ * If value is null, the value will be removed from the default Configuration source. + *

+ * 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 defaults) { + if (defaults == null) { + throw new IllegalArgumentException("Defaults may not be null"); + } + + for (Map.Entry 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 map = new LinkedHashMap(); + 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. + *

+ * 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 getKeys(boolean deep) { + Set result = new LinkedHashSet(); + + if (getRoot().options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.addAll(defaults.getKeys(deep)); + } + } + + mapChildrenKeys(result, this, deep); + + return result; + } + + public Map getValues(boolean deep) { + Map result = new LinkedHashMap(); + + 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 output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection)section; + + for (Map.Entry 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 keys = section.getKeys(deep); + + for (String key : keys) { + output.add(createPath(section, key, this)); + } + } + } + + protected void mapChildrenValues(Map output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection)section; + + for (Map.Entry 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 values = section.getValues(deep); + + for (Map.Entry 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}. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 output = new LinkedHashMap(); + + 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 input; + try { + input = (Map)yaml.load(contents); + } catch (Throwable ex) { + throw new InvalidConfigurationException("Specified contents is not a valid Configuration", ex); + } + + deserializeValues(input, this); + } + + protected void deserializeValues(Map input, ConfigurationSection section) throws InvalidConfigurationException { + if (input == null) { + return; + } + + for (Map.Entry entry : input.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof Map) { + Map subvalues; + + try { + subvalues = (Map) value; + } catch (ClassCastException ex) { + throw new InvalidConfigurationException("Map found where type is not ", 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 output, Map input) { + if (input == null) { + return; + } + + for (Map.Entry entry : input.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof ConfigurationSection) { + ConfigurationSection subsection = (ConfigurationSection)entry.getValue(); + Map subvalues = new LinkedHashMap(); + + serializeValues(subvalues, subsection.getValues(false)); + value = subvalues; + } else if (value instanceof ConfigurationSerializable) { + ConfigurationSerializable serializable = (ConfigurationSerializable)value; + Map subvalues = new LinkedHashMap(); + 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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} and returns the class. + * - A static method "valueOf" that accepts a single {@link Map} and returns the class. + * - A constructor that accepts a single {@link Map}. + */ +public interface ConfigurationSerializable { + /** + * Creates a Map representation of this class. + *

+ * 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 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 clazz; + private static Map> aliases = new HashMap>(); + + static { + registerClass(Vector.class); + registerClass(BlockVector.class); + } + + protected ConfigurationSerialization(Class 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 getConstructor() { + try { + return clazz.getConstructor(Map.class); + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + protected ConfigurationSerializable deserializeViaMethod(Method method, Map 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 ctor, Map 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 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 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. + *

+ * The class must implement {@link ConfigurationSerializable}, including the extra methods + * as specified in the javadoc of ConfigurationSerializable. + *

+ * 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 args, Class clazz) { + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Attempts to deserialize the given arguments into a new instance of the given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including the extra methods + * as specified in the javadoc of ConfigurationSerializable. + *

+ * 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 args) { + Class 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 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 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 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 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 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 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. + *

+ * 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. + *

+ * 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 serialize() { + Map result = new LinkedHashMap(); + + 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 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" + *

+ * 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 serializable = (Class)clazz; + ConfigurationSerialization.registerClass(serializable); + } + } + } + + public void removeClass(String name) { + Class clazz = classes.remove(name); + + if (ConfigurationSerializable.class.isAssignableFrom(clazz)) { + Class serializable = (Class)clazz; + ConfigurationSerialization.unregisterClass(serializable); } } @@ -973,7 +990,7 @@ public class JavaPluginLoader implements PluginLoader { Set 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 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 * clone() 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 serialize() { + Map result = new LinkedHashMap(); + + result.put("x", getX()); + result.put("y", getY()); + result.put("z", getZ()); + + return result; + } + + public static Vector deserialize(Map 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; *

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

* + * @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()); 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 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 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 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 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 getTestValues() { + HashMap result = new LinkedHashMap(); + + 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 values = getTestValues(); + + for (Map.Entry 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 values = getTestValues(); + + config.addDefaults(values); + + for (Map.Entry 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 values = getTestValues(); + Configuration defaults = getConfig(); + + for (Map.Entry entry : values.entrySet()) { + defaults.set(entry.getKey(), entry.getValue()); + } + + config.addDefaults(defaults); + + for (Map.Entry 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 values = getTestValues(); + Configuration defaults = getConfig(); + + for (Map.Entry entry : values.entrySet()) { + defaults.set(entry.getKey(), entry.getValue()); + } + + config.setDefaults(defaults); + + for (Map.Entry 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 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 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 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 values = getTestValues(); + + try { + writer.write(saved); + } finally { + writer.close(); + } + + config.load(file); + + for (Map.Entry 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 values = getTestValues(); + + try { + writer.write(saved); + } finally { + writer.close(); + } + + config.load(file.getAbsolutePath()); + + for (Map.Entry 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 values = getTestValues(); + String saved = getTestValuesString(); + + config.loadFromString(saved); + + for (Map.Entry 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 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); + } +} -- cgit v1.2.3