package org.saturnclient.config; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.LinkedHashMap; import java.util.Map; import org.saturnclient.config.property.NamespaceProperty; import org.saturnclient.config.property.Property; public class ConfigManager { private static final ObjectMapper MAPPER = new ObjectMapper(); private static File configFile; // SINGLE ROOT TREE private static final Map ROOT = new LinkedHashMap<>(); private static JsonNode cachedJson = null; private final Map currentMap; private final String[] path; /** * Creates a root namespace */ public ConfigManager(File configFile, String namespace) { ConfigManager.configFile = configFile; this.currentMap = new LinkedHashMap<>(); this.path = new String[] { namespace }; ROOT.put(namespace, Property.namespace(currentMap)); } /** * Creates a root namespace */ public ConfigManager(String namespace) { this.currentMap = new LinkedHashMap<>(); this.path = new String[] { namespace }; ROOT.put(namespace, Property.namespace(currentMap)); } /** * Creates a sub-namespace inside a parent */ public ConfigManager(ConfigManager parent, String namespace) { this.currentMap = new LinkedHashMap<>(); this.path = new String[parent.path.length + 1]; System.arraycopy(parent.path, 0, this.path, 0, parent.path.length); this.path[this.path.length - 1] = namespace; parent.property(namespace, Property.namespace(currentMap)); } /** * Add a property */ public Property property(String name, Property value) { currentMap.put(name, value); loadSingleProperty(name, value); return value; } /** * Add a property with the line shown under its name */ public Property property(String name, Property value, String description) { value.description = description; return property(name, value); } /** * Add empty namespace */ public ConfigManager sub(String name) { return new ConfigManager(this, name); } /** * Load entire config */ public void load() { JsonNode json = loadAndCache(); if (json == null || !json.isObject()) return; loadRecursive((ObjectNode) json, ROOT); } /** * Save entire config */ public static void save() { ObjectNode rootJson = MAPPER.createObjectNode(); saveRecursive(rootJson, ROOT); try { MAPPER.writerWithDefaultPrettyPrinter().writeValue(configFile, rootJson); } catch (IOException e) { e.printStackTrace(); } } /** * Recursive save */ private static void saveRecursive(ObjectNode json, Map map) { map.forEach((name, prop) -> { if (prop instanceof NamespaceProperty n) { ObjectNode child = json.objectNode(); saveRecursive(child, n.value); json.set(name, child); } else { json.set(name, prop.toJson()); } }); } /** * Recursive load */ private static void loadRecursive(ObjectNode json, Map map) { map.forEach((name, prop) -> { JsonNode element = json.get(name); if (element == null) return; if (prop instanceof NamespaceProperty n) { loadRecursive((ObjectNode) element, n.value); } else { prop.loadFromJson(element); } }); } /** * Load only a single property immediately after creation */ private void loadSingleProperty(String name, Property prop) { JsonNode element = getSaved(name); if (element != null) { prop.loadFromJson(element); } } private JsonNode getSaved(String name) { JsonNode source = cachedJson != null ? cachedJson : loadAndCache(); if (source == null || !source.isObject()) return null; JsonNode currentScope = source; for (String segment : path) { JsonNode next = currentScope.get(segment); if (next != null && next.isObject()) { currentScope = next; } else { return null; } } return currentScope.get(name); } /** * Reads file and caches json */ private JsonNode loadAndCache() { try { if (!configFile.exists()) { Files.writeString(configFile.toPath(), "{}"); cachedJson = MAPPER.createObjectNode(); return cachedJson; } cachedJson = MAPPER.readTree(configFile); return cachedJson; } catch (IOException e) { e.printStackTrace(); return null; } } public Map getProperties() { return currentMap; } }