Skip to content

Instantly share code, notes, and snippets.

@Xemiru
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Xemiru/b62394174cbe5cbcd051 to your computer and use it in GitHub Desktop.
Save Xemiru/b62394174cbe5cbcd051 to your computer and use it in GitHub Desktop.
Configuration utility to help load configurations with defaults.
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&lt;String&gt;</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&lt;String&gt; 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