From 145c9750cdbee2a0339d53d9d3bfeeb67c39bc09 Mon Sep 17 00:00:00 2001 From: sk89q Date: Fri, 1 Apr 2011 22:11:57 -0700 Subject: [PATCH] Added YAML protection database driver. --- .../worldguard/bukkit/WorldConfiguration.java | 2 +- .../protection/GlobalRegionManager.java | 4 +- .../databases/AbstractProtectionDatabase.java | 47 ++ .../protection/databases/CSVDatabase.java | 23 +- .../protection/databases/YAMLDatabase.java | 232 +++++ .../worldguard/protection/flags/Flag.java | 4 + .../protection/regions/ProtectedRegion.java | 9 + .../worldguard/util/yaml/Configuration.java | 156 ++++ .../util/yaml/ConfigurationException.java | 37 + .../util/yaml/ConfigurationNode.java | 795 ++++++++++++++++++ 10 files changed, 1284 insertions(+), 25 deletions(-) create mode 100644 src/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java create mode 100644 src/com/sk89q/worldguard/protection/databases/YAMLDatabase.java create mode 100644 src/com/sk89q/worldguard/util/yaml/Configuration.java create mode 100644 src/com/sk89q/worldguard/util/yaml/ConfigurationException.java create mode 100644 src/com/sk89q/worldguard/util/yaml/ConfigurationNode.java diff --git a/src/com/sk89q/worldguard/bukkit/WorldConfiguration.java b/src/com/sk89q/worldguard/bukkit/WorldConfiguration.java index 3a90bbfc..4d845411 100644 --- a/src/com/sk89q/worldguard/bukkit/WorldConfiguration.java +++ b/src/com/sk89q/worldguard/bukkit/WorldConfiguration.java @@ -24,6 +24,7 @@ import com.sk89q.worldguard.blacklist.loggers.ConsoleLoggerHandler; import com.sk89q.worldguard.blacklist.loggers.DatabaseLoggerHandler; import com.sk89q.worldguard.blacklist.loggers.FileLoggerHandler; +import com.sk89q.worldguard.util.yaml.Configuration; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -32,7 +33,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.entity.CreatureType; -import org.bukkit.util.config.Configuration; /** * Holds the configuration for individual worlds. diff --git a/src/com/sk89q/worldguard/protection/GlobalRegionManager.java b/src/com/sk89q/worldguard/protection/GlobalRegionManager.java index 84d94782..5c442cfb 100644 --- a/src/com/sk89q/worldguard/protection/GlobalRegionManager.java +++ b/src/com/sk89q/worldguard/protection/GlobalRegionManager.java @@ -24,7 +24,7 @@ import com.sk89q.worldguard.bukkit.ConfigurationManager; import com.sk89q.worldguard.bukkit.WorldConfiguration; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.databases.JSONDatabase; +import com.sk89q.worldguard.protection.databases.YAMLDatabase; import com.sk89q.worldguard.protection.flags.StateFlag; import com.sk89q.worldguard.protection.managers.FlatRegionManager; import com.sk89q.worldguard.protection.managers.RegionManager; @@ -134,7 +134,7 @@ public void load(World world) { try { // Create a manager - RegionManager manager = new FlatRegionManager(new JSONDatabase(file)); + RegionManager manager = new FlatRegionManager(new YAMLDatabase(file)); managers.put(name, manager); manager.load(); diff --git a/src/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java b/src/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java new file mode 100644 index 00000000..1e54a4f9 --- /dev/null +++ b/src/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java @@ -0,0 +1,47 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldguard.protection.databases; + +import java.io.IOException; +import com.sk89q.worldguard.protection.managers.RegionManager; + +public abstract class AbstractProtectionDatabase implements ProtectionDatabase { + + /** + * Load the list of regions into a region manager. + * + * @throws IOException + */ + public void load(RegionManager manager) throws IOException { + load(); + manager.setRegions(getRegions()); + } + + /** + * Save the list of regions from a region manager. + * + * @throws IOException + */ + public void save(RegionManager manager) throws IOException { + setRegions(manager.getRegions()); + save(); + } + +} diff --git a/src/com/sk89q/worldguard/protection/databases/CSVDatabase.java b/src/com/sk89q/worldguard/protection/databases/CSVDatabase.java index b1c93c05..c34b5ae8 100644 --- a/src/com/sk89q/worldguard/protection/databases/CSVDatabase.java +++ b/src/com/sk89q/worldguard/protection/databases/CSVDatabase.java @@ -22,7 +22,6 @@ import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.StateFlag; import com.sk89q.worldguard.protection.flags.StateFlag.State; -import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; import java.util.HashMap; import java.util.LinkedHashMap; @@ -44,7 +43,7 @@ * * @author sk89q */ -public class CSVDatabase implements ProtectionDatabase { +public class CSVDatabase extends AbstractProtectionDatabase { private static Logger logger = Logger.getLogger("Minecraft.WorldGuard"); /** @@ -184,26 +183,6 @@ public void load() throws IOException { this.regions = regions; } - - /** - * Load the list of regions into a region manager. - * - * @throws IOException - */ - public void load(RegionManager manager) throws IOException { - load(); - manager.setRegions(regions); - } - - /** - * Save the list of regions from a region manager. - * - * @throws IOException - */ - public void save(RegionManager manager) throws IOException { - regions = manager.getRegions(); - save(); - } /** * Used to parse the specified domain in the CSV file. diff --git a/src/com/sk89q/worldguard/protection/databases/YAMLDatabase.java b/src/com/sk89q/worldguard/protection/databases/YAMLDatabase.java new file mode 100644 index 00000000..fda6fa76 --- /dev/null +++ b/src/com/sk89q/worldguard/protection/databases/YAMLDatabase.java @@ -0,0 +1,232 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldguard.protection.databases; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.DefaultFlag; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.yaml.Configuration; +import com.sk89q.worldguard.util.yaml.ConfigurationNode; + +public class YAMLDatabase extends AbstractProtectionDatabase { + + private static Logger logger = Logger.getLogger("Minecraft.WorldGuard"); + + private Configuration config; + private Map regions; + + public YAMLDatabase(File file) { + config = new Configuration(file); + } + + @Override + public void load() throws IOException { + config.load(); + + Map regionData = config.getNodes("regions"); + + // No regions are even configured + if (regionData == null) { + this.regions = new HashMap(); + return; + } + + regions = new HashMap(); + + for (Map.Entry entry : regionData.entrySet()) { + String id = entry.getKey().toLowerCase(); + ConfigurationNode node = entry.getValue(); + + String type = node.getString("type"); + ProtectedRegion region; + + try { + if (type == null) { + logger.warning("Undefined region type for region '" + id + '"'); + continue; + } else if (type.equals("cuboid")) { + Vector pt1 = checkNonNull(node.getVector("min")); + Vector pt2 = checkNonNull(node.getVector("max")); + BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); + BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); + region = new ProtectedCuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + Integer minY = checkNonNull(node.getInt("min-y")); + Integer maxY = checkNonNull(node.getInt("max-y")); + List points = node.getBlockVector2dList("points", null); + region = new ProtectedPolygonalRegion(id, points, minY, maxY); + } else { + logger.warning("Unknown region type for region '" + id + '"'); + continue; + } + + Integer priority = checkNonNull(node.getInt("priority")); + region.setPriority(priority); + setFlags(region, node.getNode("flags")); + region.setOwners(parseDomain(node.getNode("owners"))); + region.setMembers(parseDomain(node.getNode("members"))); + regions.put(id, region); + } catch (NullPointerException e) { + logger.warning("Missing data for region '" + id + '"'); + } + } + } + + private V checkNonNull(V val) throws NullPointerException { + if (val == null) { + throw new NullPointerException(); + } + + return val; + } + + private void setFlags(ProtectedRegion region, ConfigurationNode flagsData) { + if (flagsData == null) { + return; + } + + // @TODO: Make this better + for (Flag flag : DefaultFlag.getFlags()) { + Object o = flagsData.getProperty(flag.getName()); + if (o != null) { + setFlag(region, flag, o); + } + } + } + + private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) { + T val = flag.unmarshal(rawValue); + if (val == null) { + logger.warning("Failed to parse flag '" + flag.getName() + + "' with value '" + rawValue.toString() + "'"); + return; + } + region.setFlag(flag, val); + } + + private DefaultDomain parseDomain(ConfigurationNode node) { + if (node == null) { + return new DefaultDomain(); + } + + DefaultDomain domain = new DefaultDomain(); + + for (String name : node.getStringList("players", null)) { + domain.addPlayer(name); + } + + for (String name : node.getStringList("groups", null)) { + domain.addGroup(name); + } + + return domain; + } + + @Override + public void save() throws IOException { + config.clear(); + + for (Map.Entry entry : regions.entrySet()) { + ProtectedRegion region = entry.getValue(); + ConfigurationNode node = config.addNode("regions." + entry.getKey()); + + if (region instanceof ProtectedCuboidRegion) { + ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region; + node.setProperty("type", "cuboid"); + node.setProperty("min", cuboid.getMinimumPoint()); + node.setProperty("max", cuboid.getMaximumPoint()); + } else if (region instanceof ProtectedPolygonalRegion) { + ProtectedPolygonalRegion poly = (ProtectedPolygonalRegion) region; + node.setProperty("type", "poly2d"); + node.setProperty("min-y", poly.getMinimumPoint().getBlockY()); + node.setProperty("max-y", poly.getMaximumPoint().getBlockY()); + + List> points = new ArrayList>(); + for (BlockVector2D point : poly.getPoints()) { + Map data = new HashMap(); + data.put("x", point.getBlockX()); + data.put("z", point.getBlockZ()); + points.add(data); + } + + node.setProperty("points", points); + } else { + node.setProperty("type", region.getClass().getCanonicalName()); + } + + node.setProperty("priority", region.getPriority()); + node.setProperty("flags", getFlagData(region)); + node.setProperty("owners", getDomainData(region.getOwners())); + node.setProperty("members", getDomainData(region.getMembers())); + } + + config.save(); + } + + private Map getFlagData(ProtectedRegion region) { + Map flagData = new HashMap(); + + for (Map.Entry, Object> entry : region.getFlags().entrySet()) { + Flag flag = entry.getKey(); + addMarshalledFlag(flagData, flag, entry.getValue()); + } + + return flagData; + } + + @SuppressWarnings("unchecked") + private void addMarshalledFlag(Map flagData, + Flag flag, Object val) { + flagData.put(flag.getName(), flag.marshal((V) val)); + } + + private Map getDomainData(DefaultDomain domain) { + Map domainData = new HashMap(); + + domainData.put("players", domain.getPlayers()); + domainData.put("groups", domain.getGroups()); + + return domainData; + } + + @Override + public Map getRegions() { + return regions; + } + + @Override + public void setRegions(Map regions) { + this.regions = regions; + } + +} diff --git a/src/com/sk89q/worldguard/protection/flags/Flag.java b/src/com/sk89q/worldguard/protection/flags/Flag.java index 9ac71df9..a48dbd8b 100644 --- a/src/com/sk89q/worldguard/protection/flags/Flag.java +++ b/src/com/sk89q/worldguard/protection/flags/Flag.java @@ -51,4 +51,8 @@ public char getLegacyCode() { public abstract T parseInput(WorldGuardPlugin plugin, CommandSender sender, String input) throws InvalidFlagFormat; + + public abstract T unmarshal(Object o); + + public abstract Object marshal(T o); } diff --git a/src/com/sk89q/worldguard/protection/regions/ProtectedRegion.java b/src/com/sk89q/worldguard/protection/regions/ProtectedRegion.java index e9883b0b..0db4c8ba 100644 --- a/src/com/sk89q/worldguard/protection/regions/ProtectedRegion.java +++ b/src/com/sk89q/worldguard/protection/regions/ProtectedRegion.java @@ -282,6 +282,15 @@ public , V> void setFlag(T flag, V val) { flags.put(flag, val); } } + + /** + * Get the map of flags. + * + * @return + */ + public Map, Object> getFlags() { + return flags; + } /** * Get the number of blocks in this region diff --git a/src/com/sk89q/worldguard/util/yaml/Configuration.java b/src/com/sk89q/worldguard/util/yaml/Configuration.java new file mode 100644 index 00000000..1bcaa6db --- /dev/null +++ b/src/com/sk89q/worldguard/util/yaml/Configuration.java @@ -0,0 +1,156 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldguard.util.yaml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; +import com.sk89q.worldguard.util.yaml.ConfigurationException; +import com.sk89q.worldguard.util.yaml.ConfigurationNode; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.reader.UnicodeReader; +import org.yaml.snakeyaml.representer.Representer; + +/** + * YAML configuration loader. To use this class, construct it with path to + * a file and call its load() method. For specifying node paths in the + * various get*() methods, they support SK's path notation, allowing you to + * select child nodes by delimiting node names with periods. + * + *

+ * For example, given the following configuration file:

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

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

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

+ * + * @author sk89q + */ +public class Configuration extends ConfigurationNode { + private Yaml yaml; + private File file; + + public Configuration(File file) { + super(new HashMap()); + + DumperOptions options = new DumperOptions(); + options.setIndent(4); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + yaml = new Yaml(new SafeConstructor(), new Representer(), options); + + this.file = file; + } + + /** + * Loads the configuration file. + * + * @throws IOException + */ + public void load() throws IOException { + FileInputStream stream = null; + + try { + stream = new FileInputStream(file); + read(yaml.load(new UnicodeReader(stream))); + } catch (ConfigurationException e) { + root = new HashMap(); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + } + } + } + + /** + * Saves the configuration to disk. + * + * @throws IOException + */ + public void save() throws IOException { + FileOutputStream stream = null; + + File parent = file.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + + try { + stream = new FileOutputStream(file); + yaml.dump(root, new OutputStreamWriter(stream, "UTF-8")); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + } + } + } + + @SuppressWarnings("unchecked") + private void read(Object input) throws ConfigurationException { + try { + if ( null == input ) { + root = new HashMap(); + } else { + root = (Map)input; + } + } catch (ClassCastException e) { + throw new ConfigurationException("Root document must be an key-value structure"); + } + } + + /** + * This method returns an empty ConfigurationNode for using as a + * default in methods that select a node from a node list. + * @return + */ + public static ConfigurationNode getEmptyNode() { + return new ConfigurationNode(new HashMap()); + } +} diff --git a/src/com/sk89q/worldguard/util/yaml/ConfigurationException.java b/src/com/sk89q/worldguard/util/yaml/ConfigurationException.java new file mode 100644 index 00000000..08b553d7 --- /dev/null +++ b/src/com/sk89q/worldguard/util/yaml/ConfigurationException.java @@ -0,0 +1,37 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldguard.util.yaml; + +/** + * Configuration exception. + * + * @author sk89q + */ +public class ConfigurationException extends Exception { + private static final long serialVersionUID = -2442886939908724203L; + + public ConfigurationException() { + super(); + } + + public ConfigurationException(String msg) { + super(msg); + } +} diff --git a/src/com/sk89q/worldguard/util/yaml/ConfigurationNode.java b/src/com/sk89q/worldguard/util/yaml/ConfigurationNode.java new file mode 100644 index 00000000..3ee00a06 --- /dev/null +++ b/src/com/sk89q/worldguard/util/yaml/ConfigurationNode.java @@ -0,0 +1,795 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldguard.util.yaml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldguard.util.yaml.ConfigurationNode; + +/** + * Represents a configuration node. + * + * @author sk89q + */ +public class ConfigurationNode { + protected Map root; + + protected ConfigurationNode(Map root) { + this.root = root; + } + + /** + * Clear all nodes. + */ + public void clear() { + root.clear(); + } + + /** + * Gets a property at a location. This will either return an Object + * or null, with null meaning that no configuration value exists at + * that location. This could potentially return a default value (not yet + * implemented) as defined by a plugin, if this is a plugin-tied + * configuration. + * + * @param path path to node (dot notation) + * @return object or null + */ + @SuppressWarnings("unchecked") + public Object getProperty(String path) { + if (!path.contains(".")) { + Object val = root.get(path); + if (val == null) { + return null; + } + return val; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + if (o == null) { + return null; + } + + if (i == parts.length - 1) { + return o; + } + + try { + node = (Map)o; + } catch (ClassCastException e) { + return null; + } + } + + return null; + } + + /** + * Prepare a value for serialization, in case it's not a native type + * (and we don't want to serialize objects as YAML objects). + * + * @param value + * @return + */ + private Object prepareSerialization(Object value) { + if (value instanceof Vector) { + Map out = new HashMap(); + Vector vec = (Vector) value; + out.put("x", vec.getX()); + out.put("y", vec.getY()); + out.put("z", vec.getZ()); + return out; + } + + return value; + } + + /** + * Set the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path + * @param value + */ + @SuppressWarnings("unchecked") + public void setProperty(String path, Object value) { + value = prepareSerialization(value); + + if (!path.contains(".")) { + root.put(path, value); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.put(parts[i], value); + return; + } + + if (o == null || !(o instanceof Map)) { + // This will override existing configuration data! + o = new HashMap(); + node.put(parts[i], o); + } + + node = (Map)o; + } + } + + /** + * Adds a new node to the given path. The returned object is a reference + * to the new node. This method will replace an existing node at + * the same path. See setProperty. + * + * @param path + * @return + */ + public ConfigurationNode addNode(String path) { + ConfigurationNode node = new ConfigurationNode( + new HashMap()); + setProperty(path, node); + return node; + } + + /** + * Gets a string at a location. This will either return an String + * or null, with null meaning that no configuration value exists at + * that location. If the object at the particular location is not actually + * a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or null + */ + public String getString(String path) { + Object o = getProperty(path); + if (o == null) { + return null; + } + return o.toString(); + } + + /** + * Gets a vector at a location. This will either return an Vector + * or a null. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or default + */ + public Vector getVector(String path) { + ConfigurationNode o = getNode(path); + if (o == null) { + return null; + } + + Double x = o.getDouble("x"); + Double y = o.getDouble("y"); + Double z = o.getDouble("z"); + + if (x == null || y == null || z == null) { + return null; + } + + return new Vector(x, y, z); + } + + /** + * Gets a 2D vector at a location. This will either return an Vector + * or a null. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or default + */ + public Vector2D getVector2d(String path) { + ConfigurationNode o = getNode(path); + if (o == null) { + return null; + } + + Double x = o.getDouble("x"); + Double z = o.getDouble("z"); + + if (x == null || z == null) { + return null; + } + + return new Vector2D(x, z); + } + + /** + * Gets a string at a location. This will either return an Vector + * or the default value. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public Vector getVector(String path, Vector def) { + Vector v = getVector(path); + if (v == null) { + setProperty(path, def); + return def; + } + return v; + } + + /** + * Gets a string at a location. This will either return an String + * or the default value. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public String getString(String path, String def) { + String o = getString(path); + if (o == null) { + setProperty(path, def); + return def; + } + return o; + } + + /** + * Gets an integer at a location. This will either return an integer + * or null. If the object at the particular location is not + * actually a integer, the default value will be returned. However, other + * number types will be casted to an integer. + * + * @param path path to node (dot notation) + * @return integer or null + */ + public Integer getInt(String path) { + Integer o = castInt(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets an integer at a location. This will either return an integer + * or the default value. If the object at the particular location is not + * actually a integer, the default value will be returned. However, other + * number types will be casted to an integer. + * + * @param path path to node (dot notation) + * @param def default value + * @return int or default + */ + public int getInt(String path, int def) { + Integer o = castInt(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a double at a location. This will either return an double + * or null. If the object at the particular location is not + * actually a double, the default value will be returned. However, other + * number types will be casted to an double. + * + * @param path path to node (dot notation) + * @return double or null + */ + public Double getDouble(String path) { + Double o = castDouble(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets a double at a location. This will either return an double + * or the default value. If the object at the particular location is not + * actually a double, the default value will be returned. However, other + * number types will be casted to an double. + * + * @param path path to node (dot notation) + * @param def default value + * @return double or default + */ + public double getDouble(String path, double def) { + Double o = castDouble(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a boolean at a location. This will either return an boolean + * or null. If the object at the particular location is not + * actually a boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @return boolean or null + */ + public Boolean getBoolean(String path) { + Boolean o = castBoolean(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets a boolean at a location. This will either return an boolean + * or the default value. If the object at the particular location is not + * actually a boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @param def default value + * @return boolean or default + */ + public boolean getBoolean(String path, boolean def) { + Boolean o = castBoolean(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Get a list of keys at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return list of keys + */ + @SuppressWarnings("unchecked") + public List getKeys(String path) { + if (path == null) return new ArrayList(root.keySet()); + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + return new ArrayList(((Map)o).keySet()); + } else { + return null; + } + } + + /** + * Gets a list of objects at a location. If the list is not defined, + * null will be returned. The node must be an actual list. + * + * @param path path to node (dot notation) + * @return boolean or default + */ + @SuppressWarnings("unchecked") + public List getList(String path) { + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof List) { + return (List)o; + } else { + return null; + } + } + + /** + * Gets a list of strings. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. If an item in the list + * is not a string, it will be converted to a string. The node must be + * an actual list and not just a string. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of strings + */ + public List getStringList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + for (Object o : raw) { + if (o == null) { + continue; + } + + list.add(o.toString()); + } + + return list; + } + + /** + * Gets a list of integers. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and not just an integer. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getIntList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + for (Object o : raw) { + Integer i = castInt(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of doubles. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a double. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getDoubleList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + for (Object o : raw) { + Double i = castDouble(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of booleans. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBooleanList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + for (Object o : raw) { + Boolean tetsu = castBoolean(o); + if (tetsu != null) { + list.add(tetsu); + } + } + + return list; + } + + /** + * Gets a list of vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getVectorList( + String path, List def) { + + List raw = getNodeList(path, null); + List list = new ArrayList(); + + for (ConfigurationNode o : raw) { + Double x = o.getDouble("x"); + Double y = o.getDouble("y"); + Double z = o.getDouble("z"); + + if (x == null || y == null || z == null) { + continue; + } + + list.add(new Vector(x, y, z)); + } + + return list; + } + + /** + * Gets a list of 2D vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getVector2dList( + String path, List def) { + + List raw = getNodeList(path, null); + List list = new ArrayList(); + + for (ConfigurationNode o : raw) { + Double x = o.getDouble("x"); + Double z = o.getDouble("z"); + + if (x == null || z == null) { + continue; + } + + list.add(new Vector2D(x, z)); + } + + return list; + } + + /** + * Gets a list of 2D vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBlockVector2dList( + String path, List def) { + + List raw = getNodeList(path, null); + List list = new ArrayList(); + + for (ConfigurationNode o : raw) { + Double x = o.getDouble("x"); + Double z = o.getDouble("z"); + + if (x == null || z == null) { + continue; + } + + list.add(new BlockVector2D(x, z)); + } + + return list; + } + + /** + * Gets a list of nodes. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + @SuppressWarnings("unchecked") + public List getNodeList(String path, List def) { + List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + for (Object o : raw) { + if (o instanceof Map) { + list.add(new ConfigurationNode((Map)o)); + } + } + + return list; + } + + /** + * Get a configuration node at a path. If the node doesn't exist or the + * path does not lead to a node, null will be returned. A node has + * key/value mappings. + * + * @param path + * @return node or null + */ + @SuppressWarnings("unchecked") + public ConfigurationNode getNode(String path) { + Object raw = getProperty(path); + if (raw instanceof Map) { + return new ConfigurationNode((Map)raw); + } + + return null; + } + + /** + * Get a list of nodes at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return map of nodes + */ + @SuppressWarnings("unchecked") + public Map getNodes(String path) { + Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + Map nodes = + new HashMap(); + + for (Map.Entry entry : ((Map)o).entrySet()) { + if (entry.getValue() instanceof Map) { + nodes.put(entry.getKey(), + new ConfigurationNode((Map) entry.getValue())); + } + } + + return nodes; + } else { + return null; + } + } + + /** + * Casts a value to an integer. May return null. + * + * @param o + * @return + */ + private static Integer castInt(Object o) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (int)(Byte)o; + } else if (o instanceof Integer) { + return (Integer)o; + } else if (o instanceof Double) { + return (int)(double)(Double)o; + } else if (o instanceof Float) { + return (int)(float)(Float)o; + } else if (o instanceof Long) { + return (int)(long)(Long)o; + } else { + return null; + } + } + + /** + * Casts a value to a double. May return null. + * + * @param o + * @return + */ + private static Double castDouble(Object o) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (double)(Float)o; + } else if (o instanceof Double) { + return (Double)o; + } else if (o instanceof Byte) { + return (double)(Byte)o; + } else if (o instanceof Integer) { + return (double)(Integer)o; + } else if (o instanceof Long) { + return (double)(Long)o; + } else { + return null; + } + } + + /** + * Casts a value to a boolean. May return null. + * + * @param o + * @return + */ + private static Boolean castBoolean(Object o) { + if (o == null) { + return null; + } else if (o instanceof Boolean) { + return (Boolean)o; + } else { + return null; + } + } + + /** + * Remove the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path + */ + @SuppressWarnings("unchecked") + public void removeProperty(String path) { + if (!path.contains(".")) { + root.remove(path); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.remove(parts[i]); + return; + } + + node = (Map)o; + } + } +} \ No newline at end of file