package net.cakenet.jsaton.config;
import net.cakenet.jsaton.Main;
import net.cakenet.jsaton.util.FileManager;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public final class Configuration {
public static final Configuration APP_CONFIG;
private Path path;
private LinkedHashMap<String, Object> props = new LinkedHashMap<>();
private Configuration(Path path) {
this.path = path;
load();
}
public <T> T get(String key) {
if (!props.containsKey(key))
return null;
return (T) props.get(key);
}
public <T> T get(String key, T def) {
if (!props.containsKey(key))
set(key, def);
return (T) props.get(key);
}
public void set(String key, Object value) {
props.put(key, value);
save();
}
public LinkedHashMap<String, Object> getEntries() {
return props;
}
private void load() {
try {
File f = FileManager.getAppFile(path);
props.clear();
if (!f.exists())
return;
SAXBuilder b = new SAXBuilder();
Document d = b.build(f);
Element root = d.getRootElement();
// Get config
props = loadConfig(root);
} catch (Exception e) {
throw new RuntimeException("Error loading config", e);
}
}
private void save() {
File f = FileManager.getAppFile(path);
Element root = new Element("config");
Document d = new Document(root);
storeConfig(root, props);
XMLOutputter xo = new XMLOutputter(Format.getPrettyFormat());
try {
OutputStream os = new FileOutputStream(f);
xo.output(d, os);
os.close();
} catch (IOException e) {
throw new RuntimeException("Error saving config", e);
}
}
private LinkedHashMap<String, Object> loadConfig(Element c) {
LinkedHashMap<String, Object> props = new LinkedHashMap<String, Object>();
for (Element e : c.getChildren("config")) {
String name = e.getAttributeValue("name");
String type = e.getAttributeValue("type");
Object v = loadValue(e, type);
// multiple values, bung in array
if (props.containsKey(name)) {
Object o = props.get(name);
Object[] d;
int idx;
if (o.getClass().isArray()) {
Object[] s = (Object[]) o;
idx = Array.getLength(o);
d = new Object[idx + 1];
System.arraycopy(s, 0, d, 0, idx);
} else {
d = new Object[2];
d[0] = o;
idx = 1;
}
d[idx] = v;
v = d;
}
props.put(name, v);
}
return props;
}
private Object loadValue(Element e, String type) {
String value = e.getTextTrim();
if (type.equals("int"))
return Integer.parseInt(value);
else if (type.equals("float"))
return Float.parseFloat(value);
else if (type.equals("double"))
return Double.parseDouble(value);
else if (type.equals("bool"))
return Boolean.parseBoolean(value);
else if (type.equals("map")) {
String entryTypes = e.getAttributeValue("valueType");
Map<String, Object> objs = new TreeMap<String, Object>();
for (Element child : e.getChildren("entry")) {
objs.put(child.getAttributeValue("key"), loadValue(child, entryTypes));
}
return objs;
} else if (type.equals("list")) {
String entryTypes = e.getAttributeValue("valueType");
List<Object> list = new LinkedList<Object>();
for (Element child : e.getChildren("entry")) {
list.add(loadValue(child, entryTypes));
}
return list;
} else if (type.equals("array")) {
String entryTypes = e.getAttributeValue("valueType");
List<Element> children = e.getChildren("entry");
Object[] o = new Object[children.size()];
for (int i = 0; i < children.size(); i++)
o[i] = loadValue(children.get(i), entryTypes);
return o;
}
return value;
}
private void storeConfig(Element parent, Map<String, Object> properties) {
for (String key : properties.keySet()) {
Object v = properties.get(key);
parent.addContent(createProperty("config", key, v, classToType(v.getClass())));
}
}
private Element createProperty(String nodeName, String key, Object o, String type) {
Element e = new Element(nodeName);
if (key != null)
e.setAttribute("name", key);
if (type != null)
e.setAttribute("type", type);
if (o instanceof Map) { // map
Map<String, Object> m = (Map<String, Object>) o;
if (m.isEmpty())
return e; // ignore
Class<? extends Object> eType = null;
for (String k : m.keySet()) {
Object val = m.get(k);
if (eType == null)
eType = val.getClass();
eType = findCommonType(eType, val.getClass());
Element c = createProperty("entry", null, val, null);
c.setAttribute("key", k);
e.addContent(c);
}
e.setAttribute("valueType", classToType(eType));
return e;
} else if (o instanceof List) {
List<?> list = (List<?>) o;
if (list.isEmpty())
return e; // ignore...
Class<?> eType = null;
for (Object a : list) {
if (eType == null)
eType = a.getClass();
eType = findCommonType(eType, a.getClass());
Element c = createProperty("entry", null, a, null);
e.addContent(c);
}
e.setAttribute("valueType", classToType(eType));
return e;
} else if (o.getClass().isArray()) { // array
Object[] vals = (Object[]) o;
Class<? extends Object> eType = null;
for (Object val : vals) {
if (eType == null)
eType = val.getClass();
eType = findCommonType(eType, val.getClass());
e.addContent(createProperty("entry", null, val, null));
}
e.setAttribute("valueType", classToType(eType));
return e;
}
e.setText(o.toString());
return e;
}
private static Class<?> findCommonType(Class<?> a, Class<?> b) {
if (a == b)
return a;
if (a.isAssignableFrom(b))
return a;
if (b.isAssignableFrom(a))
return b;
return null;
}
private static String classToType(Class<?> c) {
if (c == String.class)
return "string";
if (c == Boolean.TYPE || c == Boolean.class)
return "bool";
if (c == Integer.TYPE || c == Integer.class)
return "int";
if (c == Float.TYPE || c == Float.class)
return "float";
if (c == Double.TYPE || c == Double.class)
return "double";
if (c.isArray())
return "array";
if (Map.class.isAssignableFrom(c))
return "map";
if (List.class.isAssignableFrom(c))
return "list";
return null;
}
public static Configuration load(String base, String... sub) {
return new Configuration(Paths.get(base, sub));
}
static {
APP_CONFIG = Configuration.load("config.xml");
String userDataPath = APP_CONFIG.get("user.data", FileManager.USER_HOME.resolve(Main.APP_NAME).toString());
FileManager.setUserDataPath(Paths.get(userDataPath));
}
}