Add Support for Enums in Config Beans.

This commit is contained in:
Karthick Sankarachary 2016-06-02 09:32:59 -07:00
parent 23c5306038
commit 0791e41a03
6 changed files with 167 additions and 0 deletions

View File

@ -597,6 +597,22 @@ public interface Config extends ConfigMergeable {
*/ */
String getString(String path); String getString(String path);
/**
* @param enumClass
* an enum class
* @param <T>
* a generic denoting a specific type of enum
* @param path
* path expression
* @return the {@code Enum} value at the requested path
* of the requested enum class
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to an Enum
*/
public <T extends Enum<T>> T getEnum(Class<T> enumClass, String path);
/** /**
* @param path * @param path
* path expression * path expression
@ -882,6 +898,25 @@ public interface Config extends ConfigMergeable {
*/ */
List<String> getStringList(String path); List<String> getStringList(String path);
/**
* Gets a list value with {@code Enum} elements. Throws if the
* path is unset or null or not a list or contains values not
* convertible to {@code Enum}.
*
* @param enumClass
* the enum class
* @param <T>
* a generic denoting a specific type of enum
* @param path
* the path to the list value.
* @return the list at the path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to a list of {@code Enum}
*/
<T extends Enum<T>> List<T> getEnumList(Class<T> enumClass, String path);
/** /**
* Gets a list value with object elements. Throws if the * Gets a list value with object elements. Throws if the
* path is unset or null or not a list or contains values not * path is unset or null or not a list or contains values not

View File

@ -175,6 +175,10 @@ public class ConfigBeanImpl {
return config.getValue(configPropName); return config.getValue(configPropName);
} else if (parameterClass == ConfigList.class) { } else if (parameterClass == ConfigList.class) {
return config.getList(configPropName); return config.getList(configPropName);
} else if (parameterClass.isEnum()) {
@SuppressWarnings("unchecked")
Enum enumValue = config.getEnum((Class<Enum>) parameterClass, configPropName);
return enumValue;
} else if (hasAtLeastOneBeanProperty(parameterClass)) { } else if (hasAtLeastOneBeanProperty(parameterClass)) {
return createInternal(config.getConfig(configPropName), parameterClass); return createInternal(config.getConfig(configPropName), parameterClass);
} else { } else {
@ -207,6 +211,10 @@ public class ConfigBeanImpl {
return config.getObjectList(configPropName); return config.getObjectList(configPropName);
} else if (elementType == ConfigValue.class) { } else if (elementType == ConfigValue.class) {
return config.getList(configPropName); return config.getList(configPropName);
} else if (((Class<?>) elementType).isEnum()) {
@SuppressWarnings("unchecked")
List<Enum> enumValues = config.getEnumList((Class<Enum>) elementType, configPropName);
return enumValues;
} else if (hasAtLeastOneBeanProperty((Class<?>) elementType)) { } else if (hasAtLeastOneBeanProperty((Class<?>) elementType)) {
List<Object> beanList = new ArrayList<Object>(); List<Object> beanList = new ArrayList<Object>();
List<? extends Config> configList = config.getConfigList(configPropName); List<? extends Config> configList = config.getConfigList(configPropName);

View File

@ -247,6 +247,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return (String) v.unwrapped(); return (String) v.unwrapped();
} }
@Override
public <T extends Enum<T>> T getEnum(Class<T> enumClass, String path) {
ConfigValue v = find(path, ConfigValueType.STRING);
return getEnumValue(path, enumClass, v);
}
@Override @Override
public ConfigList getList(String path) { public ConfigList getList(String path) {
AbstractConfigValue v = find(path, ConfigValueType.LIST); AbstractConfigValue v = find(path, ConfigValueType.LIST);
@ -381,6 +387,35 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return getHomogeneousUnwrappedList(path, ConfigValueType.STRING); return getHomogeneousUnwrappedList(path, ConfigValueType.STRING);
} }
@Override
public <T extends Enum<T>> List<T> getEnumList(Class<T> enumClass, String path) {
List<ConfigString> enumNames = getHomogeneousWrappedList(path, ConfigValueType.STRING);
List<T> enumList = new ArrayList<T>();
for (ConfigString enumName : enumNames) {
enumList.add(getEnumValue(path, enumClass, enumName));
}
return enumList;
}
private <T extends Enum<T>> T getEnumValue(String path, Class<T> enumClass, ConfigValue enumConfigValue) {
String enumName = (String) enumConfigValue.unwrapped();
try {
return Enum.valueOf(enumClass, enumName);
} catch (IllegalArgumentException e) {
List<String> enumNames = new ArrayList<String>();
Enum[] enumConstants = enumClass.getEnumConstants();
if (enumConstants != null) {
for (Enum enumConstant : enumConstants) {
enumNames.add(enumConstant.name());
}
}
throw new ConfigException.BadValue(
enumConfigValue.origin(), path,
String.format("The enum class %s has no constant of the name '%s' (should be one of %s.)",
enumClass.getSimpleName(), enumName, enumNames));
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends ConfigValue> List<T> getHomogeneousWrappedList( private <T extends ConfigValue> List<T> getHomogeneousWrappedList(
String path, ConfigValueType expected) { String path, ConfigValueType expected) {

View File

@ -0,0 +1,65 @@
package beanconfig;
import java.util.List;
public class EnumsConfig {
public enum Problem {
P1, P2, P3;
};
public enum Solution {
S1, S2, S3;
}
Problem problem;
List<Solution> solutions;
public Problem getProblem() {
return problem;
}
public void setProblem(Problem problem) {
this.problem = problem;
}
public List<Solution> getSolutions() {
return solutions;
}
public void setSolutions(List<Solution> solutions) {
this.solutions = solutions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof EnumsConfig)) {
return false;
}
EnumsConfig that = (EnumsConfig) o;
if (getProblem() != that.getProblem()) {
return false;
}
return getSolutions() == that.getSolutions();
}
@Override
public int hashCode() {
int result = getProblem() != null ? getProblem().hashCode() : 0;
result = 31 * result + (getSolutions() != null ? getSolutions().hashCode() : 0);
return result;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("EnumsConfig{");
sb.append("problem=").append(problem);
sb.append(", solution=").append(solutions);
sb.append('}');
return sb.toString();
}
}

View File

@ -93,6 +93,10 @@
"list" : [1,2,3], "list" : [1,2,3],
"unwrappedMap" : ${validation} "unwrappedMap" : ${validation}
}, },
"enums" : {
"problem" : "P1",
"solutions" : ["S1", "S3"]
},
"objects" : { "objects" : {
"valueObject": { "valueObject": {
"mandatoryValue": "notNull" "mandatoryValue": "notNull"

View File

@ -3,6 +3,7 @@
*/ */
package com.typesafe.config.impl package com.typesafe.config.impl
import beanconfig.EnumsConfig.{ Solution, Problem }
import com.typesafe.config._ import com.typesafe.config._
import java.io.{ InputStream, InputStreamReader } import java.io.{ InputStream, InputStreamReader }
@ -13,6 +14,7 @@ import org.junit.Assert._
import org.junit._ import org.junit._
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.collection.mutable.ArrayBuffer
class ConfigBeanFactoryTest extends TestUtils { class ConfigBeanFactoryTest extends TestUtils {
@ -70,6 +72,14 @@ class ConfigBeanFactoryTest extends TestUtils {
assertEquals("yes", beanConfig.getYes) assertEquals("yes", beanConfig.getYes)
} }
@Test
def testCreateEnum() {
val beanConfig: EnumsConfig = ConfigBeanFactory.create(loadConfig().getConfig("enums"), classOf[EnumsConfig])
assertNotNull(beanConfig)
assertEquals(Problem.P1, beanConfig.getProblem)
assertEquals(ArrayBuffer(Solution.S1, Solution.S3), beanConfig.getSolutions.asScala)
}
@Test @Test
def testCreateNumber() { def testCreateNumber() {
val beanConfig: NumbersConfig = ConfigBeanFactory.create(loadConfig().getConfig("numbers"), classOf[NumbersConfig]) val beanConfig: NumbersConfig = ConfigBeanFactory.create(loadConfig().getConfig("numbers"), classOf[NumbersConfig])
@ -190,6 +200,16 @@ class ConfigBeanFactoryTest extends TestUtils {
assertTrue("error about the right property", e.getMessage.contains("notBean")) assertTrue("error about the right property", e.getMessage.contains("notBean"))
} }
@Test
def testNotAnEnumField() {
val e = intercept[ConfigException.BadValue] {
ConfigBeanFactory.create(parseConfig("{problem=P1,solutions=[S4]}"), classOf[EnumsConfig])
}
assertTrue("invalid value error", e.getMessage.contains("Invalid value"))
assertTrue("error about the right property", e.getMessage.contains("solutions"))
assertTrue("error enumerates the enum constants", e.getMessage.contains("should be one of [S1, S2, S3]"))
}
@Test @Test
def testUnsupportedListElement() { def testUnsupportedListElement() {
val e = intercept[ConfigException.BadBean] { val e = intercept[ConfigException.BadBean] {