/* * Copyright: Almende B.V. (2014), Rotterdam, The Netherlands * License: The Apache Software License, Version 2.0 */ package com.almende.eve.config; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import com.almende.util.TypeUtil; import com.almende.util.jackson.JOM; import com.almende.util.uuid.UUID; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * The Class Config. */ public class Config extends ObjectNode { private static final Logger LOG = Logger.getLogger(Config.class .getName()); private static Config global = new Config(); /** * Instantiates a new config. */ protected Config() { super(JOM.getInstance().getNodeFactory(), new MyKids(2)); } private Config(final ObjectNode node) { super(JOM.getInstance().getNodeFactory(), new MyKids( node != null ? node.size() : 2)); if (node != null) { this.setAll(node); } } static class MyKids extends LinkedHashMap<String, JsonNode> { private static final long serialVersionUID = 7450473642684901780L; public List<Config> pointers = new LinkedList<Config>(); private boolean configured = false; public MyKids(int capacity) { super(capacity); } @Override public int size() { return entrySet().size(); } @SuppressWarnings("unchecked") public Map<String, JsonNode> getParentMap() { return (Map<String, JsonNode>) super.clone(); } public void putAll(MyKids map) { super.putAll(map.getParentMap()); pointers.addAll(map.pointers); } @Override public Set<Entry<String, JsonNode>> entrySet() { final Map<String, Entry<String, JsonNode>> map = new HashMap<String, Entry<String, JsonNode>>(); for (Config other : pointers) { for (Entry<String, JsonNode> entry : other.getKids().entrySet()) { map.put(entry.getKey(), entry); } } for (Entry<String, JsonNode> entry : super.entrySet()) { map.put(entry.getKey(), entry); } return new HashSet<Entry<String, JsonNode>>(map.values()); } @Override public Collection<JsonNode> values() { Collection<JsonNode> result = new ArrayList<JsonNode>(); for (Entry<String, JsonNode> entry : entrySet()) { result.add(entry.getValue()); } return result; } @Override public Set<String> keySet() { Set<String> result = new HashSet<String>(); for (Entry<String, JsonNode> entry : entrySet()) { result.add(entry.getKey()); } return result; } @Override public void clear() { super.clear(); pointers.clear(); } @Override public JsonNode get(final Object key) { if (!(key instanceof String)) { return null; } final String strKey = (String) key; JsonNode res = super.get(key); if ((res != null && !res.isObject()) || "extends".equals(key)) { return res; } if (!configured && this.containsKey("extends")) { setupExtend(); } JsonNode otherres = null; Config result = null; for (Config other : pointers) { JsonNode val = other.get(strKey); if (val == null) { continue; } if (val.isArray()) { final ArrayNode array = JOM.createArrayNode(); for (JsonNode elem : val) { final Config item = new Config(); item.getKids().pointers.add(Config .decorate((ObjectNode) elem)); array.add(item); } super.put(strKey, array); otherres = array; } else if (!val.isObject()) { otherres = val; } else { if (result == null) { result = Config.decorate((ObjectNode) res); } result.getPointers().add(Config.decorate((ObjectNode) val)); } } if (res == null && otherres != null) { return otherres; } if (result != null) { if (res != null && res.isObject()) { // Merge the results result.setAll((ObjectNode) res); return result; } if (res == null) { return result; } } return res; } private JsonNode lget(final String... keys) { if (keys == null || keys.length == 0) { return null; } JsonNode node = this.get(keys[0]); if (node == null) { return null; } for (int i = 1; i < keys.length; i++) { node = node.get(keys[i]); if (node == null) { break; } } if (node == null) { return null; } return node; } /** * Setup extends. */ public void setupExtend() { configured = true; final JsonNode extNode = this.get("extends"); if (extNode == null || extNode.isNull()) { return; } JsonNode reference = null; boolean found = false; final String path = extNode.textValue(); if (path != null && !path.equals("")) { reference = this.lget(path.split("/")); if (reference == null || reference.isNull()) { reference = global.lget(path.split("/")); found = true; } } if (reference != null) { Config refConf = null; if (reference instanceof Config) { refConf = (Config) reference; } else { refConf = new Config((ObjectNode) reference); } if (!found) { String ref = new UUID().toString(); global.set(ref, refConf); } pointers.add(refConf); } } } /** * Load a configuration file. * * @param type * the type, one of: ["yaml","json"] * @param is * the inputStream from the config file * @return the config */ public static Config load(final String type, final InputStream is) { switch (type) { case "yaml": return YamlReader.load(is); case "json": return JsonReader.load(is); default: LOG.warning("Unknown file type given for configuration:'" + type + "' Trying to read Yaml format."); return YamlReader.load(is); } } /** * Poor man's file extension interpreter. * * @param filename * the filename * @return the type */ public static String getType(final String filename) { return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase( Locale.ENGLISH); } /** * Decorate. * * @param node * the node * @return the config */ public static Config decorate(final ObjectNode node) { Config res = null; if (node instanceof Config) { res = (Config) node; } else { res = new Config(node); } return res; } private MyKids getKids() { return (MyKids) this._children; } /** * Extend this configuration with the other tree, overwriting existing * fields, adding new ones. * * @param node * the node */ public void extend(final ObjectNode node) { if (node != null) { this.setAll(node); if (node instanceof Config) { getKids().pointers.addAll(((Config) node).getKids().pointers); } } } /** * Gets the global. * * @return the global */ public static ObjectNode getGlobal() { return global; } /** * Gets the pointers. * * @return the pointers */ public List<Config> getPointers() { return getKids().pointers; } /** * Sets the pointers. * * @param pointers * the new pointers */ public void setPointers(List<Config> pointers) { getKids().pointers = pointers; } /** * Load templates. * * @param fieldName * the field name */ public void loadTemplates(final String fieldName) { if (this.has(fieldName)) { global.set(fieldName, this.get(fieldName)); } } /** * Sets the class path. * * @param className * the new class */ public void setClassName(final String className) { this.put("class", className); } /** * Gets the class path. * * @return the class path */ public String getClassName() { if (this.has("class")) { return this.get("class").asText(); } return null; } /** * Gets the classname of the builder for this type. * * @param className * the classname of the builder */ public void setBuilder(final String className) { this.put("builder", className); } /** * Gets the builder className for this type. * * @return the classname of the builder */ public String getBuilder() { if (this.has("builder")) { return this.get("builder").asText(); } else { LOG.warning(this.getClass().getName()+": Couldn't find 'builder' field, falling back to the backwards compatibility 'class' field."); return getClassName(); } } private JsonNode lget(final String... keys) { if (keys == null || keys.length == 0) { return null; } JsonNode node = this; for (final String key : keys) { node = node.get(key); if (node == null) { break; } } if (node == null) { return null; } return node; } /** * Gets the. * * @param <T> * the generic type * @param keys * the keys * @return the json node */ public <T> T get(final String... keys) { JsonNode node = lget(keys); final TypeUtil<T> tu = new TypeUtil<T>() {}; return tu.inject(node); } @Override public ObjectNode deepCopy() { final ObjectNode result = JOM.createObjectNode(); for (Config other : getKids().pointers) { result.setAll(other.deepCopy()); } result.setAll(super.deepCopy()); return result; } }