From 0791e41a035ffebe8caac2c4fe86002228a5522d Mon Sep 17 00:00:00 2001 From: Karthick Sankarachary Date: Thu, 2 Jun 2016 09:32:59 -0700 Subject: [PATCH] Add Support for Enums in Config Beans. --- .../main/java/com/typesafe/config/Config.java | 35 ++++++++++ .../typesafe/config/impl/ConfigBeanImpl.java | 8 +++ .../typesafe/config/impl/SimpleConfig.java | 35 ++++++++++ .../src/test/java/beanconfig/EnumsConfig.java | 65 +++++++++++++++++++ .../resources/beanconfig/beanconfig01.conf | 4 ++ .../config/impl/ConfigBeanFactoryTest.scala | 20 ++++++ 6 files changed, 167 insertions(+) create mode 100644 config/src/test/java/beanconfig/EnumsConfig.java diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 68031861..0097eed6 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -597,6 +597,22 @@ public interface Config extends ConfigMergeable { */ String getString(String path); + /** + * @param enumClass + * an enum class + * @param + * 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 getEnum(Class enumClass, String path); + /** * @param path * path expression @@ -882,6 +898,25 @@ public interface Config extends ConfigMergeable { */ List 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 + * 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} + */ + > List getEnumList(Class enumClass, String path); + /** * Gets a list value with object elements. Throws if the * path is unset or null or not a list or contains values not diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java index e794132e..01fadb4d 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java @@ -175,6 +175,10 @@ public class ConfigBeanImpl { return config.getValue(configPropName); } else if (parameterClass == ConfigList.class) { return config.getList(configPropName); + } else if (parameterClass.isEnum()) { + @SuppressWarnings("unchecked") + Enum enumValue = config.getEnum((Class) parameterClass, configPropName); + return enumValue; } else if (hasAtLeastOneBeanProperty(parameterClass)) { return createInternal(config.getConfig(configPropName), parameterClass); } else { @@ -207,6 +211,10 @@ public class ConfigBeanImpl { return config.getObjectList(configPropName); } else if (elementType == ConfigValue.class) { return config.getList(configPropName); + } else if (((Class) elementType).isEnum()) { + @SuppressWarnings("unchecked") + List enumValues = config.getEnumList((Class) elementType, configPropName); + return enumValues; } else if (hasAtLeastOneBeanProperty((Class) elementType)) { List beanList = new ArrayList(); List configList = config.getConfigList(configPropName); diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index a026b6f4..c3147124 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -247,6 +247,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return (String) v.unwrapped(); } + @Override + public > T getEnum(Class enumClass, String path) { + ConfigValue v = find(path, ConfigValueType.STRING); + return getEnumValue(path, enumClass, v); + } + @Override public ConfigList getList(String path) { AbstractConfigValue v = find(path, ConfigValueType.LIST); @@ -381,6 +387,35 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return getHomogeneousUnwrappedList(path, ConfigValueType.STRING); } + @Override + public > List getEnumList(Class enumClass, String path) { + List enumNames = getHomogeneousWrappedList(path, ConfigValueType.STRING); + List enumList = new ArrayList(); + for (ConfigString enumName : enumNames) { + enumList.add(getEnumValue(path, enumClass, enumName)); + } + return enumList; + } + + private > T getEnumValue(String path, Class enumClass, ConfigValue enumConfigValue) { + String enumName = (String) enumConfigValue.unwrapped(); + try { + return Enum.valueOf(enumClass, enumName); + } catch (IllegalArgumentException e) { + List enumNames = new ArrayList(); + 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") private List getHomogeneousWrappedList( String path, ConfigValueType expected) { diff --git a/config/src/test/java/beanconfig/EnumsConfig.java b/config/src/test/java/beanconfig/EnumsConfig.java new file mode 100644 index 00000000..a1d4609c --- /dev/null +++ b/config/src/test/java/beanconfig/EnumsConfig.java @@ -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 solutions; + + public Problem getProblem() { + return problem; + } + + public void setProblem(Problem problem) { + this.problem = problem; + } + + public List getSolutions() { + return solutions; + } + + public void setSolutions(List 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(); + } +} diff --git a/config/src/test/resources/beanconfig/beanconfig01.conf b/config/src/test/resources/beanconfig/beanconfig01.conf index fae411af..791553de 100644 --- a/config/src/test/resources/beanconfig/beanconfig01.conf +++ b/config/src/test/resources/beanconfig/beanconfig01.conf @@ -93,6 +93,10 @@ "list" : [1,2,3], "unwrappedMap" : ${validation} }, + "enums" : { + "problem" : "P1", + "solutions" : ["S1", "S3"] + }, "objects" : { "valueObject": { "mandatoryValue": "notNull" diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala index 755ab2ae..c6d62782 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala @@ -3,6 +3,7 @@ */ package com.typesafe.config.impl +import beanconfig.EnumsConfig.{ Solution, Problem } import com.typesafe.config._ import java.io.{ InputStream, InputStreamReader } @@ -13,6 +14,7 @@ import org.junit.Assert._ import org.junit._ import scala.collection.JavaConverters._ +import scala.collection.mutable.ArrayBuffer class ConfigBeanFactoryTest extends TestUtils { @@ -70,6 +72,14 @@ class ConfigBeanFactoryTest extends TestUtils { 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 def testCreateNumber() { 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")) } + @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 def testUnsupportedListElement() { val e = intercept[ConfigException.BadBean] {