package org.dynmap; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; 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.introspector.Property; import org.yaml.snakeyaml.nodes.CollectionNode; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.reader.UnicodeReader; import org.yaml.snakeyaml.representer.Represent; import org.yaml.snakeyaml.representer.Representer; public class ConfigurationNode implements Map { public Map entries; private File f; private Yaml yaml; public ConfigurationNode() { entries = new LinkedHashMap(); } private void initparse() { if(yaml == null) { DumperOptions options = new DumperOptions(); options.setIndent(4); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setPrettyFlow(true); options.setVersion(DumperOptions.Version.V1_1); yaml = new Yaml(new SafeConstructor(), new EmptyNullRepresenter(), options); } } public ConfigurationNode(File f) { this.f = f; entries = new LinkedHashMap(); } public ConfigurationNode(Map map) { if (map == null) { throw new IllegalArgumentException(); } entries = map; } public ConfigurationNode(InputStream in) { load(in); } @SuppressWarnings("unchecked") public boolean load(InputStream in) { initparse(); Object o = yaml.load(new UnicodeReader(in)); if((o != null) && (o instanceof Map)) entries = (Map)o; return (entries != null); } @SuppressWarnings("unchecked") public boolean load() { initparse(); // If no file to read, just return false if (!f.canRead()) { return false; } Reader fr = null; try { fr = new UnicodeReader(new BufferedInputStream(new FileInputStream(f))); Object o = yaml.load(fr); if((o != null) && (o instanceof Map)) entries = (Map)o; fr.close(); } catch (YAMLException e) { Log.severe("Error parsing " + f.getPath() + ". Use http://yamllint.com to debug the YAML syntax." ); throw e; } catch(IOException iox) { Log.severe("Error reading " + f.getPath()); return false; } finally { if(fr != null) { try { fr.close(); } catch (IOException x) {} } } return (entries != null); } public boolean save() { return save(f); } public boolean save(File file) { initparse(); OutputStream stream = null; File parent = file.getParentFile(); if (parent != null) { parent.mkdirs(); } try { stream = new BufferedOutputStream(new FileOutputStream(file)); OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8"); yaml.dump(entries, writer); return true; } catch (IOException e) { } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) {} } return false; } @SuppressWarnings("unchecked") public Object getObject(String path) { if (path.isEmpty()) return entries; // Try get first (in case '/' is legit part Object v = get(path); int separator = path.indexOf('/'); if ((v != null) || (separator < 0)) return v; String localKey = path.substring(0, separator); Object subvalue = get(localKey); if (subvalue == null) return null; if (!(subvalue instanceof Map)) return null; Map submap; try { submap = (Map)subvalue; } catch (ClassCastException e) { return null; } String subpath = path.substring(separator + 1); return new ConfigurationNode(submap).getObject(subpath); } public Object getObject(String path, Object def) { Object o = getObject(path); if (o == null) return def; return o; } @SuppressWarnings("unchecked") public T getGeneric(String path, T def) { Object o = getObject(path, def); try { return (T)o; } catch(ClassCastException e) { return def; } } public int getInteger(String path, int def) { return Integer.parseInt(getObject(path, def).toString()); } public double getLong(String path, long def) { return Long.parseLong(getObject(path, def).toString()); } public float getFloat(String path, float def) { return Float.parseFloat(getObject(path, def).toString()); } public double getDouble(String path, double def) { return Double.parseDouble(getObject(path, def).toString()); } public boolean getBoolean(String path, boolean def) { return Boolean.parseBoolean(getObject(path, def).toString()); } public String getString(String path) { return getString(path, null); } public List getStrings(String path, List def) { Object o = getObject(path); if (!(o instanceof List)) { return def; } ArrayList strings = new ArrayList(); for(Object i : (List)o) { strings.add(i.toString()); } return strings; } public String getString(String path, String def) { Object o = getObject(path, def); if (o == null) return null; return o.toString(); } public Color getColor(String path, String def) { String lclr = this.getString(path, def); if((lclr != null) && (lclr.startsWith("#"))) { try { int c = Integer.parseInt(lclr.substring(1), 16); return new Color((c>>16)&0xFF, (c>>8)&0xFF, c&0xFF); } catch (NumberFormatException nfx) { Log.severe("Invalid color value: " + lclr + " for '" + path + "'"); } } return null; } @SuppressWarnings("unchecked") public List getList(String path) { try { List list = (List)getObject(path, null); return list; } catch (ClassCastException e) { try { T o = (T)getObject(path, null); if (o == null) { return new ArrayList(); } ArrayList al = new ArrayList(); al.add(o); return al; } catch (ClassCastException e2) { return new ArrayList(); } } } public List> getMapList(String path) { return getList(path); } public ConfigurationNode getNode(String path) { Map v = null; v = getGeneric(path, v); if (v == null) return null; return new ConfigurationNode(v); } @SuppressWarnings("unchecked") public List getNodes(String path) { List o = getList(path); if(o == null) return new ArrayList(); ArrayList nodes = new ArrayList(); for(Object i : (List)o) { if (i instanceof Map) { Map map; try { map = (Map)i; } catch(ClassCastException e) { continue; } nodes.add(new ConfigurationNode(map)); } } return nodes; } public void extend(Map other) { if (other != null) extendMap(this, other); } private final static Object copyValue(Object v) { if(v instanceof Map) { @SuppressWarnings("unchecked") Map mv = (Map)v; LinkedHashMap newv = new LinkedHashMap(); for(Map.Entry me : mv.entrySet()) { newv.put(me.getKey(), copyValue(me.getValue())); } return newv; } else if(v instanceof List) { @SuppressWarnings("unchecked") List lv = (List)v; ArrayList newv = new ArrayList(); for(int i = 0; i < lv.size(); i++) { newv.add(copyValue(lv.get(i))); } return newv; } else { return v; } } private final static void extendMap(Map left, Map right) { ConfigurationNode original = new ConfigurationNode(left); for(Map.Entry entry : right.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); original.put(key, copyValue(value)); } } public T createInstance(Class[] constructorParameters, Object[] constructorArguments) { String typeName = getString("class"); try { Class mapTypeClass = Class.forName(typeName); Class[] constructorParameterWithConfiguration = new Class[constructorParameters.length+1]; for(int i = 0; i < constructorParameters.length; i++) { constructorParameterWithConfiguration[i] = constructorParameters[i]; } constructorParameterWithConfiguration[constructorParameterWithConfiguration.length-1] = ConfigurationNode.class; Object[] constructorArgumentsWithConfiguration = new Object[constructorArguments.length+1]; for(int i = 0; i < constructorArguments.length; i++) { constructorArgumentsWithConfiguration[i] = constructorArguments[i]; } constructorArgumentsWithConfiguration[constructorArgumentsWithConfiguration.length-1] = this; Constructor constructor = mapTypeClass.getConstructor(constructorParameterWithConfiguration); @SuppressWarnings("unchecked") T t = (T)constructor.newInstance(constructorArgumentsWithConfiguration); return t; } catch (Exception e) { // TODO: Remove reference to MapManager. Log.severe("Error loading maptype", e); e.printStackTrace(); } return null; } public List createInstances(String path, Class[] constructorParameters, Object[] constructorArguments) { List nodes = getNodes(path); List instances = new ArrayList(); for(ConfigurationNode node : nodes) { T instance = node.createInstance(constructorParameters, constructorArguments); if (instance != null) { instances.add(instance); } } return instances; } @Override public int size() { return entries.size(); } @Override public boolean isEmpty() { return entries.isEmpty(); } @Override public boolean containsKey(Object key) { return entries.containsKey(key); } @Override public boolean containsValue(Object value) { return entries.containsValue(value); } @Override public Object get(Object key) { return entries.get(key); } @Override public Object put(String key, Object value) { return entries.put(key, value); } @Override public Object remove(Object key) { return entries.remove(key); } @Override public void putAll(Map m) { entries.putAll(m); } @Override public void clear() { entries.clear(); } @Override public Set keySet() { return entries.keySet(); } @Override public Collection values() { return entries.values(); } @Override public Set> entrySet() { return entries.entrySet(); } private class EmptyNullRepresenter extends Representer { public EmptyNullRepresenter() { super(); this.nullRepresenter = new EmptyRepresentNull(); } protected class EmptyRepresentNull implements Represent { public Node representData(Object data) { return representScalar(Tag.NULL, ""); // Changed "null" to "" so as to avoid writing nulls } } // Code borrowed from snakeyaml (http://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue60/SkipBeanTest.java) @Override protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) { NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); Node valueNode = tuple.getValueNode(); if (valueNode instanceof CollectionNode) { // Removed null check if (Tag.SEQ.equals(valueNode.getTag())) { SequenceNode seq = (SequenceNode) valueNode; if (seq.getValue().isEmpty()) { return null; // skip empty lists } } if (Tag.MAP.equals(valueNode.getTag())) { MappingNode seq = (MappingNode) valueNode; if (seq.getValue().isEmpty()) { return null; // skip empty maps } } } return tuple; } // End of borrowed code } }