diff options
Diffstat (limited to 'src/main/java')
23 files changed, 2603 insertions, 9 deletions
diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java index 1a7599b4..f0d59c3a 100644 --- a/src/main/java/org/bukkit/OfflinePlayer.java +++ b/src/main/java/org/bukkit/OfflinePlayer.java @@ -1,9 +1,10 @@ package org.bukkit; +import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.AnimalTamer; import org.bukkit.permissions.ServerOperator; -public interface OfflinePlayer extends ServerOperator, AnimalTamer { +public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable { /** * Checks if this player is currently online * diff --git a/src/main/java/org/bukkit/configuration/Configuration.java b/src/main/java/org/bukkit/configuration/Configuration.java new file mode 100644 index 00000000..ed66d1b8 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/Configuration.java @@ -0,0 +1,82 @@ +package org.bukkit.configuration; + +import java.util.Map; + +/** + * Represents a source of configurable options and settings + */ +public interface Configuration extends ConfigurationSection { + /** + * Sets the default value of the given path as provided. + * <p> + * If no source {@link Configuration} was provided as a default collection, + * then a new {@link MemoryConfiguration} will be created to hold the new default + * value. + * <p> + * If value is null, the value will be removed from the default Configuration source. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(String path, Object value); + + /** + * Sets the default values of the given paths as provided. + * <p> + * If no source {@link Configuration} was provided as a default collection, + * then a new {@link MemoryConfiguration} will be created to hold the new default + * values. + * + * @param defaults A map of Path->Values to add to defaults. + * @throws IllegalArgumentException Thrown if defaults is null. + */ + public void addDefaults(Map<String, Object> defaults); + + /** + * Sets the default values of the given paths as provided. + * <p> + * If no source {@link Configuration} was provided as a default collection, + * then a new {@link MemoryConfiguration} will be created to hold the new default + * value. + * <p> + * This method will not hold a reference to the specified Configuration, nor will it + * automatically update if that Configuration ever changes. If you require this, + * you should set the default source with {@link #setDefaults(org.bukkit.configuration.Configuration)}. + * + * @param defaults A configuration holding a list of defaults to copy. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void addDefaults(Configuration defaults); + + /** + * Sets the source of all default values for this {@link Configuration}. + * <p> + * If a previous source was set, or previous default values were defined, then they will + * not be copied to the new source. + * + * @param defaults New source of default values for this configuration. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void setDefaults(Configuration defaults); + + /** + * Gets the source {@link Configuration} for this configuration. + * <p> + * If no configuration source was set, but default values were added, then a + * {@link MemoryConfiguration} will be returned. If no source was set and no + * defaults were set, then this method will return null. + * + * @return Configuration source for default values, or null if none exist. + */ + public Configuration getDefaults(); + + /** + * Gets the {@link ConfigurationOptions} for this {@link Configuration}. + * <p> + * All setters through this method are chainable. + * + * @return Options for this configuration + */ + public ConfigurationOptions options(); +} diff --git a/src/main/java/org/bukkit/configuration/ConfigurationOptions.java b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java new file mode 100644 index 00000000..5392700f --- /dev/null +++ b/src/main/java/org/bukkit/configuration/ConfigurationOptions.java @@ -0,0 +1,81 @@ +package org.bukkit.configuration; + +/** + * Various settings for controlling the input and output of a {@link Configuration} + */ +public class ConfigurationOptions { + private char pathSeparator = '.'; + private boolean copyDefaults = false; + private final Configuration configuration; + + protected ConfigurationOptions(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Returns the {@link Configuration} that this object is responsible for. + * + * @return Parent configuration + */ + public Configuration configuration() { + return configuration; + } + + /** + * Gets the char that will be used to separate {@link ConfigurationSection}s + * <p> + * This value does not affect how the {@link Configuration} is stored, only in + * how you access the data. The default value is '.'. + * + * @return Path separator + */ + public char pathSeparator() { + return pathSeparator; + } + + /** + * Sets the char that will be used to separate {@link ConfigurationSection}s + * <p> + * This value does not affect how the {@link Configuration} is stored, only in + * how you access the data. The default value is '.'. + * + * @param value Path separator + * @return This object, for chaining + */ + public ConfigurationOptions pathSeparator(char value) { + this.pathSeparator = value; + return this; + } + + /** + * Checks if the {@link Configuration} should copy values from its default {@link Configuration} directly. + * <p> + * If this is true, all values in the default Configuration will be directly copied, + * making it impossible to distinguish between values that were set and values that + * are provided by default. As a result, {@link ConfigurationSection#contains(java.lang.String)} will always + * return the same value as {@link ConfigurationSection#isSet(java.lang.String)}. + * The default value is false. + * + * @return Whether or not defaults are directly copied + */ + public boolean copyDefaults() { + return copyDefaults; + } + + /** + * Sets if the {@link Configuration} should copy values from its default {@link Configuration} directly. + * <p> + * If this is true, all values in the default Configuration will be directly copied, + * making it impossible to distinguish between values that were set and values that + * are provided by default. As a result, {@link ConfigurationSection#contains(java.lang.String)} will always + * return the same value as {@link ConfigurationSection#isSet(java.lang.String)}. + * The default value is false. + * + * @param value Whether or not defaults are directly copied + * @return This object, for chaining + */ + public ConfigurationOptions copyDefaults(boolean value) { + this.copyDefaults = value; + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/ConfigurationSection.java b/src/main/java/org/bukkit/configuration/ConfigurationSection.java new file mode 100644 index 00000000..739a3359 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/ConfigurationSection.java @@ -0,0 +1,560 @@ +package org.bukkit.configuration; + +import java.util.Map; +import java.util.Set; +import java.util.List; +import org.bukkit.OfflinePlayer; +import org.bukkit.util.Vector; +import org.bukkit.inventory.ItemStack; + +/** + * Represents a section of a {@link Configuration} + */ +public interface ConfigurationSection { + /** + * Gets a set containing all keys in this section. + * <p> + * If deep is set to true, then this will contain all the keys within any child + * {@link ConfigurationSection}s (and their children, etc). These will be in a + * valid path notation for you to use. + * <p> + * If deep is set to false, then this will contain only the keys of any direct children, + * and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow list. + * @return Set of keys contained within this ConfigurationSection. + */ + public Set<String> getKeys(boolean deep); + + /** + * Gets a Map containing all keys and their values for this section. + * <p> + * If deep is set to true, then this will contain all the keys and values within + * any child {@link ConfigurationSection}s (and their children, etc). These + * keys will be in a valid path notation for you to use. + * <p> + * If deep is set to false, then this will contain only the keys and values of any + * direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow list. + * @return Map of keys and values of this section. + */ + public Map<String, Object> getValues(boolean deep); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + * <p> + * If the value for the requested path does not exist but a default value has + * been specified, this will return true. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, either via default or being set. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean contains(String path); + + /** + * Checks if this {@link ConfigurationSection} has a value set for the given path. + * <p> + * If the value for the requested path does not exist but a default value has + * been specified, this will still return false. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, regardless of having a default. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean isSet(String path); + + /** + * Gets the path of this {@link ConfigurationSection} from its root {@link Configuration} + * <p> + * For any {@link Configuration} themselves, this will return an empty string. + * <p> + * If the section is no longer contained within its root for any reason, such as + * being replaced with a different value, this may return null. + * <p> + * To retrieve the single name of this section, that is, the final part of the path + * returned by this method, you may use {@link #getName()}. + * + * @return Path of this section relative to its root + */ + public String getCurrentPath(); + + /** + * Gets the name of this individual {@link ConfigurationSection}, in the path. + * <p> + * This will always be the final part of {@link #getCurrentPath()}, unless the + * section is orphaned. + * + * @return Name of this section + */ + public String getName(); + + /** + * Gets the root {@link Configuration} that contains this {@link ConfigurationSection} + * <p> + * For any {@link Configuration} themselves, this will return its own object. + * <p> + * If the section is no longer contained within its root for any reason, such as + * being replaced with a different value, this may return null. + * + * @return Root configuration containing this section. + */ + public Configuration getRoot(); + + /** + * Gets the parent {@link ConfigurationSection} that directly contains this + * {@link ConfigurationSection}. + * <p> + * For any {@link Configuration} themselves, this will return null. + * <p> + * If the section is no longer contained within its parent for any reason, such as + * being replaced with a different value, this may return null. + * + * @return Parent section containing this section. + */ + public ConfigurationSection getParent(); + + /** + * Gets the requested Object by path. + * <p> + * If the Object does not exist but a default value has been specified, this + * will return the default value. If the Object does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the Object to get. + * @return Requested Object. + */ + public Object get(String path); + + /** + * Gets the requested Object by path, returning a default value if not found. + * <p> + * If the Object does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the Object to get. + * @return Requested Object. + */ + public Object get(String path, Object def); + + /** + * Sets the specified path to the given value. + * <p> + * If value is null, the entry will be removed. Any existing entry will be + * replaced, regardless of what the new value is. + * <p> + * Some implementations may have limitations on what you may store. See their + * individual javadocs for details. No implementations should allow you to store + * {@link Configuration}s or {@link ConfigurationSection}s, please use + * {@link #createSection(java.lang.String)} for that. + * + * @param path Path of the object to set. + * @param value New value to set the path to. + */ + public void set(String path, Object value); + + /** + * Creates an empty {@link ConfigurationSection} at the specified path. + * <p> + * Any value that was previously set at this path will be overwritten. If the + * previous value was itself a {@link ConfigurationSection}, it will be orphaned. + * + * @param path Path to create the section at. + * @return Newly created section + */ + public ConfigurationSection createSection(String path); + + // Primitives + /** + * Gets the requested String by path. + * <p> + * If the String does not exist but a default value has been specified, this + * will return the default value. If the String does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the String to get. + * @return Requested String. + */ + public String getString(String path); + + /** + * Gets the requested String by path, returning a default value if not found. + * <p> + * If the String does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the String to get. + * @return Requested String. + */ + public String getString(String path, String def); + + /** + * Checks if the specified path is a String. + * <p> + * If the path exists but is not a String, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a String and return + * appropriately. + * + * @param path Path of the String to check. + * @return Whether or not the specified path is a String. + */ + public boolean isString(String path); + + + /** + * Gets the requested int by path. + * <p> + * If the int does not exist but a default value has been specified, this + * will return the default value. If the int does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the int to get. + * @return Requested int. + */ + public int getInt(String path); + + /** + * Gets the requested int by path, returning a default value if not found. + * <p> + * If the int does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the int to get. + * @return Requested int. + */ + public int getInt(String path, int def); + + /** + * Checks if the specified path is an int. + * <p> + * If the path exists but is not a int, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a int and return + * appropriately. + * + * @param path Path of the int to check. + * @return Whether or not the specified path is an int. + */ + public boolean isInt(String path); + + + /** + * Gets the requested boolean by path. + * <p> + * If the boolean does not exist but a default value has been specified, this + * will return the default value. If the boolean does not exist and no default + * value was specified, this will return false. + * + * @param path Path of the boolean to get. + * @return Requested boolean. + */ + public boolean getBoolean(String path); + + /** + * Gets the requested boolean by path, returning a default value if not found. + * <p> + * If the boolean does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the boolean to get. + * @return Requested boolean. + */ + public boolean getBoolean(String path, boolean def); + + /** + * Checks if the specified path is a boolean. + * <p> + * If the path exists but is not a boolean, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a boolean and return + * appropriately. + * + * @param path Path of the boolean to check. + * @return Whether or not the specified path is a boolean. + */ + public boolean isBoolean(String path); + + + /** + * Gets the requested double by path. + * <p> + * If the double does not exist but a default value has been specified, this + * will return the default value. If the double does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the double to get. + * @return Requested double. + */ + public double getDouble(String path); + + /** + * Gets the requested double by path, returning a default value if not found. + * <p> + * If the double does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the double to get. + * @return Requested double. + */ + public double getDouble(String path, double def); + + /** + * Checks if the specified path is a double. + * <p> + * If the path exists but is not a double, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a double and return + * appropriately. + * + * @param path Path of the double to check. + * @return Whether or not the specified path is a double. + */ + public boolean isDouble(String path); + + + /** + * Gets the requested long by path. + * <p> + * If the long does not exist but a default value has been specified, this + * will return the default value. If the long does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the long to get. + * @return Requested long. + */ + public long getLong(String path); + + /** + * Gets the requested long by path, returning a default value if not found. + * <p> + * If the long does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the long to get. + * @return Requested long. + */ + public long getLong(String path, long def); + + /** + * Checks if the specified path is a long. + * <p> + * If the path exists but is not a long, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a long and return + * appropriately. + * + * @param path Path of the long to check. + * @return Whether or not the specified path is a long. + */ + public boolean isLong(String path); + + + + // Java + /** + * Gets the requested List by path. + * <p> + * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the List to get. + * @return Requested List. + */ + public List getList(String path); + + /** + * Gets the requested List by path, returning a default value if not found. + * <p> + * If the List does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the List to get. + * @return Requested List. + */ + public List getList(String path, List def); + + /** + * Checks if the specified path is a List. + * <p> + * If the path exists but is not a List, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a List and return + * appropriately. + * + * @param path Path of the List to check. + * @return Whether or not the specified path is a List. + */ + public boolean isList(String path); + + + + // Bukkit + /** + * Gets the requested Vector by path. + * <p> + * If the Vector does not exist but a default value has been specified, this + * will return the default value. If the Vector does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the Vector to get. + * @return Requested Vector. + */ + public Vector getVector(String path); + + /** + * Gets the requested Vector by path, returning a default value if not found. + * <p> + * If the Vector does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the Vector to get. + * @return Requested Vector. + */ + public Vector getVector(String path, Vector def); + + /** + * Checks if the specified path is a Vector. + * <p> + * If the path exists but is not a Vector, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a Vector and return + * appropriately. + * + * @param path Path of the Vector to check. + * @return Whether or not the specified path is a Vector. + */ + public boolean isVector(String path); + + + /** + * Gets the requested OfflinePlayer by path. + * <p> + * If the OfflinePlayer does not exist but a default value has been specified, this + * will return the default value. If the OfflinePlayer does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the OfflinePlayer to get. + * @return Requested OfflinePlayer. + */ + public OfflinePlayer getOfflinePlayer(String path); + + /** + * Gets the requested OfflinePlayer by path, returning a default value if not found. + * <p> + * If the OfflinePlayer does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the OfflinePlayer to get. + * @return Requested OfflinePlayer. + */ + public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def); + + /** + * Checks if the specified path is an OfflinePlayer. + * <p> + * If the path exists but is not a OfflinePlayer, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a OfflinePlayer and return + * appropriately. + * + * @param path Path of the OfflinePlayer to check. + * @return Whether or not the specified path is an OfflinePlayer. + */ + public boolean isOfflinePlayer(String path); + + + /** + * Gets the requested ItemStack by path. + * <p> + * If the ItemStack does not exist but a default value has been specified, this + * will return the default value. If the ItemStack does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the ItemStack to get. + * @return Requested ItemStack. + */ + public ItemStack getItemStack(String path); + + /** + * Gets the requested ItemStack by path, returning a default value if not found. + * <p> + * If the ItemStack does not exist then the specified default value will returned + * regardless of if a default has been identified in the root {@link Configuration}. + * + * @param path Path of the ItemStack to get. + * @return Requested ItemStack. + */ + public ItemStack getItemStack(String path, ItemStack def); + + /** + * Checks if the specified path is an ItemStack. + * <p> + * If the path exists but is not a ItemStack, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a ItemStack and return + * appropriately. + * + * @param path Path of the ItemStack to check. + * @return Whether or not the specified path is an ItemStack. + */ + public boolean isItemStack(String path); + + + /** + * Gets the requested ConfigurationSection by path. + * <p> + * If the ConfigurationSection does not exist but a default value has been specified, this + * will return the default value. If the ConfigurationSection does not exist and no default + * value was specified, this will return null. + * + * @param path Path of the ConfigurationSection to get. + * @return Requested ConfigurationSection. + */ + public ConfigurationSection getConfigurationSection(String path); + + /** + * Checks if the specified path is a ConfigurationSection. + * <p> + * If the path exists but is not a ConfigurationSection, this will return false. If the path does not + * exist, this will return false. If the path does not exist but a default value + * has been specified, this will check if that default value is a ConfigurationSection and return + * appropriately. + * + * @param path Path of the ConfigurationSection to check. + * @return Whether or not the specified path is a ConfigurationSection. + */ + public boolean isConfigurationSection(String path); + + /** + * Gets the equivalent {@link ConfigurationSection} from the default {@link Configuration} defined in {@link #getRoot()}. + * <p> + * If the root contains no defaults, or the defaults doesn't contain a value + * for this path, or the value at this path is not a {@link ConfigurationSection} then + * this will return null. + * + * @return Equivalent section in root configuration + */ + public ConfigurationSection getDefaultSection(); + + /** + * Sets the default value in the root at the given path as provided. + * <p> + * If no source {@link Configuration} was provided as a default collection, + * then a new {@link MemoryConfiguration} will be created to hold the new default + * value. + * <p> + * If value is null, the value will be removed from the default Configuration source. + * <p> + * If the value as returned by {@link #getDefaultSection()} is null, + * then this will create a new section at the path, replacing anything that + * may have existed there previously. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(String path, Object value); +} diff --git a/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java new file mode 100644 index 00000000..3057712f --- /dev/null +++ b/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java @@ -0,0 +1,39 @@ +package org.bukkit.configuration; + +/** + * Exception thrown when attempting to load an invalid {@link Configuration} + */ +public class InvalidConfigurationException extends Exception { + /** + * Creates a new instance of InvalidConfigurationException without a message or cause. + */ + public InvalidConfigurationException() {} + + /** + * Constructs an instance of InvalidConfigurationException with the specified message. + * + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg) { + super(msg); + } + + /** + * Constructs an instance of InvalidConfigurationException with the specified cause. + * + * @param cause The cause of the exception. + */ + public InvalidConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs an instance of InvalidConfigurationException with the specified message and cause. + * + * @param cause The cause of the exception. + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/bukkit/configuration/MemoryConfiguration.java b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java new file mode 100644 index 00000000..5fffc593 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemoryConfiguration.java @@ -0,0 +1,85 @@ +package org.bukkit.configuration; + +import java.util.Map; + +/** + * This is a {@link Configuration} implementation that does not save or load + * from any source, and stores all values in memory only. + * This is useful for temporary Configurations for providing defaults. + */ +public class MemoryConfiguration extends MemorySection implements Configuration { + protected Configuration defaults; + protected MemoryConfigurationOptions options; + + /** + * Creates an empty {@link MemoryConfiguration} with no default values. + */ + public MemoryConfiguration() {} + + /** + * Creates an empty {@link MemoryConfiguration} using the specified {@link Configuration} + * as a source for all default values. + * + * @param defaults Default value provider + * @throws IllegalArgumentException Thrown if defaults is null + */ + public MemoryConfiguration(Configuration defaults) { + this.defaults = defaults; + } + + @Override + public void addDefault(String path, Object value) { + if (path == null) { + throw new IllegalArgumentException("Path may not be null"); + } + + if (defaults == null) { + defaults = new MemoryConfiguration(); + } + + defaults.set(path, value); + } + + public void addDefaults(Map<String, Object> defaults) { + if (defaults == null) { + throw new IllegalArgumentException("Defaults may not be null"); + } + + for (Map.Entry<String, Object> entry : defaults.entrySet()) { + addDefault(entry.getKey(), entry.getValue()); + } + } + + public void addDefaults(Configuration defaults) { + if (defaults == null) { + throw new IllegalArgumentException("Defaults may not be null"); + } + + addDefaults(defaults.getValues(true)); + } + + public void setDefaults(Configuration defaults) { + if (defaults == null) { + throw new IllegalArgumentException("Defaults may not be null"); + } + + this.defaults = defaults; + } + + public Configuration getDefaults() { + return defaults; + } + + @Override + public ConfigurationSection getParent() { + return null; + } + + public MemoryConfigurationOptions options() { + if (options == null) { + options = new MemoryConfigurationOptions(this); + } + + return options; + } +} diff --git a/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java new file mode 100644 index 00000000..93d3e50c --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java @@ -0,0 +1,27 @@ +package org.bukkit.configuration; + +/** + * Various settings for controlling the input and output of a {@link MemoryConfiguration} + */ +public class MemoryConfigurationOptions extends ConfigurationOptions { + protected MemoryConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override + public MemoryConfiguration configuration() { + return (MemoryConfiguration)super.configuration(); + } + + @Override + public MemoryConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public MemoryConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/MemorySection.java b/src/main/java/org/bukkit/configuration/MemorySection.java new file mode 100644 index 00000000..4709e71b --- /dev/null +++ b/src/main/java/org/bukkit/configuration/MemorySection.java @@ -0,0 +1,667 @@ +package org.bukkit.configuration; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import java.io.File; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.util.Vector; + +/** + * A type of {@link ConfigurationSection} that is stored in memory. + */ +public class MemorySection implements ConfigurationSection { + protected final Map<String, Object> map = new LinkedHashMap<String, Object>(); + private final Configuration root; + private final ConfigurationSection parent; + private final String path; + private final String fullPath; + + /** + * Creates an empty MemorySection for use as a root {@link Configuration} section. + * <p> + * Note that calling this without being yourself a {@link Configuration} will throw an + * exception! + * + * @throws IllegalStateException Thrown if this is not a {@link Configuration} root. + */ + protected MemorySection() { + if (!(this instanceof Configuration)) { + throw new IllegalStateException("Cannot contruct a root MemorySection when not a Configuration"); + } + + this.path = ""; + this.fullPath = ""; + this.parent = null; + this.root = (Configuration)this; + } + + /** + * Creates an empty MemorySection with the specified parent and path. + * + * @param parent Parent section that contains this own section. + * @param path Path that you may access this section from via the root {@link Configuration}. + * @throws IllegalArgumentException Thrown is parent or path is null, or if parent contains no root Configuration. + */ + protected MemorySection(ConfigurationSection parent, String path) { + if (parent == null) { + throw new IllegalArgumentException("Parent cannot be null"); + } + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + this.path = path; + this.parent = parent; + this.root = parent.getRoot(); + + if (root == null) { + throw new IllegalArgumentException("Path cannot be orphaned"); + } + + this.fullPath = createPath(parent, path); + } + + public Set<String> getKeys(boolean deep) { + Set<String> result = new LinkedHashSet<String>(); + + if (getRoot().options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.addAll(defaults.getKeys(deep)); + } + } + + mapChildrenKeys(result, this, deep); + + return result; + } + + public Map<String, Object> getValues(boolean deep) { + Map<String, Object> result = new LinkedHashMap<String, Object>(); + + if (getRoot().options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.putAll(defaults.getValues(deep)); + } + } + + mapChildrenValues(result, this, deep); + + return result; + } + + public boolean contains(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + return get(path) != null; + } + + public boolean isSet(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + if (getRoot().options().copyDefaults()) { + return contains(path); + } else { + return get(path, null) != null; + } + } + + public String getCurrentPath() { + return fullPath; + } + + public String getName() { + return path; + } + + public Configuration getRoot() { + return root; + } + + public ConfigurationSection getParent() { + return parent; + } + + public void addDefault(String path, Object value) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + if (root == null) { + throw new IllegalStateException("Cannot set default on orphaned section"); + } else { + root.addDefault(createPath(this, path), value); + } + } + + public ConfigurationSection getDefaultSection() { + if (getRoot() == null) { + return null; + } + + Configuration defaults = getRoot().getDefaults(); + + if (defaults != null) { + if (defaults.isConfigurationSection(getCurrentPath())) { + return defaults.getConfigurationSection(getCurrentPath()); + } + } + + return null; + } + + public void set(String path, Object value) { + String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator()))); + ConfigurationSection section = this; + + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } else if (path.length() == 0) { + throw new IllegalArgumentException("Cannot set to an empty path"); + } + + for (int i = 0; i < split.length - 1; i++) { + ConfigurationSection last = section; + + section = last.getConfigurationSection(split[i]); + + if (section == null) { + section = last.createSection(split[i]); + } + } + + String key = split[split.length - 1]; + + if (section == this) { + map.put(key, prepForStorage(value)); + } else { + section.set(key, value); + } + } + + public Object get(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + return get(path, getDefault(path)); + } + + public Object get(String path, Object def) { + Object result = null; + String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator()))); + ConfigurationSection section = this; + + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } else if (path.length() == 0) { + return this; + } + + for (int i = 0; (i < split.length - 1) && (section != null); i++) { + section = getConfigurationSection(split[i]); + } + + String key = split[split.length - 1]; + + if (section == this) { + result = map.get(key); + } else if (section != null) { + result = section.get(key); + } + + return (result == null) ? def : result; + } + + public ConfigurationSection createSection(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } else if (path.length() == 0) { + throw new IllegalArgumentException("Cannot create section at empty path"); + } + + String[] split = path.split(Pattern.quote(Character.toString(getRoot().options().pathSeparator()))); + ConfigurationSection section = this; + + for (int i = 0; i < split.length - 1; i++) { + ConfigurationSection last = section; + + section = getConfigurationSection(split[i]); + + if (section == null) { + section = last.createSection(split[i]); + } + } + + String key = split[split.length - 1]; + + if (section == this) { + ConfigurationSection result = new MemorySection(this, key); + map.put(key, result); + return result; + } else { + return section.createSection(key); + } + } + + // Primitives + public String getString(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getString(path, (def instanceof String) ? (String)def : null); + } + + public String getString(String path, String def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof String) ? (String)val : def; + } + + public boolean isString(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof String; + } + + public int getInt(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getInt(path, (def instanceof Integer) ? (Integer)def : 0); + } + + public int getInt(String path, int def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof Integer) ? (Integer)val : def; + } + + public boolean isInt(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof Integer; + } + + public boolean getBoolean(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getBoolean(path, (def instanceof Boolean) ? (Boolean)def : false); + } + + public boolean getBoolean(String path, boolean def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof Boolean) ? (Boolean)val : def; + } + + public boolean isBoolean(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof Boolean; + } + + public double getDouble(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getDouble(path, (def instanceof Double) ? (Double)def : 0); + } + + public double getDouble(String path, double def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof Double) ? (Double)val : def; + } + + public boolean isDouble(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof Double; + } + + public long getLong(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getLong(path, (def instanceof Long) ? (Long)def : 0); + } + + public long getLong(String path, long def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof Long) ? (Long)val : def; + } + + public boolean isLong(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof Long; + } + +// Java + public List getList(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getList(path, (def instanceof List) ? (List)def : null); + } + + public List getList(String path, List def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof List) ? (List)val : def; + } + + public boolean isList(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof List; + } + +// Bukkit + public Vector getVector(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getVector(path, (def instanceof Vector) ? (Vector)def : null); + } + + public Vector getVector(String path, Vector def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof Vector) ? (Vector)val : def; + } + + public boolean isVector(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof Vector; + } + + public OfflinePlayer getOfflinePlayer(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getOfflinePlayer(path, (def instanceof OfflinePlayer) ? (OfflinePlayer)def : null); + } + + public OfflinePlayer getOfflinePlayer(String path, OfflinePlayer def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof OfflinePlayer) ? (OfflinePlayer)val : def; + } + + public boolean isOfflinePlayer(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof OfflinePlayer; + } + + public ItemStack getItemStack(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object def = getDefault(path); + return getItemStack(path, (def instanceof ItemStack) ? (ItemStack)def : null); + } + + public ItemStack getItemStack(String path, ItemStack def) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, def); + return (val instanceof ItemStack) ? (ItemStack)val : def; + } + + public boolean isItemStack(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof ItemStack; + } + + public ConfigurationSection getConfigurationSection(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path, getDefault(path)); + return (val instanceof ConfigurationSection) ? (ConfigurationSection)val : null; + } + + public boolean isConfigurationSection(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Object val = get(path); + return val instanceof ConfigurationSection; + } + + protected Object prepForStorage(Object input) { + if (input == null) { + throw new IllegalArgumentException("Cannot store null"); + } + + if (isPrimitiveWrapper(input) || isNaturallyStorable(input)) { + return input; + } else if (input instanceof ConfigurationSerializable) { + return input; + } + + throw new IllegalArgumentException("Cannot store " + input + " into " + this + ", unsupported class"); + } + + protected boolean isPrimitiveWrapper(Object input) { + return input instanceof Integer || input instanceof Boolean || + input instanceof Character || input instanceof Byte || + input instanceof Short || input instanceof Double || + input instanceof Long || input instanceof Float; + } + + protected boolean isNaturallyStorable(Object input) { + return input instanceof List || input instanceof Iterable || + input instanceof String || input instanceof File || + input instanceof Enum; + } + + protected Object getDefault(String path) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + + Configuration defaults = root.getDefaults(); + return (defaults == null) ? null : defaults.get(createPath(this, path)); + } + + protected void mapChildrenKeys(Set<String> output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection)section; + + for (Map.Entry<String, Object> entry : sec.map.entrySet()) { + output.add(createPath(section, entry.getKey(), this)); + + if ((deep) && (entry.getValue() instanceof ConfigurationSection)) { + ConfigurationSection subsection = (ConfigurationSection)entry.getValue(); + mapChildrenKeys(output, subsection, deep); + } + } + } else { + Set<String> keys = section.getKeys(deep); + + for (String key : keys) { + output.add(createPath(section, key, this)); + } + } + } + + protected void mapChildrenValues(Map<String, Object> output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection)section; + + for (Map.Entry<String, Object> entry : sec.map.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + + if (entry.getValue() instanceof ConfigurationSection) { + if (deep) { + mapChildrenValues(output, (ConfigurationSection)entry.getValue(), deep); + } + } + } + } else { + Map<String, Object> values = section.getValues(deep); + + for (Map.Entry<String, Object> entry : values.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + } + } + } + + /** + * Creates a full path to the given {@link ConfigurationSection} from its root {@link Configuration}. + * <p> + * You may use this method for any given {@link ConfigurationSection}, not only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key) { + return createPath(section, key, (section == null) ? null : section.getRoot()); + } + + + /** + * Creates a relative path to the given {@link ConfigurationSection} from the given relative section. + * <p> + * You may use this method for any given {@link ConfigurationSection}, not only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @param relativeTo Section to create the path relative to. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key, ConfigurationSection relativeTo) { + StringBuilder builder = new StringBuilder(); + + if (section != null) { + for (ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) { + if (builder.length() > 0) { + builder.insert(0, section.getRoot().options().pathSeparator()); + } + + builder.insert(0, parent.getName()); + } + } + + if ((key != null) && (key.length() > 0)) { + if (builder.length() > 0) { + builder.append(section.getRoot().options().pathSeparator()); + } + + builder.append(key); + } + + return builder.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + builder.append(getClass().getSimpleName()); + builder.append("[path='"); + builder.append(getCurrentPath()); + builder.append("', root='"); + builder.append(root.getClass().getSimpleName()); + builder.append("']"); + + return builder.toString(); + } +} diff --git a/src/main/java/org/bukkit/configuration/file/FileConfiguration.java b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java new file mode 100644 index 00000000..41ec8a0d --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/FileConfiguration.java @@ -0,0 +1,191 @@ +package org.bukkit.configuration.file; + +import com.google.common.io.Files; +import org.bukkit.configuration.InvalidConfigurationException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.MemoryConfiguration; + +/** + * This is a base class for all File based implementations of {@link Configuration} + */ +public abstract class FileConfiguration extends MemoryConfiguration { + /** + * Creates an empty {@link FileConfiguration} with no default values. + */ + public FileConfiguration() { + super(); + } + + /** + * Creates an empty {@link FileConfiguration} using the specified {@link Configuration} + * as a source for all default values. + * + * @param defaults Default value provider + */ + public FileConfiguration(Configuration defaults) { + super(defaults); + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + * <p> + * If the file does not exist, it will be created. If already exists, it will + * be overwritten. If it cannot be overwritten or created, an exception will be thrown. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(File file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + Files.createParentDirs(file); + + String data = saveToString(); + + FileWriter writer = new FileWriter(file); + + try { + writer.write(data); + } finally { + writer.close(); + } + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + * <p> + * If the file does not exist, it will be created. If already exists, it will + * be overwritten. If it cannot be overwritten or created, an exception will be thrown. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(String file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + save(new File(file)); + } + + /** + * Saves this {@link FileConfiguration} to a string, and returns it. + * + * @return String containing this configuration. + */ + public abstract String saveToString(); + + /** + * Loads this {@link FileConfiguration} from the specified location. + * <p> + * All the values contained within this configuration will be removed, leaving + * only settings and defaults, and the new values will be loaded from the given file. + * <p> + * If the file cannot be loaded for any reason, an exception will be thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + load(new FileInputStream(file)); + } + + /** + * Loads this {@link FileConfiguration} from the specified stream. + * <p> + * All the values contained within this configuration will be removed, leaving + * only settings and defaults, and the new values will be loaded from the given stream. + * + * @param stream Stream to load from + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration. + * @throws IllegalArgumentException Thrown when stream is null. + */ + public void load(InputStream stream) throws IOException, InvalidConfigurationException { + if (stream == null) { + throw new IllegalArgumentException("Stream cannot be null"); + } + + InputStreamReader reader = new InputStreamReader(stream); + StringBuilder builder = new StringBuilder(); + BufferedReader input = new BufferedReader(reader); + + try { + String line; + + while ((line = input.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + } finally { + input.close(); + } + + loadFromString(builder.toString()); + } + + /** + * Loads this {@link FileConfiguration} from the specified location. + * <p> + * All the values contained within this configuration will be removed, leaving + * only settings and defaults, and the new values will be loaded from the given file. + * <p> + * If the file cannot be loaded for any reason, an exception will be thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(String file) throws FileNotFoundException, IOException, InvalidConfigurationException { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + load(new File(file)); + } + + /** + * Loads this {@link FileConfiguration} from the specified string, as opposed to from file. + * <p> + * All the values contained within this configuration will be removed, leaving + * only settings and defaults, and the new values will be loaded from the given string. + * <p> + * If the string is invalid in any way, an exception will be thrown. + * + * @param contents Contents of a Configuration to load. + * @throws InvalidConfigurationException Thrown if the specified string is invalid. + * @throws IllegalArgumentException Thrown if contents is null. + */ + public abstract void loadFromString(String contents) throws InvalidConfigurationException; + + @Override + public FileConfigurationOptions options() { + if (options == null) { + options = new FileConfigurationOptions(this); + } + + return (FileConfigurationOptions)options; + } +}
\ No newline at end of file diff --git a/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java new file mode 100644 index 00000000..80427204 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java @@ -0,0 +1,67 @@ +package org.bukkit.configuration.file; + +import org.bukkit.configuration.*; + +/** + * Various settings for controlling the input and output of a {@link FileConfiguration} + */ +public class FileConfigurationOptions extends MemoryConfigurationOptions { + private String header = null; + + protected FileConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override + public FileConfiguration configuration() { + return (FileConfiguration)super.configuration(); + } + + @Override + public FileConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public FileConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + /** + * Gets the header that will be applied to the top of the saved output. + * <p> + * This header will be commented out and applied directly at the top of the + * generated output of the {@link FileConfiguration}. It is not required to + * include a newline at the end of the header as it will automatically be applied, + * but you may include one if you wish for extra spacing. + * <p> + * Null is a valid value which will indicate that no header is to be applied. + * The default value is null. + * + * @return Header + */ + public String header() { + return header; + } + + /** + * Sets the header that will be applied to the top of the saved output. + * <p> + * This header will be commented out and applied directly at the top of the + * generated output of the {@link FileConfiguration}. It is not required to + * include a newline at the end of the header as it will automatically be applied, + * but you may include one if you wish for extra spacing. + * <p> + * Null is a valid value which will indicate that no header is to be applied. + * The default value is null. + * + * @param value New header + * @return This object, for chaining + */ + public FileConfigurationOptions header(String value) { + this.header = value; + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java new file mode 100644 index 00000000..2ce6fcba --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java @@ -0,0 +1,228 @@ +package org.bukkit.configuration.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.representer.Representer; + +/** + * An implementation of {@link Configuration} which saves all files in Yaml. + */ +public class YamlConfiguration extends FileConfiguration { + protected static final String COMMENT_PREFIX = "# "; + protected static final String BLANK_CONFIG = "{}\n"; + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new Representer(); + private final Yaml yaml = new Yaml(new SafeConstructor(), yamlRepresenter, yamlOptions); + + @Override + public String saveToString() { + Map<String, Object> output = new LinkedHashMap<String, Object>(); + + yamlOptions.setIndent(options().indent()); + yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + serializeValues(output, getValues(false)); + + String dump = yaml.dump(output); + + if (dump.equals(BLANK_CONFIG)) { + dump = ""; + } + + return buildHeader() + dump; + } + + @Override + public void loadFromString(String contents) throws InvalidConfigurationException { + if (contents == null) { + throw new IllegalArgumentException("Contents cannot be null"); + } + + Map<String, Object> input; + try { + input = (Map<String, Object>)yaml.load(contents); + } catch (Throwable ex) { + throw new InvalidConfigurationException("Specified contents is not a valid Configuration", ex); + } + + deserializeValues(input, this); + } + + protected void deserializeValues(Map<String, Object> input, ConfigurationSection section) throws InvalidConfigurationException { + if (input == null) { + return; + } + + for (Map.Entry<String, Object> entry : input.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof Map) { + Map<String, Object> subvalues; + + try { + subvalues = (Map<String, Object>) value; + } catch (ClassCastException ex) { + throw new InvalidConfigurationException("Map found where type is not <String, Object>", ex); + } + + if (subvalues.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { + try { + ConfigurationSerializable serializable = ConfigurationSerialization.deserializeObject(subvalues); + section.set(entry.getKey(), serializable); + } catch (IllegalArgumentException ex) { + throw new InvalidConfigurationException("Could not deserialize object", ex); + } + } else { + ConfigurationSection subsection = section.createSection(entry.getKey()); + deserializeValues(subvalues, subsection); + } + } else { + section.set(entry.getKey(), entry.getValue()); + } + } + } + + protected void serializeValues(Map<String, Object> output, Map<String, Object> input) { + if (input == null) { + return; + } + + for (Map.Entry<String, Object> entry : input.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof ConfigurationSection) { + ConfigurationSection subsection = (ConfigurationSection)entry.getValue(); + Map<String, Object> subvalues = new LinkedHashMap<String, Object>(); + + serializeValues(subvalues, subsection.getValues(false)); + value = subvalues; + } else if (value instanceof ConfigurationSerializable) { + ConfigurationSerializable serializable = (ConfigurationSerializable)value; + Map<String, Object> subvalues = new LinkedHashMap<String, Object>(); + subvalues.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass())); + + serializeValues(subvalues, serializable.serialize()); + value = subvalues; + } else if ((!isPrimitiveWrapper(value)) && (!isNaturallyStorable(value))) { + throw new IllegalStateException("Configuration contains non-serializable values, cannot process"); + } + + if (value != null) { + output.put(entry.getKey(), value); + } + } + } + + protected String buildHeader() { + String header = options().header(); + + if (header == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + String[] lines = header.split("\r?\n"); + + for (int i = 0; i < lines.length; i++) { + builder.append(COMMENT_PREFIX); + builder.append(lines[i]); + builder.append("\n"); + } + + return builder.toString(); + } + + @Override + public YamlConfigurationOptions options() { + if (options == null) { + options = new YamlConfigurationOptions(this); + } + + return (YamlConfigurationOptions)options; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given file. + * <p> + * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be returned. + * + * @param file Input file + * @return Resulting configuration + * @throws IllegalArgumentException Thrown is file is null + */ + public static YamlConfiguration loadConfiguration(File file) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(file); + } catch (FileNotFoundException ex) { + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } catch (InvalidConfigurationException ex) { + if (ex.getCause() instanceof YAMLException) { + Bukkit.getLogger().severe("Config file " + file + " isn't valid! " + ex.getCause()); + } else if ((ex.getCause() == null) || (ex.getCause() instanceof ClassCastException)) { + Bukkit.getLogger().severe("Config file " + file + " isn't valid!"); + } else { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file + ": " + ex.getCause().getClass(), ex); + } + } + + return config; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given stream. + * <p> + * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be returned. + * + * @param stream Input stream + * @return Resulting configuration + * @throws IllegalArgumentException Thrown is stream is null + */ + public static YamlConfiguration loadConfiguration(InputStream stream) { + if (stream == null) { + throw new IllegalArgumentException("Stream cannot be null"); + } + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(stream); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration", ex); + } catch (InvalidConfigurationException ex) { + if (ex.getCause() instanceof YAMLException) { + Bukkit.getLogger().severe("Config file isn't valid! " + ex.getCause()); + } else if ((ex.getCause() == null) || (ex.getCause() instanceof ClassCastException)) { + Bukkit.getLogger().severe("Config file isn't valid!"); + } else { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration: " + ex.getCause().getClass(), ex); + } + } + + return config; + } +} diff --git a/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java new file mode 100644 index 00000000..eee75fb1 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java @@ -0,0 +1,63 @@ +package org.bukkit.configuration.file; + +/** + * Various settings for controlling the input and output of a {@link YamlConfiguration} + */ +public class YamlConfigurationOptions extends FileConfigurationOptions { + private int indent = 2; + + protected YamlConfigurationOptions(YamlConfiguration configuration) { + super(configuration); + } + + @Override + public YamlConfiguration configuration() { + return (YamlConfiguration)super.configuration(); + } + + @Override + public YamlConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override + public YamlConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + @Override + public YamlConfigurationOptions header(String value) { + super.header(value); + return this; + } + + /** + * Gets how much spaces should be used to indent each line. + * <p> + * The minimum value this may be is 2, and the maximum is 9. + * + * @return How much to indent by + */ + public int indent() { + return indent; + } + + /** + * Sets how much spaces should be used to indent each line. + * <p> + * The minimum value this may be is 2, and the maximum is 9. + * + * @param value New indent + * @return This object, for chaining + */ + public YamlConfigurationOptions indent(int value) { + if ((indent < 2) || (value > 9)) { + throw new IllegalArgumentException("Indent must be between 1 and 10 characters"); + } + + this.indent = value; + return this; + } +} diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java new file mode 100644 index 00000000..6e2af873 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java @@ -0,0 +1,24 @@ +package org.bukkit.configuration.serialization; + +import java.util.Map; + +/** + * Represents an object that may be serialized. + * <p> + * These objects MUST implement one of the following, in addition to the methods + * as defined by this interface: + * - A static method "deserialize" that accepts a single {@link Map<String, Object>} and returns the class. + * - A static method "valueOf" that accepts a single {@link Map<String, Object>} and returns the class. + * - A constructor that accepts a single {@link Map<String, Object>}. + */ +public interface ConfigurationSerializable { + /** + * Creates a Map representation of this class. + * <p> + * This class must provide a method to restore this class, as defined in the + * {@link ConfigurationSerializable} interface javadocs. + * + * @return Map containing the current state of this class + */ + public Map<String, Object> serialize(); +} diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java new file mode 100644 index 00000000..ce594136 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java @@ -0,0 +1,251 @@ +package org.bukkit.configuration.serialization; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +/** + * Utility class for storing and retrieving classes for {@link Configuration}. + */ +public class ConfigurationSerialization { + public static final String SERIALIZED_TYPE_KEY = "=="; + private final Class<? extends ConfigurationSerializable> clazz; + private static Map<String, Class<? extends ConfigurationSerializable>> aliases = new HashMap<String, Class<? extends ConfigurationSerializable>>(); + + static { + registerClass(Vector.class); + registerClass(BlockVector.class); + } + + protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) { + this.clazz = clazz; + } + + protected Method getMethod(String name, boolean isStatic) { + try { + Method method = clazz.getDeclaredMethod(name, Map.class); + + if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) { + return null; + } + if (Modifier.isStatic(method.getModifiers()) != isStatic) { + return null; + } + + return method; + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + protected Constructor<? extends ConfigurationSerializable> getConstructor() { + try { + return clazz.getConstructor(Map.class); + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + protected ConfigurationSerializable deserializeViaMethod(Method method, Map<String, Object> args) { + try { + ConfigurationSerializable result = (ConfigurationSerializable)method.invoke(null, args); + + if (result == null) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null"); + } else { + return result; + } + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization", ex); + } + + return null; + } + + protected ConfigurationSerializable deserializeViaCtor(Constructor<? extends ConfigurationSerializable> ctor, Map<String, Object> args) { + try { + return ctor.newInstance(args); + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization", ex); + } + + return null; + } + + public ConfigurationSerializable deserialize(Map<String, Object> args) { + if (args == null) { + throw new IllegalArgumentException("Args must not be null"); + } + + ConfigurationSerializable result = null; + Method method = null; + + if (result == null) { + method = getMethod("deserialize", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + method = getMethod("valueOf", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + Constructor<? extends ConfigurationSerializable> constructor = getConstructor(); + + if (constructor != null) { + result = deserializeViaCtor(constructor, args); + } + } + + return result; + } + + /** + * Attempts to deserialize the given arguments into a new instance of the given class. + * <p> + * The class must implement {@link ConfigurationSerializable}, including the extra methods + * as specified in the javadoc of ConfigurationSerializable. + * <p> + * If a new instance could not be made, an example being the class not fully implementing + * the interface, null will be returned. + * + * @param args Arguments for deserialization + * @param clazz Class to deserialize into + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map<String, Object> args, Class<? extends ConfigurationSerializable> clazz) { + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Attempts to deserialize the given arguments into a new instance of the given class. + * <p> + * The class must implement {@link ConfigurationSerializable}, including the extra methods + * as specified in the javadoc of ConfigurationSerializable. + * <p> + * If a new instance could not be made, an example being the class not fully implementing + * the interface, null will be returned. + * + * @param args Arguments for deserialization + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map<String, Object> args) { + Class<? extends ConfigurationSerializable> clazz = null; + + if (args.containsKey(SERIALIZED_TYPE_KEY)) { + try { + String alias = (String)args.get(SERIALIZED_TYPE_KEY); + + if (alias == null) { + throw new IllegalArgumentException("Specified class does not exist ('" + alias + ")'"); + } else { + clazz = getClassByAlias(alias); + } + } catch (ClassCastException ex) { + ex.fillInStackTrace(); + throw ex; + } + } else { + throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')"); + } + + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Registers the given {@link ConfigurationSerializable} class by its alias + * + * @param clazz Class to register + */ + public static void registerClass(Class<? extends ConfigurationSerializable> clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate == null ) { + registerClass(clazz, getAlias(clazz)); + registerClass(clazz, clazz.getName()); + } + } + + /** + * Registers the given alias to the specified {@link ConfigurationSerializable} class + * + * @param clazz Class to register + * @param alias Alias to register as + */ + public static void registerClass(Class<? extends ConfigurationSerializable> clazz, String alias) { + aliases.put(alias, clazz); + } + + /** + * Unregisters the specified alias to a {@link ConfigurationSerializable} + * + * @param alias Alias to unregister + */ + public static void unregisterClass(String alias) { + aliases.remove(alias); + } + + /** + * Unregisters any aliases for the specified {@link ConfigurationSerializable} class + * + * @param clazz Class to unregister + */ + public static void unregisterClass(Class<? extends ConfigurationSerializable> clazz) { + while (aliases.values().remove(clazz)); + } + + /** + * Attempts to get a registered {@link ConfigurationSerializable} class by its alias + * + * @param alias Alias of the serializable + * @return Registered class, or null if not found + */ + public static Class<? extends ConfigurationSerializable> getClassByAlias(String alias) { + return aliases.get(alias); + } + + /** + * Gets the correct alias for the given {@link ConfigurationSerializable} class + * + * @param clazz Class to get alias for + * @return Alias to use for the class + */ + public static String getAlias(Class<? extends ConfigurationSerializable> clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate != null) { + if ((delegate.value() == null) || (delegate.value() == clazz)) { + delegate = null; + } else { + return getAlias(delegate.value()); + } + } + + if (delegate == null) { + SerializableAs alias = clazz.getAnnotation(SerializableAs.class); + + if ((alias != null) && (alias.value() != null)) { + return alias.value(); + } + } + + return clazz.getName(); + } +}
\ No newline at end of file diff --git a/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java new file mode 100644 index 00000000..9e38f1a0 --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java @@ -0,0 +1,21 @@ +package org.bukkit.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents a {@link ConfigurationSerializable} that will delegate all deserialization to another + * {@link ConfigurationSerializable} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DelegateDeserialization { + /** + * Which class should be used as a delegate for this classes deserialization + * + * @return Delegate class + */ + public Class<? extends ConfigurationSerializable> value(); +} diff --git a/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java new file mode 100644 index 00000000..adfc0aac --- /dev/null +++ b/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java @@ -0,0 +1,28 @@ +package org.bukkit.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents an "alias" that a {@link ConfigurationSerializable} may be stored as. + * If this is not present on a {@link ConfigurationSerializable} class, it will use the + * fully qualified name of the class. + * <p> + * Using this annotation on any other class than a {@link ConfigurationSerializable} will + * have no effect. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SerializableAs { + /** + * This is the name your class will be stored and retrieved as. + * <p> + * This name MUST be unique. We recommend using names such as "MyPluginThing" instead of + * "Thing". + * + * @return Name to serialize the class as. + */ + public String value(); +} diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java index 61d43c18..b948ccdb 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1,12 +1,16 @@ package org.bukkit.inventory; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.material.MaterialData; /** * Represents a stack of items */ -public class ItemStack { +public class ItemStack implements Serializable, ConfigurationSerializable { private int type; private int amount = 0; private MaterialData data = null; @@ -208,4 +212,36 @@ public class ItemStack { hash = hash * 7 + 23 * getAmount(); // too bad these are mutable values... Q_Q return hash; } + + public Map<String, Object> serialize() { + Map<String, Object> result = new LinkedHashMap<String, Object>(); + + result.put("type", getType()); + + if (durability != 0) { + result.put("damage", durability); + } + + if (amount != 1) { + result.put("amount", amount); + } + + return result; + } + + public static ItemStack deserialize(Map<String, Object> args) { + Material type = Material.getMaterial((String)args.get("type")); + short damage = 0; + int amount = 1; + + if (args.containsKey("damage")) { + damage = (Short)args.get("damage"); + } + + if (args.containsKey("amount")) { + amount = (Integer)args.get("amount"); + } + + return new ItemStack(type, amount, damage); + } } diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java index 69ced916..37244293 100644 --- a/src/main/java/org/bukkit/plugin/Plugin.java +++ b/src/main/java/org/bukkit/plugin/Plugin.java @@ -2,8 +2,10 @@ package org.bukkit.plugin; import com.avaje.ebean.EbeanServer; import java.io.File; +import java.io.InputStream; import org.bukkit.Server; import org.bukkit.command.CommandExecutor; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.generator.ChunkGenerator; import org.bukkit.util.config.Configuration; @@ -33,6 +35,29 @@ public interface Plugin extends CommandExecutor { * @return The configuration */ public Configuration getConfiguration(); + + /** + * Gets a {@link FileConfiguration} for this plugin, read through "config.yml" + * <p> + * If there is a default config.yml embedded in this plugin, it will be provided + * as a default for this Configuration. + * + * @return Plugin configuration + */ + public FileConfiguration getConfig(); + + /** + * Gets an embedded resource in this plugin + * + * @param filename Filename of the resource + * @return File if found, otherwise null + */ + public InputStream getResource(String filename); + + /** + * Saves the {@link FileConfiguration} retrievable by {@link #getConfig()}. + */ + public void saveConfig(); /** * Gets the associated PluginLoader responsible for this plugin diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java index 93dc08cc..2645b83b 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java @@ -7,17 +7,27 @@ import com.avaje.ebean.config.ServerConfig; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.ddl.DdlGenerator; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginLoader; import org.bukkit.util.config.Configuration; +import org.yaml.snakeyaml.error.YAMLException; /** * Represents a Java plugin @@ -34,6 +44,8 @@ public abstract class JavaPlugin implements Plugin { private Configuration config = null; private boolean naggable = true; private EbeanServer ebean = null; + private FileConfiguration newConfig = null; + private File configFile = null; public JavaPlugin() {} @@ -99,10 +111,32 @@ public abstract class JavaPlugin implements Plugin { * the configuration file will have no values. * * @return The configuration. + * @deprecated See the new */ + @Deprecated public Configuration getConfiguration() { return config; } + + public FileConfiguration getConfig() { + return newConfig; + } + + public void saveConfig() { + try { + newConfig.save(configFile); + } catch (IOException ex) { + Logger.getLogger(JavaPlugin.class.getName()).log(Level.SEVERE, "Could not save config to " + configFile, ex); + } + } + + public InputStream getResource(String filename) { + if (filename == null) { + throw new IllegalArgumentException("Filename cannot be null"); + } + + return getClassLoader().getResourceAsStream(filename); + } /** * Returns the ClassLoader which holds this plugin @@ -153,8 +187,17 @@ public abstract class JavaPlugin implements Plugin { this.description = description; this.dataFolder = dataFolder; this.classLoader = classLoader; - this.config = new Configuration(new File(dataFolder, "config.yml")); + this.configFile = new File(dataFolder, "config.yml"); + this.config = new Configuration(configFile); this.config.load(); + this.newConfig = YamlConfiguration.loadConfiguration(configFile); + + InputStream defConfigStream = getResource("config.yml"); + if (defConfigStream != null) { + YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream); + + newConfig.setDefaults(defConfig); + } if (description.isDatabaseEnabled()) { ServerConfig db = new ServerConfig(); diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index ad69872e..bf9379bd 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -15,6 +15,9 @@ import java.util.jar.JarFile; import java.util.logging.Level; import java.util.regex.Pattern; import org.bukkit.Server; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.configuration.serialization.SerializableAs; import org.bukkit.event.CustomEventListener; import org.bukkit.event.Event; import org.bukkit.event.Listener; @@ -229,6 +232,20 @@ public class JavaPluginLoader implements PluginLoader { public void setClass(final String name, final Class<?> clazz) { if (!classes.containsKey(name)) { classes.put(name, clazz); + + if (ConfigurationSerializable.class.isAssignableFrom(clazz)) { + Class<? extends ConfigurationSerializable> serializable = (Class<? extends ConfigurationSerializable>)clazz; + ConfigurationSerialization.registerClass(serializable); + } + } + } + + public void removeClass(String name) { + Class<?> clazz = classes.remove(name); + + if (ConfigurationSerializable.class.isAssignableFrom(clazz)) { + Class<? extends ConfigurationSerializable> serializable = (Class<? extends ConfigurationSerializable>)clazz; + ConfigurationSerialization.unregisterClass(serializable); } } @@ -973,7 +990,7 @@ public class JavaPluginLoader implements PluginLoader { Set<String> names = loader.getClasses(); for (String name : names) { - classes.remove(name); + removeClass(name); } } } diff --git a/src/main/java/org/bukkit/util/BlockVector.java b/src/main/java/org/bukkit/util/BlockVector.java index 61932922..06d0b9cf 100644 --- a/src/main/java/org/bukkit/util/BlockVector.java +++ b/src/main/java/org/bukkit/util/BlockVector.java @@ -1,13 +1,15 @@ package org.bukkit.util; +import java.util.Map; +import org.bukkit.configuration.serialization.SerializableAs; + /** * A vector with a hash function that floors the X, Y, Z components, a la * BlockVector in WorldEdit. BlockVectors can be used in hash sets and * hash maps. Be aware that BlockVectors are mutable, but it is important * that BlockVectors are never changed once put into a hash set or hash map. - * - * @author sk89q */ +@SerializableAs("BlockVector") public class BlockVector extends Vector { /** @@ -109,4 +111,22 @@ public class BlockVector extends Vector { v.z = z; return v; } + + public static BlockVector deserialize(Map<String, Object> args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double)args.get("x"); + } + if (args.containsKey("y")) { + y = (Double)args.get("y"); + } + if (args.containsKey("z")) { + z = (Double)args.get("z"); + } + + return new BlockVector(x, y, z); + } } diff --git a/src/main/java/org/bukkit/util/Vector.java b/src/main/java/org/bukkit/util/Vector.java index 194f4ac3..d8e2289e 100644 --- a/src/main/java/org/bukkit/util/Vector.java +++ b/src/main/java/org/bukkit/util/Vector.java @@ -1,18 +1,21 @@ package org.bukkit.util; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Random; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; /** * Represents a mutable vector. Because the components of Vectors are mutable, * storing Vectors long term may be dangerous if passing code modifies the * Vector later. If you want to keep around a Vector, it may be wise to call * <code>clone()</code> in order to get a copy. - * - * @author sk89q */ -public class Vector implements Cloneable { +@SerializableAs("Vector") +public class Vector implements Cloneable, ConfigurationSerializable { private static final long serialVersionUID = -2657651106777219169L; private static Random random = new Random(); @@ -635,4 +638,32 @@ public class Vector implements Cloneable { public static Vector getRandom() { return new Vector(random.nextDouble(), random.nextDouble(), random.nextDouble()); } + + public Map<String, Object> serialize() { + Map<String, Object> result = new LinkedHashMap<String, Object>(); + + result.put("x", getX()); + result.put("y", getY()); + result.put("z", getZ()); + + return result; + } + + public static Vector deserialize(Map<String, Object> args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double)args.get("x"); + } + if (args.containsKey("y")) { + y = (Double)args.get("y"); + } + if (args.containsKey("z")) { + z = (Double)args.get("z"); + } + + return new Vector(x, y, z); + } } diff --git a/src/main/java/org/bukkit/util/config/Configuration.java b/src/main/java/org/bukkit/util/config/Configuration.java index bc9a465f..2259452e 100644 --- a/src/main/java/org/bukkit/util/config/Configuration.java +++ b/src/main/java/org/bukkit/util/config/Configuration.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.util.HashMap; import java.util.Map; +import org.bukkit.configuration.file.YamlConfiguration; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -52,12 +53,18 @@ import org.yaml.snakeyaml.representer.Representer; * <p>This class is currently incomplete. It is not yet possible to get a node. * </p> * + * @deprecated See {@link YamlConfiguration} */ +@Deprecated public class Configuration extends ConfigurationNode { private Yaml yaml; private File file; private String header = null; + /** + * @deprecated See {@link YamlConfiguration} + */ + @Deprecated public Configuration(File file) { super(new HashMap<String, Object>()); |