-
-
Save Xemiru/b62394174cbe5cbcd051 to your computer and use it in GitHub Desktop.
Configuration utility to help load configurations with defaults.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import static com.google.common.base.Preconditions.checkNotNull; | |
import com.google.common.base.Function; | |
import ninja.leaping.configurate.ConfigurationNode; | |
import org.slf4j.Logger; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.annotation.Nonnull; | |
import javax.annotation.Nullable; | |
/** | |
* A utility class for configuration loading and | |
* (apparently) value verification. | |
* | |
* @author Xemiru | |
* @version 0.2 | |
* | |
* vhistory: | |
* v0.1 released | |
* v0.2 added functionality for Functions | |
* v0.3 switch to using root nodes instead of source | |
* files | |
*/ | |
public class ConfigUtil { | |
private static Logger log; | |
public static void setLogger(Logger log) { | |
ConfigUtil.log = log; | |
} | |
/** | |
* Returns a mapping of configuration values harvested | |
* from the given HOCON configuration file. | |
* | |
* <p>This variant of the method omits the warning | |
* message parameter, defaulting it to null.</p> | |
* | |
* @param root the ConfigurationNode to search for | |
* values | |
* @param defaults a mapping of default values of the | |
* configuration | |
* | |
* @return a Map with String keys and Object values | |
* representative of the values retrieved from | |
* the configuration, including the default | |
* values for configuration items that were | |
* found unset or invalid | |
* | |
* @see #loadValues(ConfigurationNode, Map, String) | |
*/ | |
public static Map<String, Object> loadValues(@Nonnull ConfigurationNode root, @Nonnull Map<String, Object> defaults) { | |
return loadValues(root, defaults, null); | |
} | |
/** | |
* Returns a mapping of configuration values harvested | |
* from the given HOCON configuration file. | |
* | |
* <p>The default values mapping is used as reference to | |
* what values should be harvested and what type they | |
* should be received as. The String keys simply | |
* represent the path to the configuration value, in the | |
* notation of <code>path.to.a.value</code>, with the | |
* addition of path of the parent node provided by the | |
* <code>root</code> parameter. The Object values are | |
* the default value to return if the value is found to | |
* be unset or invalid.</p> | |
* | |
* <p>The types of Object values found within the | |
* default mapping is enforced upon the configuration | |
* values loaded. Optionally, when the loaded value is | |
* found unset or invalid, a warning is sent to the | |
* console regarding the value in question. The warning | |
* supports the presence of two String parameters. The | |
* full key of the configuration node is inserted into | |
* <code>%1$s</code>. The expected type of value | |
* (provided by {@link #getFriendlyName(Class)}) is | |
* inserted into <code>%2$s</code>. The default value | |
* (provided by {@link Object#toString()}) is inserted | |
* into <code>%3$s</code>. The warning is not sent if it | |
* is set to be null, or if the class does not have a | |
* reference to a logger.</p> | |
* | |
* <p>As standard nature of configuration files, the | |
* types of object available to be received is limited, | |
* resulting in support only for the types allowed by | |
* the Configurate library:</p> | |
* | |
* <li>Boolean</li> | |
* <li>Double</li> | |
* <li>Float/Long/Integer (treated similarly in reading, | |
* e.g. 1 = 1F/1/1)</li> | |
* <li>String</li> | |
* <li>List<String></li></p> | |
* | |
* <p>Because List objects are of a class with generic | |
* type arguments, they cannot be checked to ensure they | |
* hold Strings. Regardless, if a valid value is loaded, | |
* the returned mapping will always result in a | |
* List<String> as the value for List objects.</p> | |
* | |
* <p>All other types should be stored serialized into a | |
* String to be converted back into the desired type | |
* upon retrieval. If an invalid type is detected within | |
* the mapping, this method throws an | |
* UnsupportedOperationException.</p> | |
* | |
* <p>As a special case, if the type of the default | |
* value is a {@link Function}, then the String | |
* harvested from the value is given to the function to | |
* parse. Implementations of the function should return | |
* null if the value is found invalid, and should return | |
* the default value when null is passed to its | |
* <code>apply()</code> method. The value returned by | |
* the <code>apply()</code> method is the value placed | |
* in the returned mapping.</p> | |
* | |
* @param root the ConfigurationNode to search for | |
* values | |
* @param defaults a mapping of default values of the | |
* configuration | |
* @param unsetOrInvalidWarning the warning to send to | |
* the console if an unset or invalid | |
* configuration value is encountered, or | |
* null to disable the warning | |
* | |
* @return a Map with String keys and Object values | |
* representative of the values retrieved from | |
* the configuration, including the default | |
* values for configuration items that were | |
* found unset or invalid | |
*/ | |
@SuppressWarnings({ "unchecked", "rawtypes" }) | |
public static Map<String, Object> loadValues(@Nonnull ConfigurationNode root, @Nonnull Map<String, Object> defaults, @Nullable String unsetOrInvalidWarning) { | |
checkNotNull(root, "source"); | |
checkNotNull(defaults, "defaults"); | |
Map<String, Object> values = new HashMap<String, Object>(); | |
for(String key : defaults.keySet()) { | |
ConfigurationNode valueNode = root.getNode((Object[]) key.split("\\.")); | |
Object defaultValue = defaults.get(key); | |
values.put(key, | |
parseValue(valueNode, | |
defaults.get(key), | |
unsetOrInvalidWarning == null ? null : String.format(unsetOrInvalidWarning, | |
key, | |
getFriendlyName(defaults.get(key)), | |
defaultValue instanceof Function ? ((Function) defaultValue).apply(null).toString() : defaultValue.toString() | |
) | |
) | |
); | |
} | |
return values; | |
} | |
/** | |
* Organized method. Returns the value of the | |
* configuration node as the type of the given default | |
* value, or the default value itself if the value of | |
* the node was found non-existant or invalid as per the | |
* rules of the target type. | |
* | |
* @param valueNode confignode | |
* @param defaultt object | |
* @param warning string | |
* | |
* @return object | |
*/ | |
@SuppressWarnings({ "unchecked", "rawtypes" }) | |
private static Object parseValue(ConfigurationNode valueNode, Object defaultt, String warning) { | |
Object returned = null; | |
if(!valueNode.isVirtual()) { | |
if(!valueNode.hasListChildren() && !valueNode.hasMapChildren()) { | |
if(defaultt instanceof String) { | |
returned = valueNode.getString(); | |
} else if(defaultt instanceof Function) { | |
returned = ((Function) defaultt).apply((Object) valueNode.getString()); | |
} else if(defaultt instanceof Boolean) { | |
if(isBoolean(valueNode.getString())) { | |
returned = valueNode.getBoolean(); | |
} | |
} else if(defaultt instanceof Integer) { | |
if(isInteger(valueNode.getString())) { | |
returned = valueNode.getInt(); | |
} | |
} else if(defaultt instanceof Double) { | |
if(isDouble(valueNode.getString())) { | |
returned = valueNode.getDouble(); | |
} | |
} else if(defaultt instanceof Long) { | |
if(isLong(valueNode.getString())) { | |
returned = valueNode.getLong(); | |
} | |
} else if(defaultt instanceof Float) { | |
if(isFloat(valueNode.getString())) { | |
returned = valueNode.getFloat(); | |
} | |
} else { | |
throw new UnsupportedOperationException("Unsupported object type " + defaultt.getClass().getSimpleName()); | |
} | |
} else if(valueNode.hasListChildren()) { | |
if(defaultt instanceof List) { | |
returned = valueNode.<String> getList(new Function<Object, String>() { | |
public String apply(Object element) { | |
return element.toString(); | |
} | |
}); | |
} | |
} | |
} | |
if(returned == null) { | |
if(log != null && warning != null) { | |
log.warn(warning); | |
} | |
if(defaultt instanceof Function) { | |
returned = ((Function) defaultt).apply(null); | |
} else { | |
returned = defaultt; | |
} | |
} | |
return returned; | |
} | |
/** | |
* Returns whether or not the given String represents a | |
* boolean value. | |
*/ | |
public static boolean isBoolean(String booleann) { | |
if(booleann == null) { | |
return false; | |
} | |
return booleann.equalsIgnoreCase("true") || booleann.equalsIgnoreCase("false"); | |
} | |
/** | |
* Returns whether or not the given String represents a | |
* double value. | |
*/ | |
public static boolean isDouble(String doublee) { | |
if(doublee == null) { | |
return false; | |
} | |
try { | |
Double.parseDouble(doublee); | |
} catch(Exception e) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns whether or not the given String represents an | |
* integer value. | |
*/ | |
public static boolean isInteger(String integer) { | |
if(integer == null) { | |
return false; | |
} | |
try { | |
Integer.parseInt(integer); | |
} catch(Exception e) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns whether or not the given String represents a | |
* long value. | |
*/ | |
public static boolean isLong(String longg) { | |
if(longg == null) { | |
return false; | |
} | |
try { | |
Long.parseLong(longg); | |
} catch(Exception e) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns whether or not the given String represents a | |
* float value. | |
*/ | |
public static boolean isFloat(String floatt) { | |
if(floatt == null) { | |
return false; | |
} | |
String parsedFloat = floatt; | |
if(!parsedFloat.endsWith("F")) { | |
parsedFloat = parsedFloat + "F"; | |
} | |
try { | |
Float.parseFloat(parsedFloat); | |
} catch(Exception e) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns a more user-friendly name of the given class, | |
* if supported. | |
* | |
* @param clazz the class to get the name of | |
* | |
* @return a more user-friendly name, or the class name | |
* returned by {@link Class#getSimpleName()} if | |
* unsupported | |
*/ | |
private static String getFriendlyName(Object object) { | |
Class<?> clazz = object.getClass(); | |
if(clazz == String.class || object instanceof Function) { | |
return "text"; | |
} else if(clazz == Boolean.class) { | |
return "true or false"; | |
} else if(clazz == Double.class) { | |
return "decimal"; | |
} else if(clazz == Integer.class || clazz == Long.class) { | |
return "number"; | |
} else if(clazz == Float.class) { | |
return "decimal or number"; | |
} else if(clazz == List.class) { | |
return "list of texts"; | |
} else { | |
return clazz.getSimpleName(); | |
} | |
} | |
/** | |
* no instance 4 u | |
*/ | |
private ConfigUtil() {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment