// $Id$ /* * WorldEdit * Copyright (C) 2010, 2011 sk89q and contributors * * 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.util.yaml; import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector2D; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Represents a configuration node. * * @author sk89q */ public class YAMLNode { protected Map root; private boolean writeDefaults; public YAMLNode(Map root, boolean writeDefaults) { this.root = root; this.writeDefaults = writeDefaults; } /** * Return the underlying map. * * @return the map */ public Map getMap() { return 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 LinkedHashMap(); 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 LinkedHashMap(); 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 YAMLNode addNode(String path) { Map map = new LinkedHashMap(); YAMLNode node = new YAMLNode(map, writeDefaults); setProperty(path, map); 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) { YAMLNode 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) { YAMLNode 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) { if (writeDefaults) 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) { if (writeDefaults) 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) { if (writeDefaults) 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) { if (writeDefaults) 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) { if (writeDefaults) 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 (YAMLNode 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 (YAMLNode 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 (YAMLNode 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 YAMLNode((Map) o, writeDefaults)); } } 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 YAMLNode getNode(String path) { Object raw = getProperty(path); if (raw instanceof Map) { return new YAMLNode((Map) raw, writeDefaults); } 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 LinkedHashMap(); for (Map.Entry entry : ((Map) o).entrySet()) { if (entry.getValue() instanceof Map) { nodes.put(entry.getKey(), new YAMLNode((Map) entry.getValue(), writeDefaults)); } } 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 Number) { return ((Number) o).intValue(); } 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 Number) { return ((Number) o).doubleValue(); } 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; } } public boolean writeDefaults() { return writeDefaults; } public void setWriteDefaults(boolean writeDefaults) { this.writeDefaults = writeDefaults; } }