mirror of https://github.com/webbukkit/dynmap.git
469 lines
15 KiB
Java
469 lines
15 KiB
Java
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<String, Object> {
|
|
public Map<String, Object> entries;
|
|
private File f;
|
|
private Yaml yaml;
|
|
|
|
public ConfigurationNode() {
|
|
entries = new LinkedHashMap<String, Object>();
|
|
}
|
|
|
|
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<String, Object>();
|
|
}
|
|
|
|
public ConfigurationNode(Map<String, Object> 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<String, Object>)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<String, Object>)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<String, Object> submap;
|
|
try {
|
|
submap = (Map<String, Object>)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> 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<String> getStrings(String path, List<String> def) {
|
|
Object o = getObject(path);
|
|
if (!(o instanceof List<?>)) {
|
|
return def;
|
|
}
|
|
ArrayList<String> strings = new ArrayList<String>();
|
|
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 <T> List<T> getList(String path) {
|
|
try {
|
|
List<T> list = (List<T>)getObject(path, null);
|
|
return list;
|
|
} catch (ClassCastException e) {
|
|
try {
|
|
T o = (T)getObject(path, null);
|
|
if (o == null) {
|
|
return new ArrayList<T>();
|
|
}
|
|
ArrayList<T> al = new ArrayList<T>();
|
|
al.add(o);
|
|
return al;
|
|
} catch (ClassCastException e2) {
|
|
return new ArrayList<T>();
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<Map<String,Object>> getMapList(String path) {
|
|
return getList(path);
|
|
}
|
|
|
|
public ConfigurationNode getNode(String path) {
|
|
Map<String, Object> v = null;
|
|
v = getGeneric(path, v);
|
|
if (v == null)
|
|
return null;
|
|
return new ConfigurationNode(v);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public List<ConfigurationNode> getNodes(String path) {
|
|
List<Object> o = getList(path);
|
|
|
|
if(o == null)
|
|
return new ArrayList<ConfigurationNode>();
|
|
|
|
ArrayList<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
|
|
for(Object i : (List<?>)o) {
|
|
if (i instanceof Map<?, ?>) {
|
|
Map<String, Object> map;
|
|
try {
|
|
map = (Map<String, Object>)i;
|
|
} catch(ClassCastException e) {
|
|
continue;
|
|
}
|
|
nodes.add(new ConfigurationNode(map));
|
|
}
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
public void extend(Map<String, Object> other) {
|
|
if (other != null)
|
|
extendMap(this, other);
|
|
}
|
|
|
|
private final static Object copyValue(Object v) {
|
|
if(v instanceof Map) {
|
|
@SuppressWarnings("unchecked")
|
|
Map<String, Object> mv = (Map<String, Object>)v;
|
|
LinkedHashMap<String, Object> newv = new LinkedHashMap<String,Object>();
|
|
for(Map.Entry<String, Object> me : mv.entrySet()) {
|
|
newv.put(me.getKey(), copyValue(me.getValue()));
|
|
}
|
|
return newv;
|
|
}
|
|
else if(v instanceof List) {
|
|
@SuppressWarnings("unchecked")
|
|
List<Object> lv = (List<Object>)v;
|
|
ArrayList<Object> newv = new ArrayList<Object>();
|
|
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<String, Object> left, Map<String, Object> right) {
|
|
ConfigurationNode original = new ConfigurationNode(left);
|
|
for(Map.Entry<String, Object> entry : right.entrySet()) {
|
|
String key = entry.getKey();
|
|
Object value = entry.getValue();
|
|
original.put(key, copyValue(value));
|
|
}
|
|
}
|
|
|
|
public <T> 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 <T> List<T> createInstances(String path, Class<?>[] constructorParameters, Object[] constructorArguments) {
|
|
List<ConfigurationNode> nodes = getNodes(path);
|
|
List<T> instances = new ArrayList<T>();
|
|
for(ConfigurationNode node : nodes) {
|
|
T instance = node.<T>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<? extends String, ? extends Object> m) {
|
|
entries.putAll(m);
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
entries.clear();
|
|
}
|
|
|
|
@Override
|
|
public Set<String> keySet() {
|
|
return entries.keySet();
|
|
}
|
|
|
|
@Override
|
|
public Collection<Object> values() {
|
|
return entries.values();
|
|
}
|
|
|
|
@Override
|
|
public Set<java.util.Map.Entry<String, Object>> 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
|
|
}
|
|
|
|
}
|