diff --git a/.gitignore b/.gitignore index d3823e2..2eb3147 100644 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,9 @@ plugins/ resource_packs/ logs/ worlds/ -server.json -whitelist.json -op.json -banned-ips.json -banned-players.json \ No newline at end of file +./server.properties +./whitelist.txt +./op.txt +./banned-ips.txt +./banned-players.txt +./sculk.yml \ No newline at end of file diff --git a/src/main/java/org/sculk/Server.java b/src/main/java/org/sculk/Server.java index 7d1ce8c..e0b23f8 100644 --- a/src/main/java/org/sculk/Server.java +++ b/src/main/java/org/sculk/Server.java @@ -66,9 +66,9 @@ public Server(Logger logger, String dataPath) { logger.info("Loading {}...", TextFormat.AQUA + "sculk.yml" + TextFormat.WHITE); this.config = new Config(this.dataPath + "sculk.yml"); - logger.info("Loading {}...", TextFormat.AQUA + "server.json" + TextFormat.WHITE); - this.properties = new Config(dataPath + "/server.json"); - if(this.properties.getString("server-port") == null) { + logger.info("Loading {}...", TextFormat.AQUA + "server.properties" + TextFormat.WHITE); + this.properties = new Config(this.dataPath.resolve("server.properties").toString(), Config.PROPERTIES); + if(!this.properties.exists("server-port")) { this.properties.set("language", "English"); this.properties.set("motd", "A Sculk Server Software"); this.properties.set("server-port", 19132); @@ -83,13 +83,13 @@ public Server(Logger logger, String dataPath) { this.properties.set("level-type", "DEFAULT"); this.properties.set("auto-save", true); this.properties.set("xbox-auth", true); + this.properties.save(); } - this.operators = new Config(this.dataPath + "/op.json"); - this.operators.save(); - this.whitelist = new Config(this.dataPath + "/whitelist.json"); - this.banByName = new Config(this.dataPath + "/banned-players.json"); - this.banByIp = new Config(this.dataPath + "/banned-ips.json"); + this.operators = new Config(this.dataPath.resolve("op.txt").toString(), Config.ENUM); + this.whitelist = new Config(this.dataPath.resolve("whitelist.txt").toString(), Config.ENUM); + this.banByName = new Config(this.dataPath.resolve("banned-players.txt").toString(), Config.ENUM); + this.banByIp = new Config(this.dataPath.resolve("banned-ip.txt").toString(), Config.ENUM); logger.info("Selected {} as the base language", this.properties.getString("language")); logger.info("Starting Minecraft: Bedrock Edition server version {}", TextFormat.AQUA + Sculk.MINECRAFT_VERSION + TextFormat.WHITE); diff --git a/src/main/java/org/sculk/config/Config.java b/src/main/java/org/sculk/config/Config.java index 5540e1c..2da168d 100644 --- a/src/main/java/org/sculk/config/Config.java +++ b/src/main/java/org/sculk/config/Config.java @@ -1,18 +1,23 @@ package org.sculk.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import org.sculk.Server; +import org.sculk.utils.Utils; import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.representer.Representer; +import org.yaml.snakeyaml.resolver.Resolver; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.HashMap; -import java.util.Map; +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Pattern; /* * ____ _ _ __ __ ____ @@ -31,72 +36,550 @@ */ public class Config { - private Map configMap = new HashMap<>(); - private final String filePath; - private final boolean isJson; + public static final int DETECT = -1; //Detect by file extension + public static final int PROPERTIES = 0; // .properties + public static final int CNF = Config.PROPERTIES; // .cnf + public static final int JSON = 1; // .js, .json + public static final int YAML = 2; // .yml, .yaml + public static final int ENUM = 5; // .txt, .list, .enum + public static final int ENUMERATION = Config.ENUM; - public Config(String filePath) throws IOException { - this.filePath = filePath; - this.isJson = filePath.endsWith(".json"); - load(); + //private LinkedHashMap config = new LinkedHashMap<>(); + private ConfigSection config = new ConfigSection(); + private File file; + private boolean correct = false; + private int type = Config.DETECT; + + public static final Map format = new TreeMap<>(); + + static { + format.put("properties", Config.PROPERTIES); + format.put("con", Config.PROPERTIES); + format.put("conf", Config.PROPERTIES); + format.put("config", Config.PROPERTIES); + format.put("js", Config.JSON); + format.put("json", Config.JSON); + format.put("yml", Config.YAML); + format.put("yaml", Config.YAML); + format.put("txt", Config.ENUM); + format.put("list", Config.ENUM); + format.put("enum", Config.ENUM); } - private void load() throws IOException { - File file = new File(filePath); - if (!file.exists()) { - save(); // create a new file with an empty config - return; - } + /** + * Constructor for Config instance with undefined file object + * + * @param type - Config type + */ + public Config(int type) { + this.type = type; + this.correct = true; + this.config = new ConfigSection(); + } + + /** + * Constructor for Config (YAML) instance with undefined file object + */ + public Config() { + this(Config.YAML); + } + + public Config(String file) { + this(file, Config.DETECT); + } + + public Config(File file) { + this(file.toString(), Config.DETECT); + } + + public Config(String file, int type) { + this(file, type, new ConfigSection()); + } + + public Config(File file, int type) { + this(file.toString(), type, new ConfigSection()); + } + + @Deprecated + public Config(String file, int type, LinkedHashMap defaultMap) { + this.load(file, type, new ConfigSection(defaultMap)); + } + + public Config(String file, int type, ConfigSection defaultMap) { + this.load(file, type, defaultMap); + } + + public Config(File file, int type, ConfigSection defaultMap) { + this.load(file.toString(), type, defaultMap); + } - if (isJson) { - ObjectMapper mapper = new ObjectMapper(); - configMap = mapper.readValue(file, Map.class); + @Deprecated + public Config(File file, int type, LinkedHashMap defaultMap) { + this(file.toString(), type, new ConfigSection(defaultMap)); + } + + public void reload() { + this.config.clear(); + this.correct = false; + if (this.file == null) throw new IllegalStateException("Failed to reload Config. File object is undefined."); + this.load(this.file.toString(), this.type); + + } + + public boolean load(String file) { + return this.load(file, Config.DETECT); + } + + public boolean load(String file, int type) { + return this.load(file, type, new ConfigSection()); + } + + public boolean load(String file, int type, ConfigSection defaultMap) { + this.correct = true; + this.type = type; + this.file = new File(file); + if (!this.file.exists()) { + try { + this.file.getParentFile().mkdirs(); + this.file.createNewFile(); + } catch (IOException e) { + Server.getInstance().getLogger().error("Could not create Config " + this.file.toString(), e); + } + this.config = defaultMap; + this.save(); } else { - Yaml yaml = new Yaml(); // Using default constructor for YAML - try (FileInputStream inputStream = new FileInputStream(file)) { - configMap = yaml.load(inputStream); + if (this.type == Config.DETECT) { + String extension = ""; + if (this.file.getName().lastIndexOf(".") != -1 && this.file.getName().lastIndexOf(".") != 0) { + extension = this.file.getName().substring(this.file.getName().lastIndexOf(".") + 1); + } + if (format.containsKey(extension)) { + this.type = format.get(extension); + } else { + this.correct = false; + } + } + if (this.correct) { + String content = ""; + try { + content = Utils.readFile(this.file); + } catch (IOException e) { + Server.getInstance().getLogger().catching(e); + } + this.parseContent(content); + if (!this.correct) return false; + if (this.setDefault(defaultMap) > 0) { + this.save(); + } + } else { + return false; } } + return true; } - public boolean exists() { - return new File(filePath).exists(); + public boolean load(InputStream inputStream) { + if (inputStream == null) return false; + if (this.correct) { + String content; + try { + content = Utils.readFile(inputStream); + } catch (IOException e) { + Server.getInstance().getLogger().catching(e); + return false; + } + this.parseContent(content); + } + return correct; } - public void save() throws IOException { - File file = new File(filePath); - if (isJson) { - ObjectMapper mapper = new ObjectMapper(); - mapper.writerWithDefaultPrettyPrinter().writeValue(file, configMap); - } else { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(FlowStyle.BLOCK); - Yaml yaml = new Yaml(options); + public boolean check() { + return this.correct; + } + + public boolean isCorrect() { + return correct; + } + + /** + * Save configuration into provided file. Internal file object will be set to new file. + * + * @param file + * @param async + * @return + */ + public boolean save(File file, boolean async) { + this.file = file; + return save(async); + } - try (Writer writer = new OutputStreamWriter(new FileOutputStream(file))) { - yaml.dump(configMap, writer); + public boolean save(File file) { + this.file = file; + return save(); + } + + public boolean save() { + return this.save(false); + } + + public boolean save(Boolean async) { + if (this.file == null) throw new IllegalStateException("Failed to save Config. File object is undefined."); + if (this.correct) { + StringBuilder content = new StringBuilder(); + switch (this.type) { + case Config.PROPERTIES: + content = new StringBuilder(this.writeProperties()); + break; + case Config.JSON: + content = new StringBuilder(new GsonBuilder().setPrettyPrinting().create().toJson(this.config)); + break; + case Config.YAML: + DumperOptions dumperOptions = new DumperOptions(); + dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setCodePointLimit(104857600); // Allow over 3mb config files + Yaml yaml = new Yaml(new Constructor(loaderOptions), new Representer(dumperOptions), dumperOptions, loaderOptions, new Resolver()); + content = new StringBuilder(yaml.dump(this.config)); + break; + case Config.ENUM: + for (Object o : this.config.entrySet()) { + Map.Entry entry = (Map.Entry) o; + content.append(entry.getKey()).append("\r\n"); + } + break; + } +/* if (async) { + Server.getInstance().getScheduler().scheduleAsyncTask(new FileWriteTask(this.file, content.toString())); + //TODO Scheduler + } else {*/ + try { + Utils.writeFile(this.file, content.toString()); + } catch (IOException e) { + Server.getInstance().getLogger().catching(e); +/* }*/ } + return true; + } else { + return false; } } - public void set(String key, Object value) throws IOException { - configMap.put(key, value); - save(); + public void set(final String key, Object value) { + this.config.set(key, value); + } + + public Object get(String key) { + return this.get(key, null); + } + + public T get(String key, T defaultValue) { + return this.correct ? this.config.get(key, defaultValue) : defaultValue; + } + + public ConfigSection getSection(String key) { + return this.correct ? this.config.getSection(key) : new ConfigSection(); + } + + public boolean isSection(String key) { + return config.isSection(key); + } + + public ConfigSection getSections(String key) { + return this.correct ? this.config.getSections(key) : new ConfigSection(); + } + + public ConfigSection getSections() { + return this.correct ? this.config.getSections() : new ConfigSection(); + } + + public int getInt(String key) { + return this.getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + return this.correct ? this.config.getInt(key, defaultValue) : defaultValue; + } + + public boolean isInt(String key) { + return config.isInt(key); + } + + public long getLong(String key) { + return this.getLong(key, 0); + } + + public long getLong(String key, long defaultValue) { + return this.correct ? this.config.getLong(key, defaultValue) : defaultValue; + } + + public boolean isLong(String key) { + return config.isLong(key); + } + + public double getDouble(String key) { + return this.getDouble(key, 0); + } + + public double getDouble(String key, double defaultValue) { + return this.correct ? this.config.getDouble(key, defaultValue) : defaultValue; + } + + public boolean isDouble(String key) { + return config.isDouble(key); } public String getString(String key) { - Object value = configMap.get(key); - return value != null ? value.toString() : null; + return this.getString(key, ""); + } + + public String getString(String key, String defaultValue) { + return this.correct ? this.config.getString(key, defaultValue) : defaultValue; + } + + public boolean isString(String key) { + return config.isString(key); + } + + public boolean getBoolean(String key) { + return this.getBoolean(key, false); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return this.correct ? this.config.getBoolean(key, defaultValue) : defaultValue; + } + + public boolean isBoolean(String key) { + return config.isBoolean(key); + } + + public List getList(String key) { + return this.getList(key, null); + } + + public List getList(String key, List defaultList) { + return this.correct ? this.config.getList(key, defaultList) : defaultList; + } + + public boolean isList(String key) { + return config.isList(key); + } + + public List getStringList(String key) { + return config.getStringList(key); + } + + public List getIntegerList(String key) { + return config.getIntegerList(key); + } + + public List getBooleanList(String key) { + return config.getBooleanList(key); + } + + public List getDoubleList(String key) { + return config.getDoubleList(key); + } + + public List getFloatList(String key) { + return config.getFloatList(key); + } + + public List getLongList(String key) { + return config.getLongList(key); + } + + public List getByteList(String key) { + return config.getByteList(key); + } + + public List getCharacterList(String key) { + return config.getCharacterList(key); + } + + public List getShortList(String key) { + return config.getShortList(key); + } + + public List getMapList(String key) { + return config.getMapList(key); + } + + public void setAll(LinkedHashMap map) { + this.config = new ConfigSection(map); + } + + public void setAll(ConfigSection section) { + this.config = section; + } + + public boolean exists(String key) { + return config.exists(key); } - public Integer getInt(String key) { - Object value = configMap.get(key); - return value instanceof Integer ? (Integer) value : null; + public boolean exists(String key, boolean ignoreCase) { + return config.exists(key, ignoreCase); } - public Boolean getBoolean(String key) { - Object value = configMap.get(key); - return value instanceof Boolean ? (Boolean) value : null; + public void remove(String key) { + config.remove(key); } -} + public Map getAll() { + return this.config.getAllMap(); + } + + /** + * Get root (main) config section of the Config + * + */ + public ConfigSection getRootSection() { + return config; + } + + public int setDefault(LinkedHashMap map) { + return setDefault(new ConfigSection(map)); + } + + public int setDefault(ConfigSection map) { + int size = this.config.size(); + this.config = this.fillDefaults(map, this.config); + return this.config.size() - size; + } + + + private ConfigSection fillDefaults(ConfigSection defaultMap, ConfigSection data) { + for (String key : defaultMap.keySet()) { + if (!data.containsKey(key)) { + data.put(key, defaultMap.get(key)); + } + } + return data; + } + + private void parseList(String content) { + content = content.replace("\r\n", "\n"); + for (String v : content.split("\n")) { + if (v.trim().isEmpty()) { + continue; + } + config.put(v, true); + } + } + + private String writeProperties() { + StringBuilder content = new StringBuilder("#Properties Config file\r\n#" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + "\r\n"); + for (Object o : this.config.entrySet()) { + Map.Entry entry = (Map.Entry) o; + Object v = entry.getValue(); + Object k = entry.getKey(); + if (v instanceof Boolean) { + v = (Boolean) v ? "on" : "off"; + } + content.append(k).append("=").append(v).append("\r\n"); + } + return content.toString(); + } + + private void parseProperties(String content) { + for (final String line : content.split("\n")) { + if (Pattern.compile("[a-zA-Z0-9\\-_.]*+=+[^\\r\\n]*").matcher(line).matches()) { + final int splitIndex = line.indexOf('='); + if (splitIndex == -1) { + continue; + } + final String key = line.substring(0, splitIndex); + final String value = line.substring(splitIndex + 1); + final String valueLower = value.toLowerCase(); + if (this.config.containsKey(key)) { + Server.getInstance().getLogger().debug("[Config] Repeated property " + key + " on file " + this.file.toString()); + } + switch (valueLower) { + case "on": + case "true": + case "yes": + this.config.put(key, true); + break; + case "off": + case "false": + case "no": + this.config.put(key, false); + break; + default: + this.config.put(key, value); + break; + } + } + } + } + + /** + * @deprecated use {@link #get(String)} instead + */ + @Deprecated + public Object getNested(String key) { + return get(key); + } + + /** + * @deprecated use {@link #get(String, Object)} instead + */ + @Deprecated + public T getNested(String key, T defaultValue) { + return get(key, defaultValue); + } + + /** + * @deprecated use {@link #get(String)} instead + */ + @Deprecated + @SuppressWarnings("unchecked") + public T getNestedAs(String key, Class type) { + return (T) get(key); + } + + /** + * @deprecated use {@link #remove(String)} instead + */ + @Deprecated + public void removeNested(String key) { + remove(key); + } + + private void parseContent(String content) { + switch (this.type) { + case Config.PROPERTIES: + this.parseProperties(content); + break; + case Config.JSON: + GsonBuilder builder = new GsonBuilder(); + Gson gson = builder.create(); + this.config = new ConfigSection(gson.fromJson(content, new TypeToken>() { + }.getType())); + break; + case Config.YAML: + DumperOptions dumperOptions = new DumperOptions(); + dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setCodePointLimit(104857600); // Allow over 3mb config files + Yaml yaml = new Yaml(new Constructor(loaderOptions), new Representer(dumperOptions), dumperOptions, loaderOptions, new Resolver()); + this.config = new ConfigSection(yaml.loadAs(content, LinkedHashMap.class)); + break; + // case Config.SERIALIZED + case Config.ENUM: + this.parseList(content); + break; + default: + this.correct = false; + } + } + + public Set getKeys() { + if (this.correct) return config.getKeys(); + return new HashSet<>(); + } + + public Set getKeys(boolean child) { + if (this.correct) return config.getKeys(child); + return new HashSet<>(); + } +} \ No newline at end of file diff --git a/src/main/java/org/sculk/config/ConfigSection.java b/src/main/java/org/sculk/config/ConfigSection.java new file mode 100644 index 0000000..1ec2a6d --- /dev/null +++ b/src/main/java/org/sculk/config/ConfigSection.java @@ -0,0 +1,748 @@ +package org.sculk.config; + +import java.util.*; + +/* + * ____ _ _ __ __ ____ + * / ___| ___ _ _| | | __ | \/ | _ \ + * \___ \ / __| | | | | |/ / _____ | |\/| | |_) | + * ___) | (__| |_| | | < |_____| | | | | __/ + * |____/ \___|\__,_|_|_|\_\ |_| |_|_| + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * @author: SculkTeams + * @link: http://www.sculkmp.org/ + */ +public class ConfigSection extends LinkedHashMap { + + /** + * Empty ConfigSection constructor + */ + public ConfigSection() { + super(); + } + + /** + * Constructor of ConfigSection that contains initial key/value data + * + * @param key + * @param value + */ + public ConfigSection(String key, Object value) { + this(); + this.set(key, value); + } + + /** + * Constructor of ConfigSection, based on values stored in map. + * + * @param map + */ + public ConfigSection(LinkedHashMap map) { + this(); + if (map == null || map.isEmpty()) return; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof LinkedHashMap) { + super.put(entry.getKey(), new ConfigSection((LinkedHashMap) entry.getValue())); + } else if (entry.getValue() instanceof List) { + super.put(entry.getKey(), parseList((List) entry.getValue())); + } else { + super.put(entry.getKey(), entry.getValue()); + } + } + } + + private List parseList(List list) { + List newList = new ArrayList<>(); + + for (Object o : list) { + if (o instanceof LinkedHashMap) { + newList.add(new ConfigSection((LinkedHashMap) o)); + } else { + newList.add(o); + } + } + + return newList; + } + + /** + * Get root section as LinkedHashMap + */ + public Map getAllMap() { + return new LinkedHashMap<>(this); + } + + + /** + * Get new instance of config section + * + */ + public ConfigSection getAll() { + return new ConfigSection(this); + } + + /** + * Get object by key. If section does not contain value, return null + */ + public Object get(String key) { + return this.get(key, null); + } + + /** + * Get object by key. If section does not contain value, return default value + * + * @param key String + * @param defaultValue T + */ + public T get(String key, T defaultValue) { + if (key == null || key.isEmpty()) return defaultValue; + if (super.containsKey(key)) return (T) super.get(key); + String[] keys = key.split("\\.", 2); + if (!super.containsKey(keys[0])) return defaultValue; + Object value = super.get(keys[0]); + if (value instanceof ConfigSection) { + ConfigSection section = (ConfigSection) value; + return section.get(keys[1], defaultValue); + } + return defaultValue; + } + + /** + * Store value into config section + * + * @param key + * @param value + */ + public void set(String key, Object value) { + String[] subKeys = key.split("\\.", 2); + if (subKeys.length > 1) { + ConfigSection childSection = new ConfigSection(); + if (this.containsKey(subKeys[0]) && super.get(subKeys[0]) instanceof ConfigSection) + childSection = (ConfigSection) super.get(subKeys[0]); + childSection.set(subKeys[1], value); + super.put(subKeys[0], childSection); + } else super.put(subKeys[0], value); + } + + /** + * Check type of section element defined by key. Return true this element is ConfigSection + * + * @param key + * @return + */ + public boolean isSection(String key) { + Object value = this.get(key); + return value instanceof ConfigSection; + } + + /** + * Get config section element defined by key + * + * @param key + * @return + */ + public ConfigSection getSection(String key) { + return this.get(key, new ConfigSection()); + } + + //@formatter:off + + /** + * Get all ConfigSections in root path. + * Example config: + * a1: + * b1: + * c1: + * c2: + * a2: + * b2: + * c3: + * c4: + * a3: true + * a4: "hello" + * a5: 100 + *

+ * getSections() will return new ConfigSection, that contains sections a1 and a2 only. + * + * @return + */ + //@formatter:on + public ConfigSection getSections() { + return getSections(null); + } + + /** + * Get sections (and only sections) from provided path + * + * @param key - config section path, if null or empty root path will used. + * @return + */ + public ConfigSection getSections(String key) { + ConfigSection sections = new ConfigSection(); + ConfigSection parent = key == null || key.isEmpty() ? this.getAll() : getSection(key); + if (parent == null) return sections; + parent.forEach((key1, value) -> { + if (value instanceof ConfigSection) + sections.put(key1, value); + }); + return sections; + } + + /** + * Get int value of config section element + * + * @param key - key (inside) current section (default value equals to 0) + * @return + */ + public int getInt(String key) { + return this.getInt(key, 0); + } + + /** + * Get int value of config section element + * + * @param key - key (inside) current section + * @param defaultValue - default value that will returned if section element is not exists + * @return + */ + public int getInt(String key, int defaultValue) { + return this.get(key, ((Number) defaultValue)).intValue(); + } + + /** + * Check type of section element defined by key. Return true this element is Integer + * + * @param key + * @return + */ + public boolean isInt(String key) { + Object val = get(key); + return val instanceof Integer; + } + + /** + * Get long value of config section element + * + * @param key - key (inside) current section + * @return + */ + public long getLong(String key) { + return this.getLong(key, 0); + } + + /** + * Get long value of config section element + * + * @param key - key (inside) current section + * @param defaultValue - default value that will returned if section element is not exists + * @return + */ + public long getLong(String key, long defaultValue) { + return this.get(key, ((Number) defaultValue)).longValue(); + } + + /** + * Check type of section element defined by key. Return true this element is Long + * + * @param key + * @return + */ + public boolean isLong(String key) { + Object val = get(key); + return val instanceof Long; + } + + /** + * Get double value of config section element + * + * @param key - key (inside) current section + * @return + */ + public double getDouble(String key) { + return this.getDouble(key, 0); + } + + /** + * Get double value of config section element + * + * @param key - key (inside) current section + * @param defaultValue - default value that will returned if section element is not exists + * @return + */ + public double getDouble(String key, double defaultValue) { + return this.get(key, ((Number) defaultValue)).doubleValue(); + } + + /** + * Check type of section element defined by key. Return true this element is Double + * + * @param key + * @return + */ + public boolean isDouble(String key) { + Object val = get(key); + return val instanceof Double; + } + + /** + * Get String value of config section element + * + * @param key - key (inside) current section + * @return + */ + public String getString(String key) { + return this.getString(key, ""); + } + + /** + * Get String value of config section element + * + * @param key - key (inside) current section + * @param defaultValue - default value that will returned if section element is not exists + * @return + */ + public String getString(String key, String defaultValue) { + Object result = this.get(key, defaultValue); + return String.valueOf(result); + } + + /** + * Check type of section element defined by key. Return true this element is String + * + * @param key + * @return + */ + public boolean isString(String key) { + Object val = get(key); + return val instanceof String; + } + + /** + * Get boolean value of config section element + * + * @param key - key (inside) current section + * @return + */ + public boolean getBoolean(String key) { + return this.getBoolean(key, false); + } + + /** + * Get boolean value of config section element + * + * @param key - key (inside) current section + * @param defaultValue - default value that will returned if section element is not exists + * @return + */ + public boolean getBoolean(String key, boolean defaultValue) { + return this.get(key, defaultValue); + } + + /** + * Check type of section element defined by key. Return true this element is Integer + * + * @param key + * @return + */ + public boolean isBoolean(String key) { + Object val = get(key); + return val instanceof Boolean; + } + + /** + * Get List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getList(String key) { + return this.getList(key, null); + } + + /** + * Get List value of config section element + * + * @param key - key (inside) current section + * @param defaultList - default value that will returned if section element is not exists + * @return + */ + public List getList(String key, List defaultList) { + return this.get(key, defaultList); + } + + /** + * Check type of section element defined by key. Return true this element is List + * + * @param key + * @return + */ + public boolean isList(String key) { + Object val = get(key); + return val instanceof List; + } + + /** + * Get String List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getStringList(String key) { + List value = this.getList(key); + if (value == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + for (Object o : value) { + if (o instanceof String || o instanceof Number || o instanceof Boolean || o instanceof Character) { + result.add(String.valueOf(o)); + } + } + return result; + } + + /** + * Get Integer List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getIntegerList(String key) { + List list = getList(key); + if (list == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Integer) { + result.add((Integer) object); + } else if (object instanceof String) { + try { + result.add(Integer.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((int) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).intValue()); + } + } + return result; + } + + /** + * Get Boolean List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getBooleanList(String key) { + List list = getList(key); + if (list == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + for (Object object : list) { + if (object instanceof Boolean) { + result.add((Boolean) object); + } else if (object instanceof String) { + if (Boolean.TRUE.toString().equals(object)) { + result.add(true); + } else if (Boolean.FALSE.toString().equals(object)) { + result.add(false); + } + } + } + return result; + } + + /** + * Get Double List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getDoubleList(String key) { + List list = getList(key); + if (list == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + for (Object object : list) { + if (object instanceof Double) { + result.add((Double) object); + } else if (object instanceof String) { + try { + result.add(Double.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((double) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).doubleValue()); + } + } + return result; + } + + /** + * Get Float List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getFloatList(String key) { + List list = getList(key); + if (list == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + for (Object object : list) { + if (object instanceof Float) { + result.add((Float) object); + } else if (object instanceof String) { + try { + result.add(Float.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((float) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).floatValue()); + } + } + return result; + } + + /** + * Get Long List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getLongList(String key) { + List list = getList(key); + if (list == null) { + return new ArrayList<>(0); + } + List result = new ArrayList<>(); + for (Object object : list) { + if (object instanceof Long) { + result.add((Long) object); + } else if (object instanceof String) { + try { + result.add(Long.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((long) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).longValue()); + } + } + return result; + } + + /** + * Get Byte List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getByteList(String key) { + List list = getList(key); + + if (list == null) { + return new ArrayList<>(0); + } + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Byte) { + result.add((Byte) object); + } else if (object instanceof String) { + try { + result.add(Byte.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((byte) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).byteValue()); + } + } + + return result; + } + + /** + * Get Character List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getCharacterList(String key) { + List list = getList(key); + + if (list == null) { + return new ArrayList<>(0); + } + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Character) { + result.add((Character) object); + } else if (object instanceof String) { + String str = (String) object; + + if (str.length() == 1) { + result.add(str.charAt(0)); + } + } else if (object instanceof Number) { + result.add((char) ((Number) object).intValue()); + } + } + + return result; + } + + /** + * Get Short List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getShortList(String key) { + List list = getList(key); + + if (list == null) { + return new ArrayList<>(0); + } + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Short) { + result.add((Short) object); + } else if (object instanceof String) { + try { + result.add(Short.valueOf((String) object)); + } catch (Exception ex) { + //ignore + } + } else if (object instanceof Character) { + result.add((short) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).shortValue()); + } + } + + return result; + } + + /** + * Get Map List value of config section element + * + * @param key - key (inside) current section + * @return + */ + public List getMapList(String key) { + List list = getList(key); + List result = new ArrayList<>(); + + if (list == null) { + return result; + } + + for (Object object : list) { + if (object instanceof Map) { + result.add((Map) object); + } + } + + return result; + } + + /** + * Check existence of config section element + * + * @param key + * @param ignoreCase + * @return + */ + public boolean exists(String key, boolean ignoreCase) { + if (ignoreCase) key = key.toLowerCase(); + for (String existKey : this.getKeys(true)) { + if (ignoreCase) existKey = existKey.toLowerCase(); + if (existKey.equals(key)) return true; + } + return false; + } + + /** + * Check existence of config section element + * + * @param key + * @return + */ + public boolean exists(String key) { + return exists(key, false); + } + + /** + * Remove config section element + * + * @param key + */ + public void remove(String key) { + if (key == null || key.isEmpty()) return; + if (super.containsKey(key)) super.remove(key); + else if (this.containsKey(".")) { + String[] keys = key.split("\\.", 2); + if (super.get(keys[0]) instanceof ConfigSection) { + ConfigSection section = (ConfigSection) super.get(keys[0]); + section.remove(keys[1]); + } + } + } + + /** + * Get all keys + * + * @param child - true = include child keys + * @return + */ + public Set getKeys(boolean child) { + Set keys = new LinkedHashSet<>(); + this.forEach((key, value) -> { + keys.add(key); + if (value instanceof ConfigSection) { + if (child) + ((ConfigSection) value).getKeys(true).forEach(childKey -> keys.add(key + "." + childKey)); + } + }); + return keys; + } + + /** + * Get all keys + * + * @return + */ + public Set getKeys() { + return this.getKeys(true); + } +}