package openeye.config; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.Table; import com.google.common.io.Closer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; 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.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.util.Map; import openeye.Log; public class GsonConfigProcessingEngine implements IConfigProcessingEngine { private static final String VALUE_TAG = "value"; private static final String COMMENT_TAG = "comment"; private static JsonElement parse(File file) { try { InputStream stream = new FileInputStream(file); try { Reader fileReader = new InputStreamReader(stream, Charsets.UTF_8); JsonReader jsonReader = new JsonReader(fileReader); jsonReader.setLenient(true); return Streams.parse(jsonReader); } finally { stream.close(); } } catch (IOException e) { throw Throwables.propagate(e); } } private static void write(File file, JsonElement element) { try { Closer closer = Closer.create(); try { FileOutputStream stream = closer.register(new FileOutputStream(file)); Writer fileWriter = closer.register(new OutputStreamWriter(stream, Charsets.UTF_8)); JsonWriter jsonWriter = closer.register(new JsonWriter(fileWriter)); jsonWriter.setIndent(" "); Streams.write(element, jsonWriter); } finally { closer.close(); } } catch (IOException e) { throw Throwables.propagate(e); } } private static JsonElement dumpConfig(Table<String, String, IConfigPropertyHolder> properties) { JsonObject result = new JsonObject(); Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create(); for (Map.Entry<String, Map<String, IConfigPropertyHolder>> category : properties.rowMap().entrySet()) { JsonObject categoryNode = new JsonObject(); for (Map.Entry<String, IConfigPropertyHolder> property : category.getValue().entrySet()) { JsonObject propertyNode = new JsonObject(); IConfigPropertyHolder propertyHolder = property.getValue(); final String comment = propertyHolder.comment(); if (!Strings.isNullOrEmpty(comment)) propertyNode.addProperty(COMMENT_TAG, comment); try { Object value = propertyHolder.getValue(); JsonElement serialized = gson.toJsonTree(value); propertyNode.add(VALUE_TAG, serialized); } catch (Exception e) { Log.warn(e, "Failed to serialize property %s:%s", propertyHolder.category(), propertyHolder.name()); } categoryNode.add(property.getKey(), propertyNode); } result.add(category.getKey(), categoryNode); } return result; } private static boolean loadConfig(JsonElement parsed, Table<String, String, IConfigPropertyHolder> properties) { if (!parsed.isJsonObject()) return true; Gson gson = new Gson(); JsonObject rootNode = parsed.getAsJsonObject(); boolean missingFields = false; for (Map.Entry<String, Map<String, IConfigPropertyHolder>> e : properties.rowMap().entrySet()) { JsonElement categoryTmp = rootNode.get(e.getKey()); missingFields |= parseCategory(categoryTmp, e.getValue(), gson); } return missingFields; } private static boolean parseCategory(JsonElement categoryElement, Map<String, IConfigPropertyHolder> properties, Gson gson) { if (!(categoryElement instanceof JsonObject)) return true; JsonObject categoryNode = categoryElement.getAsJsonObject(); boolean missingFields = false; for (Map.Entry<String, IConfigPropertyHolder> e : properties.entrySet()) { JsonElement propertyValue = categoryNode.get(e.getKey()); missingFields |= parseProperty(propertyValue, e.getValue(), gson); } return missingFields; } private static boolean parseProperty(JsonElement propertyElement, IConfigPropertyHolder property, Gson gson) { if (!(propertyElement instanceof JsonObject)) return true; JsonObject propertyNode = propertyElement.getAsJsonObject(); JsonElement value = propertyNode.get(VALUE_TAG); if (value == null) return true; try { Object parsedValue = gson.fromJson(value, property.getType()); property.setValue(parsedValue); } catch (Exception e) { Log.warn(e, "Failed to parse value of field %s:%s", property.category(), property.name()); return true; } JsonElement comment = propertyNode.get(COMMENT_TAG); final String expectedComment = property.comment(); if (comment == null) { return !Strings.isNullOrEmpty(expectedComment); } else if (comment.isJsonPrimitive()) { String commentValue = comment.getAsString(); return !expectedComment.equals(commentValue); } return true; } @Override public boolean loadConfig(File source, Table<String, String, IConfigPropertyHolder> properties) { if (source.exists()) { try { JsonElement parsed = parse(source); return loadConfig(parsed, properties); } catch (Exception e) { Log.warn(e, "Failed to parse file %s, using defaults", source); } } return true; } @Override public void dumpConfig(File source, Table<String, String, IConfigPropertyHolder> properties) { try { JsonElement serialized = dumpConfig(properties); write(source, serialized); } catch (Exception e) { Log.warn(e, "Failed to save config to file %s", source); } } }