mirror of
https://github.com/lightbend/config.git
synced 2025-01-28 21:20:07 +08:00
Merge pull request #264 from typesafehub/beanfactory
Add ConfigBeanFactory
This commit is contained in:
commit
edd4f14c99
@ -0,0 +1,43 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
import com.typesafe.config.impl.ConfigBeanImpl;
|
||||
|
||||
/**
|
||||
* Factory for automatically creating a Java class from a {@link Config}.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>
|
||||
* Config configSource = ConfigFactory.load().getConfig("foo");
|
||||
* FooConfig config = ConfigBeanFactory.create(configSource, FooConfig.class);
|
||||
* </pre>
|
||||
*
|
||||
* The Java class should follow JavaBean conventions. Field types
|
||||
* can be any of the types you can normally get from a {@link
|
||||
* Config}, including <code>java.time.Duration</code> or {@link
|
||||
* ConfigMemorySize}. Fields may also be another JavaBean-style
|
||||
* class.
|
||||
*
|
||||
* Fields are mapped to config by converting the config key to
|
||||
* camel case. So the key <code>foo-bar</code> becomes JavaBean
|
||||
* setter <code>setFooBar</code>.
|
||||
*/
|
||||
public class ConfigBeanFactory {
|
||||
|
||||
/**
|
||||
* Creates an instance of a class, initializing its fields from a {@link Config}.
|
||||
* @param config source of config information
|
||||
* @param clazz class to be instantiated
|
||||
* @param <T> the type of the class to be instantiated
|
||||
* @return an instance of the class populated with data from the config
|
||||
* @throws ConfigException.BadBean
|
||||
* If something is wrong with the JavaBean
|
||||
* @throws ConfigException.ValidationFailed
|
||||
* If the config doesn't conform to the bean's implied schema
|
||||
* @throws ConfigException
|
||||
* Can throw the same exceptions as the getters on <code>Config</code>
|
||||
*/
|
||||
public static <T> T create(Config config, Class<T> clazz) {
|
||||
return ConfigBeanImpl.createInternal(config, clazz);
|
||||
}
|
||||
}
|
@ -385,6 +385,21 @@ public abstract class ConfigException extends RuntimeException implements Serial
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some problem with a JavaBean we are trying to initialize.
|
||||
*/
|
||||
public static class BadBean extends BugOrBroken {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BadBean(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BadBean(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception that doesn't fall into any other category.
|
||||
*/
|
||||
|
@ -0,0 +1,240 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigList;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigMemorySize;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
public class ConfigBeanImpl {
|
||||
|
||||
/**
|
||||
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
|
||||
* may change.
|
||||
*/
|
||||
public static <T> T createInternal(Config config, Class<T> clazz) {
|
||||
if (((SimpleConfig)config).root().resolveStatus() != ResolveStatus.RESOLVED)
|
||||
throw new ConfigException.NotResolved(
|
||||
"need to Config#resolve() a config before using it to initialize a bean, see the API docs for Config#resolve()");
|
||||
|
||||
Map<String, AbstractConfigValue> configProps = new HashMap<String, AbstractConfigValue>();
|
||||
Map<String, String> originalNames = new HashMap<String, String>();
|
||||
for (Map.Entry<String, ConfigValue> configProp : config.root().entrySet()) {
|
||||
String originalName = configProp.getKey();
|
||||
String camelName = ConfigImplUtil.toCamelCase(originalName);
|
||||
// if a setting is in there both as some hyphen name and the camel name,
|
||||
// the camel one wins
|
||||
if (originalNames.containsKey(camelName) && originalName != camelName) {
|
||||
// if we aren't a camel name to start with, we lose.
|
||||
// if we are or we are the first matching key, we win.
|
||||
} else {
|
||||
configProps.put(camelName, (AbstractConfigValue) configProp.getValue());
|
||||
originalNames.put(camelName, originalName);
|
||||
}
|
||||
}
|
||||
|
||||
BeanInfo beanInfo = null;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(clazz);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new ConfigException.BadBean("Could not get bean information for class " + clazz.getName(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
List<PropertyDescriptor> beanProps = new ArrayList<PropertyDescriptor>();
|
||||
for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) {
|
||||
if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) {
|
||||
continue;
|
||||
}
|
||||
beanProps.add(beanProp);
|
||||
}
|
||||
|
||||
// Try to throw all validation issues at once (this does not comprehensively
|
||||
// find every issue, but it should find common ones).
|
||||
List<ConfigException.ValidationProblem> problems = new ArrayList<ConfigException.ValidationProblem>();
|
||||
for (PropertyDescriptor beanProp : beanProps) {
|
||||
Method setter = beanProp.getWriteMethod();
|
||||
Class parameterClass = setter.getParameterTypes()[0];
|
||||
|
||||
ConfigValueType expectedType = getValueTypeOrNull(parameterClass);
|
||||
if (expectedType != null) {
|
||||
String name = originalNames.get(beanProp.getName());
|
||||
if (name == null)
|
||||
name = beanProp.getName();
|
||||
Path path = Path.newKey(name);
|
||||
AbstractConfigValue configValue = configProps.get(beanProp.getName());
|
||||
if (configValue != null) {
|
||||
SimpleConfig.checkValid(path, expectedType, configValue, problems);
|
||||
} else {
|
||||
SimpleConfig.addMissing(problems, expectedType, path, config.origin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!problems.isEmpty()) {
|
||||
throw new ConfigException.ValidationFailed(problems);
|
||||
}
|
||||
|
||||
// Fill in the bean instance
|
||||
T bean = clazz.newInstance();
|
||||
for (PropertyDescriptor beanProp : beanProps) {
|
||||
Method setter = beanProp.getWriteMethod();
|
||||
Type parameterType = setter.getGenericParameterTypes()[0];
|
||||
Class parameterClass = setter.getParameterTypes()[0];
|
||||
Object unwrapped = getValue(clazz, parameterType, parameterClass, config, originalNames.get(beanProp.getName()));
|
||||
setter.invoke(bean, unwrapped);
|
||||
}
|
||||
return bean;
|
||||
} catch (InstantiationException e) {
|
||||
throw new ConfigException.BadBean(clazz.getName() + " needs a public no-args constructor to be used as a bean", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ConfigException.BadBean(clazz.getName() + " getters and setters are not accessible, they must be for use as a bean", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new ConfigException.BadBean("Calling bean method on " + clazz.getName() + " caused an exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
// we could magically make this work in many cases by doing
|
||||
// getAnyRef() (or getValue().unwrapped()), but anytime we
|
||||
// rely on that, we aren't doing the type conversions Config
|
||||
// usually does, and we will throw ClassCastException instead
|
||||
// of a nicer error message giving the name of the bad
|
||||
// setting. So, instead, we only support a limited number of
|
||||
// types plus you can always use Object, ConfigValue, Config,
|
||||
// ConfigObject, etc. as an escape hatch.
|
||||
private static Object getValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) {
|
||||
if (parameterClass == Boolean.class || parameterClass == boolean.class) {
|
||||
return config.getBoolean(configPropName);
|
||||
} else if (parameterClass == Integer.class || parameterClass == int.class) {
|
||||
return config.getInt(configPropName);
|
||||
} else if (parameterClass == Double.class || parameterClass == double.class) {
|
||||
return config.getDouble(configPropName);
|
||||
} else if (parameterClass == Long.class || parameterClass == long.class) {
|
||||
return config.getLong(configPropName);
|
||||
} else if (parameterClass == String.class) {
|
||||
return config.getString(configPropName);
|
||||
} else if (parameterClass == Duration.class) {
|
||||
return config.getDuration(configPropName);
|
||||
} else if (parameterClass == ConfigMemorySize.class) {
|
||||
return config.getMemorySize(configPropName);
|
||||
} else if (parameterClass == Object.class) {
|
||||
return config.getAnyRef(configPropName);
|
||||
} else if (parameterClass == List.class) {
|
||||
return getListValue(beanClass, parameterType, parameterClass, config, configPropName);
|
||||
} else if (parameterClass == Map.class) {
|
||||
// we could do better here, but right now we don't.
|
||||
Type[] typeArgs = ((ParameterizedType)parameterType).getActualTypeArguments();
|
||||
if (typeArgs[0] != String.class || typeArgs[1] != Object.class) {
|
||||
throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported Map<" + typeArgs[0] + "," + typeArgs[1] + ">, only Map<String,Object> is supported right now");
|
||||
}
|
||||
return config.getObject(configPropName).unwrapped();
|
||||
} else if (parameterClass == Config.class) {
|
||||
return config.getConfig(configPropName);
|
||||
} else if (parameterClass == ConfigObject.class) {
|
||||
return config.getObject(configPropName);
|
||||
} else if (parameterClass == ConfigValue.class) {
|
||||
return config.getValue(configPropName);
|
||||
} else if (parameterClass == ConfigList.class) {
|
||||
return config.getList(configPropName);
|
||||
} else if (hasAtLeastOneBeanProperty(parameterClass)) {
|
||||
return createInternal(config.getConfig(configPropName), parameterClass);
|
||||
} else {
|
||||
throw new ConfigException.BadBean("Bean property " + configPropName + " of class " + beanClass.getName() + " has unsupported type " + parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getListValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) {
|
||||
Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0];
|
||||
|
||||
if (elementType == Boolean.class) {
|
||||
return config.getBooleanList(configPropName);
|
||||
} else if (elementType == Integer.class) {
|
||||
return config.getIntList(configPropName);
|
||||
} else if (elementType == Double.class) {
|
||||
return config.getDoubleList(configPropName);
|
||||
} else if (elementType == Long.class) {
|
||||
return config.getLongList(configPropName);
|
||||
} else if (elementType == String.class) {
|
||||
return config.getStringList(configPropName);
|
||||
} else if (elementType == Duration.class) {
|
||||
return config.getDurationList(configPropName);
|
||||
} else if (elementType == ConfigMemorySize.class) {
|
||||
return config.getMemorySizeList(configPropName);
|
||||
} else if (elementType == Object.class) {
|
||||
return config.getAnyRefList(configPropName);
|
||||
} else if (elementType == Config.class) {
|
||||
return config.getConfigList(configPropName);
|
||||
} else if (elementType == ConfigObject.class) {
|
||||
return config.getObjectList(configPropName);
|
||||
} else if (elementType == ConfigValue.class) {
|
||||
return config.getList(configPropName);
|
||||
} else {
|
||||
throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType);
|
||||
}
|
||||
}
|
||||
|
||||
// null if we can't easily say; this is heuristic/best-effort
|
||||
private static ConfigValueType getValueTypeOrNull(Class<?> parameterClass) {
|
||||
if (parameterClass == Boolean.class || parameterClass == boolean.class) {
|
||||
return ConfigValueType.BOOLEAN;
|
||||
} else if (parameterClass == Integer.class || parameterClass == int.class) {
|
||||
return ConfigValueType.NUMBER;
|
||||
} else if (parameterClass == Double.class || parameterClass == double.class) {
|
||||
return ConfigValueType.NUMBER;
|
||||
} else if (parameterClass == Long.class || parameterClass == long.class) {
|
||||
return ConfigValueType.NUMBER;
|
||||
} else if (parameterClass == String.class) {
|
||||
return ConfigValueType.STRING;
|
||||
} else if (parameterClass == Duration.class) {
|
||||
return null;
|
||||
} else if (parameterClass == ConfigMemorySize.class) {
|
||||
return null;
|
||||
} else if (parameterClass == List.class) {
|
||||
return ConfigValueType.LIST;
|
||||
} else if (parameterClass == Map.class) {
|
||||
return ConfigValueType.OBJECT;
|
||||
} else if (parameterClass == Config.class) {
|
||||
return ConfigValueType.OBJECT;
|
||||
} else if (parameterClass == ConfigObject.class) {
|
||||
return ConfigValueType.OBJECT;
|
||||
} else if (parameterClass == ConfigList.class) {
|
||||
return ConfigValueType.LIST;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasAtLeastOneBeanProperty(Class<?> clazz) {
|
||||
BeanInfo beanInfo = null;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(clazz);
|
||||
} catch (IntrospectionException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) {
|
||||
if (beanProp.getReadMethod() != null && beanProp.getWriteMethod() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -230,4 +230,18 @@ final public class ConfigImplUtil {
|
||||
SerializedConfigValue.writeOrigin(new DataOutputStream(out), (SimpleConfigOrigin) origin,
|
||||
null);
|
||||
}
|
||||
|
||||
static String toCamelCase(String originalName) {
|
||||
String[] words = originalName.split("-+");
|
||||
StringBuilder nameBuilder = new StringBuilder(originalName.length());
|
||||
for (String word : words) {
|
||||
if (nameBuilder.length() == 0) {
|
||||
nameBuilder.append(word);
|
||||
} else {
|
||||
nameBuilder.append(word.substring(0, 1).toUpperCase());
|
||||
nameBuilder.append(word.substring(1));
|
||||
}
|
||||
}
|
||||
return nameBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -726,31 +726,54 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem));
|
||||
}
|
||||
|
||||
private static String getDesc(ConfigValueType type) {
|
||||
return type.name().toLowerCase();
|
||||
}
|
||||
|
||||
private static String getDesc(ConfigValue refValue) {
|
||||
if (refValue instanceof AbstractConfigObject) {
|
||||
AbstractConfigObject obj = (AbstractConfigObject) refValue;
|
||||
if (obj.isEmpty())
|
||||
return "object";
|
||||
else
|
||||
if (!obj.isEmpty())
|
||||
return "object with keys " + obj.keySet();
|
||||
} else if (refValue instanceof SimpleConfigList) {
|
||||
return "list";
|
||||
else
|
||||
return getDesc(refValue.valueType());
|
||||
} else {
|
||||
return refValue.valueType().name().toLowerCase();
|
||||
return getDesc(refValue.valueType());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMissing(List<ConfigException.ValidationProblem> accumulator,
|
||||
ConfigValue refValue, Path path, ConfigOrigin origin) {
|
||||
String refDesc, Path path, ConfigOrigin origin) {
|
||||
addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: "
|
||||
+ getDesc(refValue));
|
||||
+ refDesc);
|
||||
}
|
||||
|
||||
private static void addMissing(List<ConfigException.ValidationProblem> accumulator,
|
||||
ConfigValue refValue, Path path, ConfigOrigin origin) {
|
||||
addMissing(accumulator, getDesc(refValue), path, origin);
|
||||
}
|
||||
|
||||
// JavaBean stuff uses this
|
||||
static void addMissing(List<ConfigException.ValidationProblem> accumulator,
|
||||
ConfigValueType refType, Path path, ConfigOrigin origin) {
|
||||
addMissing(accumulator, getDesc(refType), path, origin);
|
||||
}
|
||||
|
||||
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
|
||||
String refDesc, AbstractConfigValue actual, Path path) {
|
||||
addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render()
|
||||
+ "', expecting: " + refDesc + " but got: "
|
||||
+ getDesc(actual));
|
||||
}
|
||||
|
||||
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
|
||||
ConfigValue refValue, AbstractConfigValue actual, Path path) {
|
||||
addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render()
|
||||
+ "', expecting: " + getDesc(refValue) + " but got: "
|
||||
+ getDesc(actual));
|
||||
addWrongType(accumulator, getDesc(refValue), actual, path);
|
||||
}
|
||||
|
||||
private static void addWrongType(List<ConfigException.ValidationProblem> accumulator,
|
||||
ConfigValueType refType, AbstractConfigValue actual, Path path) {
|
||||
addWrongType(accumulator, getDesc(refType), actual, path);
|
||||
}
|
||||
|
||||
private static boolean couldBeNull(AbstractConfigValue v) {
|
||||
@ -759,23 +782,32 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
|
||||
private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) {
|
||||
if (couldBeNull((AbstractConfigValue) reference) || couldBeNull(value)) {
|
||||
if (couldBeNull((AbstractConfigValue) reference)) {
|
||||
// we allow any setting to be null
|
||||
return true;
|
||||
} else if (reference instanceof AbstractConfigObject) {
|
||||
} else {
|
||||
return haveCompatibleTypes(reference.valueType(), value);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean haveCompatibleTypes(ConfigValueType referenceType, AbstractConfigValue value) {
|
||||
if (referenceType == ConfigValueType.NULL || couldBeNull(value)) {
|
||||
// we allow any setting to be null
|
||||
return true;
|
||||
} else if (referenceType == ConfigValueType.OBJECT) {
|
||||
if (value instanceof AbstractConfigObject) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (reference instanceof SimpleConfigList) {
|
||||
} else if (referenceType == ConfigValueType.LIST) {
|
||||
// objects may be convertible to lists if they have numeric keys
|
||||
if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (reference instanceof ConfigString) {
|
||||
} else if (referenceType == ConfigValueType.STRING) {
|
||||
// assume a string could be gotten as any non-collection type;
|
||||
// allows things like getMilliseconds including domain-specific
|
||||
// interpretations of strings
|
||||
@ -784,7 +816,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
// assume a string could be gotten as any non-collection type
|
||||
return true;
|
||||
} else {
|
||||
if (reference.valueType() == value.valueType()) {
|
||||
if (referenceType == value.valueType()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -833,6 +865,22 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the JavaBean-based validator
|
||||
static void checkValid(Path path, ConfigValueType referenceType, AbstractConfigValue value,
|
||||
List<ConfigException.ValidationProblem> accumulator) {
|
||||
if (haveCompatibleTypes(referenceType, value)) {
|
||||
if (referenceType == ConfigValueType.LIST && value instanceof SimpleConfigObject) {
|
||||
// attempt conversion of indexed object to list
|
||||
AbstractConfigValue listValue = DefaultTransformer.transform(value,
|
||||
ConfigValueType.LIST);
|
||||
if (!(listValue instanceof SimpleConfigList))
|
||||
addWrongType(accumulator, referenceType, value, path);
|
||||
}
|
||||
} else {
|
||||
addWrongType(accumulator, referenceType, value, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
|
||||
List<ConfigException.ValidationProblem> accumulator) {
|
||||
// Unmergeable is supposed to be impossible to encounter in here
|
||||
|
130
config/src/test/java/beanconfig/ArraysConfig.java
Normal file
130
config/src/test/java/beanconfig/ArraysConfig.java
Normal file
@ -0,0 +1,130 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.Duration;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigMemorySize;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
|
||||
public class ArraysConfig {
|
||||
|
||||
List<Integer> empty;
|
||||
List<Integer> ofInt;
|
||||
List<String> ofString;
|
||||
List<Double> ofDouble;
|
||||
List<Long> ofLong;
|
||||
List<Object> ofNull;
|
||||
List<Boolean> ofBoolean;
|
||||
List<Object> ofObject;
|
||||
List<Config> ofConfig;
|
||||
List<ConfigObject> ofConfigObject;
|
||||
List<ConfigValue> ofConfigValue;
|
||||
List<Duration> ofDuration;
|
||||
List<ConfigMemorySize> ofMemorySize;
|
||||
|
||||
public List<Integer> getEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public void setEmpty(List<Integer> empty) {
|
||||
this.empty = empty;
|
||||
}
|
||||
|
||||
public List<Integer> getOfInt() {
|
||||
return ofInt;
|
||||
}
|
||||
|
||||
public void setOfInt(List<Integer> ofInt) {
|
||||
this.ofInt = ofInt;
|
||||
}
|
||||
|
||||
public List<String> getOfString() {
|
||||
return ofString;
|
||||
}
|
||||
|
||||
public void setOfString(List<String> ofString) {
|
||||
this.ofString = ofString;
|
||||
}
|
||||
|
||||
public List<Double> getOfDouble() {
|
||||
return ofDouble;
|
||||
}
|
||||
|
||||
public void setOfDouble(List<Double> ofDouble) {
|
||||
this.ofDouble = ofDouble;
|
||||
}
|
||||
|
||||
public List<Object> getOfNull() {
|
||||
return ofNull;
|
||||
}
|
||||
|
||||
public void setOfNull(List<Object> ofNull) {
|
||||
this.ofNull = ofNull;
|
||||
}
|
||||
|
||||
public List<Boolean> getOfBoolean() {
|
||||
return ofBoolean;
|
||||
}
|
||||
|
||||
public void setOfBoolean(List<Boolean> ofBoolean) {
|
||||
this.ofBoolean = ofBoolean;
|
||||
}
|
||||
|
||||
public List<Object> getOfObject() {
|
||||
return ofObject;
|
||||
}
|
||||
|
||||
public void setOfObject(List<Object> ofObject) {
|
||||
this.ofObject = ofObject;
|
||||
}
|
||||
|
||||
public List<Long> getOfLong() {
|
||||
return ofLong;
|
||||
}
|
||||
|
||||
public void setOfLong(List<Long> ofLong) {
|
||||
this.ofLong = ofLong;
|
||||
}
|
||||
|
||||
public List<Config> getOfConfig() {
|
||||
return ofConfig;
|
||||
}
|
||||
|
||||
public void setOfConfig(List<Config> ofConfig) {
|
||||
this.ofConfig = ofConfig;
|
||||
}
|
||||
|
||||
public List<ConfigObject> getOfConfigObject() {
|
||||
return ofConfigObject;
|
||||
}
|
||||
|
||||
public void setOfConfigObject(List<ConfigObject> ofConfigObject) {
|
||||
this.ofConfigObject = ofConfigObject;
|
||||
}
|
||||
|
||||
public List<ConfigValue> getOfConfigValue() {
|
||||
return ofConfigValue;
|
||||
}
|
||||
|
||||
public void setOfConfigValue(List<ConfigValue> ofConfigValue) {
|
||||
this.ofConfigValue = ofConfigValue;
|
||||
}
|
||||
|
||||
public List<Duration> getOfDuration() {
|
||||
return ofDuration;
|
||||
}
|
||||
|
||||
public void setOfDuration(List<Duration> ofDuration) {
|
||||
this.ofDuration = ofDuration;
|
||||
}
|
||||
|
||||
public List<ConfigMemorySize> getOfMemorySize() {
|
||||
return ofMemorySize;
|
||||
}
|
||||
|
||||
public void setOfMemorySize(List<ConfigMemorySize> ofMemorySize) {
|
||||
this.ofMemorySize = ofMemorySize;
|
||||
}
|
||||
}
|
59
config/src/test/java/beanconfig/BooleansConfig.java
Normal file
59
config/src/test/java/beanconfig/BooleansConfig.java
Normal file
@ -0,0 +1,59 @@
|
||||
package beanconfig;
|
||||
|
||||
|
||||
public class BooleansConfig {
|
||||
Boolean trueVal;
|
||||
Boolean trueValAgain;
|
||||
Boolean falseVal;
|
||||
Boolean falseValAgain;
|
||||
Boolean on;
|
||||
boolean off;
|
||||
|
||||
public Boolean getTrueVal() {
|
||||
return trueVal;
|
||||
}
|
||||
|
||||
public void setTrueVal(Boolean trueVal) {
|
||||
this.trueVal = trueVal;
|
||||
}
|
||||
|
||||
public Boolean getTrueValAgain() {
|
||||
return trueValAgain;
|
||||
}
|
||||
|
||||
public void setTrueValAgain(Boolean trueValAgain) {
|
||||
this.trueValAgain = trueValAgain;
|
||||
}
|
||||
|
||||
public Boolean getFalseVal() {
|
||||
return falseVal;
|
||||
}
|
||||
|
||||
public void setFalseVal(Boolean falseVal) {
|
||||
this.falseVal = falseVal;
|
||||
}
|
||||
|
||||
public Boolean getFalseValAgain() {
|
||||
return falseValAgain;
|
||||
}
|
||||
|
||||
public void setFalseValAgain(Boolean falseValAgain) {
|
||||
this.falseValAgain = falseValAgain;
|
||||
}
|
||||
|
||||
public Boolean getOn() {
|
||||
return on;
|
||||
}
|
||||
|
||||
public void setOn(Boolean on) {
|
||||
this.on = on;
|
||||
}
|
||||
|
||||
public boolean getOff() {
|
||||
return off;
|
||||
}
|
||||
|
||||
public void setOff(boolean off) {
|
||||
this.off = off;
|
||||
}
|
||||
}
|
34
config/src/test/java/beanconfig/BytesConfig.java
Normal file
34
config/src/test/java/beanconfig/BytesConfig.java
Normal file
@ -0,0 +1,34 @@
|
||||
package beanconfig;
|
||||
|
||||
import com.typesafe.config.ConfigMemorySize;
|
||||
|
||||
public class BytesConfig {
|
||||
|
||||
private ConfigMemorySize kilobyte;
|
||||
private ConfigMemorySize kibibyte;
|
||||
private ConfigMemorySize thousandBytes;
|
||||
|
||||
public ConfigMemorySize getKilobyte() {
|
||||
return kilobyte;
|
||||
}
|
||||
|
||||
public void setKilobyte(ConfigMemorySize kilobyte) {
|
||||
this.kilobyte = kilobyte;
|
||||
}
|
||||
|
||||
public ConfigMemorySize getKibibyte() {
|
||||
return kibibyte;
|
||||
}
|
||||
|
||||
public void setKibibyte(ConfigMemorySize kibibyte) {
|
||||
this.kibibyte = kibibyte;
|
||||
}
|
||||
|
||||
public ConfigMemorySize getThousandBytes() {
|
||||
return thousandBytes;
|
||||
}
|
||||
|
||||
public void setThousandBytes(ConfigMemorySize thousandBytes) {
|
||||
this.thousandBytes = thousandBytes;
|
||||
}
|
||||
}
|
34
config/src/test/java/beanconfig/DurationsConfig.java
Normal file
34
config/src/test/java/beanconfig/DurationsConfig.java
Normal file
@ -0,0 +1,34 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class DurationsConfig {
|
||||
Duration second;
|
||||
Duration secondAsNumber;
|
||||
Duration halfSecond;
|
||||
|
||||
|
||||
public Duration getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setSecond(Duration second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public Duration getSecondAsNumber() {
|
||||
return secondAsNumber;
|
||||
}
|
||||
|
||||
public void setSecondAsNumber(Duration secondAsNumber) {
|
||||
this.secondAsNumber = secondAsNumber;
|
||||
}
|
||||
|
||||
public Duration getHalfSecond() {
|
||||
return halfSecond;
|
||||
}
|
||||
|
||||
public void setHalfSecond(Duration halfSecond) {
|
||||
this.halfSecond = halfSecond;
|
||||
}
|
||||
}
|
18
config/src/test/java/beanconfig/NotABeanFieldConfig.java
Normal file
18
config/src/test/java/beanconfig/NotABeanFieldConfig.java
Normal file
@ -0,0 +1,18 @@
|
||||
package beanconfig;
|
||||
|
||||
public class NotABeanFieldConfig {
|
||||
|
||||
public static class NotABean {
|
||||
int stuff;
|
||||
}
|
||||
|
||||
private NotABean notBean;
|
||||
|
||||
public NotABean getNotBean() {
|
||||
return notBean;
|
||||
}
|
||||
|
||||
public void setNotBean(NotABean notBean) {
|
||||
this.notBean = notBean;
|
||||
}
|
||||
}
|
62
config/src/test/java/beanconfig/NumbersConfig.java
Normal file
62
config/src/test/java/beanconfig/NumbersConfig.java
Normal file
@ -0,0 +1,62 @@
|
||||
package beanconfig;
|
||||
|
||||
|
||||
public class NumbersConfig {
|
||||
|
||||
|
||||
private int intVal;
|
||||
private Integer intObj;
|
||||
private long longVal;
|
||||
private Long longObj;
|
||||
private double doubleVal;
|
||||
private Double doubleObj;
|
||||
|
||||
|
||||
public int getIntVal() {
|
||||
return intVal;
|
||||
}
|
||||
|
||||
public void setIntVal(int intVal) {
|
||||
this.intVal = intVal;
|
||||
}
|
||||
|
||||
public Integer getIntObj() {
|
||||
return intObj;
|
||||
}
|
||||
|
||||
public void setIntObj(Integer intObj) {
|
||||
this.intObj = intObj;
|
||||
}
|
||||
|
||||
public long getLongVal() {
|
||||
return longVal;
|
||||
}
|
||||
|
||||
public void setLongVal(long longVal) {
|
||||
this.longVal = longVal;
|
||||
}
|
||||
|
||||
public Long getLongObj() {
|
||||
return longObj;
|
||||
}
|
||||
|
||||
public void setLongObj(Long longObj) {
|
||||
this.longObj = longObj;
|
||||
}
|
||||
|
||||
public double getDoubleVal() {
|
||||
return doubleVal;
|
||||
}
|
||||
|
||||
public void setDoubleVal(double doubleVal) {
|
||||
this.doubleVal = doubleVal;
|
||||
}
|
||||
|
||||
public Double getDoubleObj() {
|
||||
return doubleObj;
|
||||
}
|
||||
|
||||
public void setDoubleObj(Double doubleObj) {
|
||||
this.doubleObj = doubleObj;
|
||||
}
|
||||
}
|
22
config/src/test/java/beanconfig/PreferCamelNamesConfig.java
Normal file
22
config/src/test/java/beanconfig/PreferCamelNamesConfig.java
Normal file
@ -0,0 +1,22 @@
|
||||
package beanconfig;
|
||||
|
||||
|
||||
public class PreferCamelNamesConfig {
|
||||
|
||||
|
||||
private String fooBar;
|
||||
private String bazBar;
|
||||
|
||||
public String getFooBar() {
|
||||
return fooBar;
|
||||
}
|
||||
public void setFooBar(String v) {
|
||||
this.fooBar = v;
|
||||
}
|
||||
public String getBazBar() {
|
||||
return bazBar;
|
||||
}
|
||||
public void setBazBar(String v) {
|
||||
this.bazBar = v;
|
||||
}
|
||||
}
|
23
config/src/test/java/beanconfig/StringsConfig.java
Normal file
23
config/src/test/java/beanconfig/StringsConfig.java
Normal file
@ -0,0 +1,23 @@
|
||||
package beanconfig;
|
||||
|
||||
|
||||
public class StringsConfig {
|
||||
String abcd;
|
||||
String yes;
|
||||
|
||||
public String getAbcd() {
|
||||
return abcd;
|
||||
}
|
||||
|
||||
public void setAbcd(String s) {
|
||||
abcd = s;
|
||||
}
|
||||
|
||||
public String getYes() {
|
||||
return yes;
|
||||
}
|
||||
|
||||
public void setYes(String s) {
|
||||
yes = s;
|
||||
}
|
||||
}
|
15
config/src/test/java/beanconfig/TestBeanConfig.java
Normal file
15
config/src/test/java/beanconfig/TestBeanConfig.java
Normal file
@ -0,0 +1,15 @@
|
||||
package beanconfig;
|
||||
|
||||
public class TestBeanConfig {
|
||||
|
||||
|
||||
private NumbersConfig numbers;
|
||||
|
||||
public NumbersConfig getNumbers() {
|
||||
return numbers;
|
||||
}
|
||||
|
||||
public void setNumbers(NumbersConfig numbers) {
|
||||
this.numbers = numbers;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
public class UnsupportedListElementConfig {
|
||||
private List<URI> uri;
|
||||
|
||||
public List<URI> getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public void setUri(List<URI> uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
}
|
16
config/src/test/java/beanconfig/UnsupportedMapKeyConfig.java
Normal file
16
config/src/test/java/beanconfig/UnsupportedMapKeyConfig.java
Normal file
@ -0,0 +1,16 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class UnsupportedMapKeyConfig {
|
||||
private Map<Integer, Object> map;
|
||||
|
||||
public Map<Integer, Object> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public void setMap(Map<Integer, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class UnsupportedMapValueConfig {
|
||||
private Map<String, Integer> map;
|
||||
|
||||
public Map<String, Integer> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public void setMap(Map<String, Integer> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
}
|
44
config/src/test/java/beanconfig/ValidationBeanConfig.java
Normal file
44
config/src/test/java/beanconfig/ValidationBeanConfig.java
Normal file
@ -0,0 +1,44 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ValidationBeanConfig extends TestBeanConfig{
|
||||
|
||||
private String propNotListedInConfig;
|
||||
private int shouldBeInt;
|
||||
private boolean shouldBeBoolean;
|
||||
private List<Integer> shouldBeList;
|
||||
|
||||
public String getPropNotListedInConfig() {
|
||||
return propNotListedInConfig;
|
||||
}
|
||||
|
||||
public void setPropNotListedInConfig(String propNotListedInConfig) {
|
||||
this.propNotListedInConfig = propNotListedInConfig;
|
||||
}
|
||||
|
||||
public int getShouldBeInt() {
|
||||
return shouldBeInt;
|
||||
}
|
||||
|
||||
public void setShouldBeInt(int v) {
|
||||
shouldBeInt = v;
|
||||
}
|
||||
|
||||
public boolean getShouldBeBoolean() {
|
||||
return shouldBeBoolean;
|
||||
}
|
||||
|
||||
public void setShouldBeBoolean(boolean v) {
|
||||
shouldBeBoolean = v;
|
||||
}
|
||||
|
||||
public List<Integer> getShouldBeList() {
|
||||
return shouldBeList;
|
||||
}
|
||||
|
||||
public void setShouldBeList(List<Integer> v) {
|
||||
shouldBeList = v;
|
||||
}
|
||||
|
||||
}
|
68
config/src/test/java/beanconfig/ValuesConfig.java
Normal file
68
config/src/test/java/beanconfig/ValuesConfig.java
Normal file
@ -0,0 +1,68 @@
|
||||
package beanconfig;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigList;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
|
||||
// test bean for various "uncooked" values
|
||||
public class ValuesConfig {
|
||||
|
||||
Object obj;
|
||||
Config config;
|
||||
ConfigObject configObj;
|
||||
ConfigValue configValue;
|
||||
ConfigList list;
|
||||
Map<String,Object> unwrappedMap;
|
||||
|
||||
public Object getObj() {
|
||||
return obj;
|
||||
}
|
||||
|
||||
public void setObj(Object obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
public Config getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public ConfigObject getConfigObj() {
|
||||
return configObj;
|
||||
}
|
||||
|
||||
public void setConfigObj(ConfigObject configObj) {
|
||||
this.configObj = configObj;
|
||||
}
|
||||
|
||||
public ConfigValue getConfigValue() {
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public void setConfigValue(ConfigValue configValue) {
|
||||
this.configValue = configValue;
|
||||
}
|
||||
|
||||
public ConfigList getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(ConfigList list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public Map<String, Object> getUnwrappedMap() {
|
||||
return unwrappedMap;
|
||||
}
|
||||
|
||||
public void setUnwrappedMap(Map<String, Object> unwrappedMap) {
|
||||
this.unwrappedMap = unwrappedMap;
|
||||
}
|
||||
|
||||
}
|
86
config/src/test/resources/beanconfig/beanconfig01.conf
Normal file
86
config/src/test/resources/beanconfig/beanconfig01.conf
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"booleans" : {
|
||||
"trueVal" : true,
|
||||
"trueValAgain" : ${booleans.trueVal},
|
||||
"falseVal" : false,
|
||||
"falseValAgain" : ${booleans.falseVal},
|
||||
"on" : "on",
|
||||
"off" : "off"
|
||||
}
|
||||
|
||||
"numbers" : {
|
||||
"byteVal" : "1",
|
||||
"byteObj" : ${numbers.byteVal},
|
||||
"shortVal" : "2",
|
||||
"shortObj" : ${numbers.shortVal},
|
||||
"intVal" : "3",
|
||||
"intObj" : ${numbers.intVal},
|
||||
"longVal" : "4",
|
||||
"longObj" : ${numbers.longVal},
|
||||
"doubleVal" : "1.0",
|
||||
"doubleObj" : ${numbers.doubleVal}
|
||||
}
|
||||
//
|
||||
"strings" : {
|
||||
"abcd" : "abcd",
|
||||
"abcd-again" : ${strings.a}${strings.b}${strings.c}${strings.d},
|
||||
"a" : "a",
|
||||
"b" : "b",
|
||||
"c" : "c",
|
||||
"d" : "d",
|
||||
"concatenated" : null bar 42 baz true 3.14 hi,
|
||||
"double" : "3.14",
|
||||
"number" : "57",
|
||||
"null" : "null",
|
||||
"true" : "true",
|
||||
"yes" : "yes",
|
||||
"false" : "false",
|
||||
"no" : "no"
|
||||
},
|
||||
|
||||
"arrays" : {
|
||||
"empty" : [],
|
||||
"ofInt" : [1, 2, 3],
|
||||
"ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ],
|
||||
"of-double" : [3.14, 4.14, 5.14],
|
||||
"of-long" : { "1" : 32, "2" : 42, "3" : 52 }, // object-to-list conversion
|
||||
"ofNull" : [null, null, null],
|
||||
"ofBoolean" : [true, false],
|
||||
"ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}],
|
||||
"ofObject" : [${numbers}, ${booleans}, ${strings}],
|
||||
"ofConfig" : [${numbers}, ${booleans}, ${strings}],
|
||||
"ofConfigObject" : [${numbers}, ${booleans}, ${strings}],
|
||||
"ofConfigValue" : [1, 2, "a"],
|
||||
"ofDuration" : [1, 2h, 3 days],
|
||||
"ofMemorySize" : [1024, 1M, 1G]
|
||||
},
|
||||
"bytes" : {
|
||||
"kilobyte" : "1kB",
|
||||
"kibibyte" : "1K",
|
||||
"thousandBytes" : "1000B"
|
||||
},
|
||||
"durations" : {
|
||||
"second" : 1s,
|
||||
"secondAsNumber" : 1000,
|
||||
"halfSecond" : 0.5s
|
||||
},
|
||||
"validation" : {
|
||||
"shouldBeInt" : true,
|
||||
"should-be-boolean" : 42,
|
||||
"should-be-list" : "hello"
|
||||
},
|
||||
"preferCamelNames" : {
|
||||
"foo-bar" : "no",
|
||||
"fooBar" : "yes",
|
||||
"baz-bar" : "no",
|
||||
"bazBar" : "yes"
|
||||
},
|
||||
"values" : {
|
||||
"obj" : 42,
|
||||
"config" : ${strings},
|
||||
"configObj" : ${numbers},
|
||||
"configValue" : "hello world",
|
||||
"list" : [1,2,3],
|
||||
"unwrappedMap" : ${validation}
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Typesafe Inc. <http://typesafe.com>
|
||||
*/
|
||||
package com.typesafe.config.impl
|
||||
|
||||
import com.typesafe.config._
|
||||
|
||||
import java.io.{ InputStream, InputStreamReader }
|
||||
import java.time.Duration;
|
||||
|
||||
import beanconfig._
|
||||
import org.junit.Assert._
|
||||
import org.junit._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class ConfigBeanFactoryTest extends TestUtils {
|
||||
|
||||
@Test
|
||||
def toCamelCase() {
|
||||
assertEquals("configProp", ConfigImplUtil.toCamelCase("config-prop"))
|
||||
assertEquals("configProp", ConfigImplUtil.toCamelCase("configProp"))
|
||||
assertEquals("fooBar", ConfigImplUtil.toCamelCase("foo-----bar"))
|
||||
assertEquals("fooBar", ConfigImplUtil.toCamelCase("fooBar"))
|
||||
assertEquals("foo", ConfigImplUtil.toCamelCase("-foo"))
|
||||
assertEquals("bar", ConfigImplUtil.toCamelCase("bar-"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreate() {
|
||||
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf")
|
||||
val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs),
|
||||
ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve
|
||||
val beanConfig: TestBeanConfig = ConfigBeanFactory.create(config, classOf[TestBeanConfig])
|
||||
assertNotNull(beanConfig)
|
||||
// recursive bean inside the first bean
|
||||
assertEquals(3, beanConfig.getNumbers.getIntVal)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testValidation() {
|
||||
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf")
|
||||
val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs),
|
||||
ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve.getConfig("validation")
|
||||
val e = intercept[ConfigException.ValidationFailed] {
|
||||
ConfigBeanFactory.create(config, classOf[ValidationBeanConfig])
|
||||
}
|
||||
|
||||
val expecteds = Seq(Missing("propNotListedInConfig", 67, "string"),
|
||||
WrongType("shouldBeInt", 68, "number", "boolean"),
|
||||
WrongType("should-be-boolean", 69, "boolean", "number"),
|
||||
WrongType("should-be-list", 70, "list", "string"))
|
||||
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateBool() {
|
||||
val beanConfig: BooleansConfig = ConfigBeanFactory.create(loadConfig().getConfig("booleans"), classOf[BooleansConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(true, beanConfig.getTrueVal)
|
||||
assertEquals(false, beanConfig.getFalseVal)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateString() {
|
||||
val beanConfig: StringsConfig = ConfigBeanFactory.create(loadConfig().getConfig("strings"), classOf[StringsConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals("abcd", beanConfig.getAbcd)
|
||||
assertEquals("yes", beanConfig.getYes)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateNumber() {
|
||||
val beanConfig: NumbersConfig = ConfigBeanFactory.create(loadConfig().getConfig("numbers"), classOf[NumbersConfig])
|
||||
assertNotNull(beanConfig)
|
||||
|
||||
assertEquals(3, beanConfig.getIntVal)
|
||||
assertEquals(3, beanConfig.getIntObj)
|
||||
|
||||
assertEquals(4L, beanConfig.getLongVal)
|
||||
assertEquals(4L, beanConfig.getLongObj)
|
||||
|
||||
assertEquals(1.0, beanConfig.getDoubleVal, 1e-6)
|
||||
assertEquals(1.0, beanConfig.getDoubleObj, 1e-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateList() {
|
||||
val beanConfig: ArraysConfig = ConfigBeanFactory.create(loadConfig().getConfig("arrays"), classOf[ArraysConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(List().asJava, beanConfig.getEmpty)
|
||||
assertEquals(List(1, 2, 3).asJava, beanConfig.getOfInt)
|
||||
assertEquals(List(32L, 42L, 52L).asJava, beanConfig.getOfLong)
|
||||
assertEquals(List("a", "b", "c").asJava, beanConfig.getOfString)
|
||||
//assertEquals(List(List("a", "b", "c").asJava,
|
||||
// List("a", "b", "c").asJava,
|
||||
// List("a", "b", "c").asJava).asJava,
|
||||
// beanConfig.getOfArray)
|
||||
assertEquals(3, beanConfig.getOfObject.size)
|
||||
assertEquals(3, beanConfig.getOfDouble.size)
|
||||
assertEquals(3, beanConfig.getOfConfig.size)
|
||||
assertTrue(beanConfig.getOfConfig.get(0).isInstanceOf[Config])
|
||||
assertEquals(3, beanConfig.getOfConfigObject.size)
|
||||
assertTrue(beanConfig.getOfConfigObject.get(0).isInstanceOf[ConfigObject])
|
||||
assertEquals(List(intValue(1), intValue(2), stringValue("a")),
|
||||
beanConfig.getOfConfigValue.asScala)
|
||||
assertEquals(List(Duration.ofMillis(1), Duration.ofHours(2), Duration.ofDays(3)),
|
||||
beanConfig.getOfDuration.asScala)
|
||||
assertEquals(List(ConfigMemorySize.ofBytes(1024),
|
||||
ConfigMemorySize.ofBytes(1048576),
|
||||
ConfigMemorySize.ofBytes(1073741824)),
|
||||
beanConfig.getOfMemorySize.asScala)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateDuration() {
|
||||
val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(Duration.ofMillis(500), beanConfig.getHalfSecond)
|
||||
assertEquals(Duration.ofMillis(1000), beanConfig.getSecond)
|
||||
assertEquals(Duration.ofMillis(1000), beanConfig.getSecondAsNumber)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateBytes() {
|
||||
val beanConfig: BytesConfig = ConfigBeanFactory.create(loadConfig().getConfig("bytes"), classOf[BytesConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(ConfigMemorySize.ofBytes(1024), beanConfig.getKibibyte)
|
||||
assertEquals(ConfigMemorySize.ofBytes(1000), beanConfig.getKilobyte)
|
||||
assertEquals(ConfigMemorySize.ofBytes(1000), beanConfig.getThousandBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testPreferCamelNames() {
|
||||
val beanConfig = ConfigBeanFactory.create(loadConfig().getConfig("preferCamelNames"), classOf[PreferCamelNamesConfig])
|
||||
assertNotNull(beanConfig)
|
||||
|
||||
assertEquals("yes", beanConfig.getFooBar)
|
||||
assertEquals("yes", beanConfig.getBazBar)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testValues() {
|
||||
val beanConfig = ConfigBeanFactory.create(loadConfig().getConfig("values"), classOf[ValuesConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(42, beanConfig.getObj())
|
||||
assertEquals("abcd", beanConfig.getConfig.getString("abcd"))
|
||||
assertEquals(3, beanConfig.getConfigObj.toConfig.getInt("intVal"))
|
||||
assertEquals(stringValue("hello world"), beanConfig.getConfigValue)
|
||||
assertEquals(List(1, 2, 3).map(intValue(_)), beanConfig.getList.asScala)
|
||||
assertEquals(true, beanConfig.getUnwrappedMap.get("shouldBeInt"))
|
||||
assertEquals(42, beanConfig.getUnwrappedMap.get("should-be-boolean"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testNotABeanField() {
|
||||
val e = intercept[ConfigException.BadBean] {
|
||||
ConfigBeanFactory.create(parseConfig("notBean=42"), classOf[NotABeanFieldConfig])
|
||||
}
|
||||
assertTrue("unsupported type error", e.getMessage.contains("unsupported type"))
|
||||
assertTrue("error about the right property", e.getMessage.contains("notBean"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testUnsupportedListElement() {
|
||||
val e = intercept[ConfigException.BadBean] {
|
||||
ConfigBeanFactory.create(parseConfig("uri=[42]"), classOf[UnsupportedListElementConfig])
|
||||
}
|
||||
assertTrue("unsupported element type error", e.getMessage.contains("unsupported list element type"))
|
||||
assertTrue("error about the right property", e.getMessage.contains("uri"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testUnsupportedMapKey() {
|
||||
val e = intercept[ConfigException.BadBean] {
|
||||
ConfigBeanFactory.create(parseConfig("map={}"), classOf[UnsupportedMapKeyConfig])
|
||||
}
|
||||
assertTrue("unsupported map type error", e.getMessage.contains("unsupported Map"))
|
||||
assertTrue("error about the right property", e.getMessage.contains("'map'"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testUnsupportedMapValue() {
|
||||
val e = intercept[ConfigException.BadBean] {
|
||||
ConfigBeanFactory.create(parseConfig("map={}"), classOf[UnsupportedMapValueConfig])
|
||||
}
|
||||
assertTrue("unsupported map type error", e.getMessage.contains("unsupported Map"))
|
||||
assertTrue("error about the right property", e.getMessage.contains("'map'"))
|
||||
}
|
||||
|
||||
private def loadConfig(): Config = {
|
||||
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf")
|
||||
try {
|
||||
val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs),
|
||||
ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve
|
||||
config
|
||||
} finally {
|
||||
configIs.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ import java.util.concurrent.Callable
|
||||
import com.typesafe.config._
|
||||
import scala.reflect.ClassTag
|
||||
import scala.reflect.classTag
|
||||
import scala.collection.JavaConverters._
|
||||
import language.implicitConversions
|
||||
|
||||
abstract trait TestUtils {
|
||||
@ -740,4 +741,53 @@ abstract trait TestUtils {
|
||||
|
||||
protected def quoteJsonString(s: String): String =
|
||||
ConfigImplUtil.renderJsonString(s)
|
||||
|
||||
sealed abstract class Problem(path: String, line: Int) {
|
||||
def check(p: ConfigException.ValidationProblem) {
|
||||
assertEquals("matching path", path, p.path())
|
||||
assertEquals("matching line for " + path, line, p.origin().lineNumber())
|
||||
}
|
||||
|
||||
protected def assertMessage(p: ConfigException.ValidationProblem, re: String) {
|
||||
assertTrue("didn't get expected message for " + path + ": got '" + p.problem() + "'",
|
||||
p.problem().matches(re))
|
||||
}
|
||||
}
|
||||
|
||||
case class Missing(path: String, line: Int, expected: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "No setting.*" + path + ".*expecting.*" + expected + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
case class WrongType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "Wrong value type.*" + path + ".*expecting.*" + expected + ".*got.*" + got + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
case class WrongElementType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "List at.*" + path + ".*wrong value type.*expecting.*" + expected + ".*got.*element of.*" + got + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
protected def checkValidationException(e: ConfigException.ValidationFailed, expecteds: Seq[Problem]) {
|
||||
val problems = e.problems().asScala.toIndexedSeq.sortBy(_.path).sortBy(_.origin.lineNumber)
|
||||
|
||||
//for (problem <- problems)
|
||||
// System.err.println(problem.origin().description() + ": " + problem.path() + ": " + problem.problem())
|
||||
|
||||
for ((problem, expected) <- problems zip expecteds) {
|
||||
expected.check(problem)
|
||||
}
|
||||
assertEquals("found expected validation problems, got '" + problems + "' and expected '" + expecteds + "'",
|
||||
expecteds.size, problems.size)
|
||||
}
|
||||
}
|
||||
|
@ -13,55 +13,6 @@ import scala.io.Source
|
||||
|
||||
class ValidationTest extends TestUtils {
|
||||
|
||||
sealed abstract class Problem(path: String, line: Int) {
|
||||
def check(p: ConfigException.ValidationProblem) {
|
||||
assertEquals("matching path", path, p.path())
|
||||
assertEquals("matching line", line, p.origin().lineNumber())
|
||||
}
|
||||
|
||||
protected def assertMessage(p: ConfigException.ValidationProblem, re: String) {
|
||||
assertTrue("didn't get expected message for " + path + ": got '" + p.problem() + "'",
|
||||
p.problem().matches(re))
|
||||
}
|
||||
}
|
||||
|
||||
case class Missing(path: String, line: Int, expected: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "No setting.*" + path + ".*expecting.*" + expected + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
case class WrongType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "Wrong value type.*" + path + ".*expecting.*" + expected + ".*got.*" + got + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
case class WrongElementType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) {
|
||||
override def check(p: ConfigException.ValidationProblem) {
|
||||
super.check(p)
|
||||
val re = "List at.*" + path + ".*wrong value type.*expecting.*" + expected + ".*got.*element of.*" + got + ".*"
|
||||
assertMessage(p, re)
|
||||
}
|
||||
}
|
||||
|
||||
private def checkException(e: ConfigException.ValidationFailed, expecteds: Seq[Problem]) {
|
||||
val problems = e.problems().asScala.toIndexedSeq.sortBy(_.path).sortBy(_.origin.lineNumber)
|
||||
|
||||
//for (problem <- problems)
|
||||
// System.err.println(problem.origin().description() + ": " + problem.path() + ": " + problem.problem())
|
||||
|
||||
for ((problem, expected) <- problems zip expecteds) {
|
||||
expected.check(problem)
|
||||
}
|
||||
assertEquals("found expected validation problems, got '" + problems + "' and expected '" + expecteds + "'",
|
||||
expecteds.size, problems.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
def validation() {
|
||||
val reference = ConfigFactory.parseFile(resourceFile("validate-reference.conf"), ConfigParseOptions.defaults())
|
||||
@ -87,7 +38,7 @@ class ValidationTest extends TestUtils {
|
||||
Missing("a.b.c.d.e.f.j", 28, "boolean"),
|
||||
WrongType("a.b.c.d.e.f.i", 30, "boolean", "list"))
|
||||
|
||||
checkException(e, expecteds)
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -106,7 +57,7 @@ class ValidationTest extends TestUtils {
|
||||
Missing("a.b.c.d.e.f.j", 28, "boolean"),
|
||||
WrongType("a.b.c.d.e.f.i", 30, "boolean", "list"))
|
||||
|
||||
checkException(e, expecteds)
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -130,7 +81,7 @@ class ValidationTest extends TestUtils {
|
||||
|
||||
val expecteds = Seq(WrongType("a", 1, "list", "number"))
|
||||
|
||||
checkException(e, expecteds)
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -143,7 +94,7 @@ class ValidationTest extends TestUtils {
|
||||
|
||||
val expecteds = Seq(WrongElementType("a", 1, "boolean", "number"))
|
||||
|
||||
checkException(e, expecteds)
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -163,7 +114,7 @@ class ValidationTest extends TestUtils {
|
||||
|
||||
val expecteds = Seq(WrongType("a", 1, "list", "object"))
|
||||
|
||||
checkException(e, expecteds)
|
||||
checkValidationException(e, expecteds)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,5 @@
|
||||
addSbtPlugin("de.johoop" % "findbugs4sbt" % "1.2.1")
|
||||
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.1")
|
||||
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.6")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.6.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.2.1")
|
||||
|
Loading…
Reference in New Issue
Block a user