package openmods.config.simple; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import openmods.Log; public class ConfigProcessor { public interface UpdateListener { public void valueSet(String value); } private static class EntryMeta { public final int version; public final String value; public String[] comment; public final transient UpdateListener listener; public EntryMeta(String value, int version, UpdateListener listener) { this.version = version; this.value = value; this.listener = listener; this.comment = null; } public EntryMeta(String value, int version, UpdateListener listener, String... comment) { this.version = version; this.value = value; this.listener = listener; this.comment = comment; } @Override public String toString() { return "[value=" + value + ", version=" + version + ", comment=" + Arrays.toString(comment) + "]"; } } private Map<String, EntryMeta> entries = Maps.newHashMap(); public void addEntry(String name, int version, String defaultValue, UpdateListener listener, String... comment) { Preconditions.checkNotNull(listener); addEntry(name, new EntryMeta(defaultValue, version, listener, comment)); } public void addEntry(String name, int version, String defaultValue, UpdateListener listener) { Preconditions.checkNotNull(listener); addEntry(name, new EntryMeta(defaultValue, version, listener)); } private void addEntry(String name, EntryMeta meta) { EntryMeta prev = entries.put(name, meta); Preconditions.checkState(prev == null, "Duplicate property '%s': [%s, %s]", name, prev, meta); } private static class EntryCollection extends HashMap<String, EntryMeta> { private static final long serialVersionUID = -3851628207393131247L; } public void process(File config) { boolean modified = false; Log.debug("Parsing config file '%s'", config); Map<String, EntryMeta> values = readFile(config); if (values == null) values = Maps.newHashMap(); for (String key : Sets.intersection(values.keySet(), entries.keySet())) { EntryMeta defaultEntry = entries.get(key); EntryMeta actualEntry = values.get(key); if (defaultEntry.version > actualEntry.version) { Log.warn("Config value '%s' replaced with newer version", key); values.put(key, defaultEntry); modified = true; } else { if (!Arrays.equals(defaultEntry.comment, actualEntry.comment)) { actualEntry.comment = defaultEntry.comment; modified = true; } defaultEntry.listener.valueSet(actualEntry.value); } } Set<String> removed = Sets.difference(values.keySet(), entries.keySet()); if (!removed.isEmpty()) { Log.warn("Removing obsolete values: '%s'", removed); modified = true; for (String key : ImmutableSet.copyOf(removed)) values.remove(key); } Set<String> added = Sets.difference(entries.keySet(), values.keySet()); if (!added.isEmpty()) { Log.warn("Adding new values: '%s'", added); modified = true; for (String key : added) { final EntryMeta entry = entries.get(key); values.put(key, entry); entry.listener.valueSet(entry.value); } } if (modified) { writeFile(config, values); } } private static void writeFile(File output, Map<String, EntryMeta> values) { try { OutputStream stream = new FileOutputStream(output); try { Writer writer = new OutputStreamWriter(stream, Charsets.UTF_8); try { Gson gson = new GsonBuilder().setPrettyPrinting().create(); gson.toJson(values, writer); } finally { writer.close(); } } finally { stream.close(); } } catch (IOException e) { throw Throwables.propagate(e); } } private static Map<String, EntryMeta> readFile(File input) { if (!input.exists()) return null; try { InputStream stream = new FileInputStream(input); try { Reader reader = new InputStreamReader(stream, Charsets.UTF_8); Gson gson = new Gson(); return gson.fromJson(reader, EntryCollection.class); } finally { stream.close(); } } catch (IOException e) { throw Throwables.propagate(e); } } }