From 63c3d44e14790f00feb381cc9adc8e638cb3c97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojtek=20Pitu=C5=82a?= Date: Mon, 19 Dec 2016 14:08:00 +0100 Subject: [PATCH 01/24] Added static-config as scala wrapper in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0f722c4d..e53713c6 100644 --- a/README.md +++ b/README.md @@ -842,6 +842,7 @@ format. * Simple Scala Config https://github.com/ElderResearch/ssc * konfig https://github.com/vpon/konfig * ScalaConfig https://github.com/andr83/scalaconfig + * static-config https://github.com/Krever/static-config #### Clojure wrappers for the Java library From d82c9736425716d16898058b74f0163dffffdf4c Mon Sep 17 00:00:00 2001 From: ronanM Date: Tue, 17 Jan 2017 10:55:08 +0100 Subject: [PATCH 02/24] Command line for alternative config `-Dconfig.file` Each time I search this information, I search `-D` in this README but can't find it **quickly**, so need to google it, then go to stackoverflow, etc. With this little example this information quickly reachable. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e53713c6..723d46b5 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,8 @@ library examples in `examples/` show how to accept a custom config while defaulting to `ConfigFactory.load()`. For applications using `application.{conf,json,properties}`, -system properties can be used to force a different config source: +system properties can be used to force a different config source +(e.g. from command line `-Dconfig.file=path/to/config-file`): - `config.resource` specifies a resource name - not a basename, i.e. `application.conf` not `application` From 61ccb169c3bdc136019f42efa4c11e53f73c1834 Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Sun, 12 Feb 2017 11:58:54 +0000 Subject: [PATCH 03/24] Add Validated Configuration Library to README validated-config allows Typesafe config files to be validated via a lightweight DSL. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 723d46b5..9efa6e49 100644 --- a/README.md +++ b/README.md @@ -844,6 +844,7 @@ format. * konfig https://github.com/vpon/konfig * ScalaConfig https://github.com/andr83/scalaconfig * static-config https://github.com/Krever/static-config + * validated-config https://github.com/carlpulley/validated-config #### Clojure wrappers for the Java library From 91f81b5ccc248ea8d87243699fb67810768de7bb Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Fri, 17 Feb 2017 11:00:29 -0500 Subject: [PATCH 04/24] Added Cedi Config to list of Scala Wrappers --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9efa6e49..06a5792a 100644 --- a/README.md +++ b/README.md @@ -845,6 +845,7 @@ format. * ScalaConfig https://github.com/andr83/scalaconfig * static-config https://github.com/Krever/static-config * validated-config https://github.com/carlpulley/validated-config + * Cedi Config https://github.com/ccadllc/cedi-config #### Clojure wrappers for the Java library From 4d98a0069e549321b0470b9f1abc79087272febc Mon Sep 17 00:00:00 2001 From: tzarouali Date: Mon, 20 Feb 2017 20:02:31 +0000 Subject: [PATCH 05/24] Fix for issue #447. Ignore bean properties without a proper setter/getter --- .../typesafe/config/impl/ConfigBeanImpl.java | 4 +++- ...DifferentFieldNameFromAccessorsConfig.java | 24 +++++++++++++++++++ .../config/impl/ConfigBeanFactoryTest.scala | 8 +++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 config/src/test/java/beanconfig/DifferentFieldNameFromAccessorsConfig.java 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 01fadb4d..15341afc 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java @@ -69,7 +69,9 @@ public class ConfigBeanImpl { try { List beanProps = new ArrayList(); for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { - if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { + if (beanProp.getReadMethod() == null + || beanProp.getWriteMethod() == null + || getField(clazz, beanProp.getName()) == null) { continue; } beanProps.add(beanProp); diff --git a/config/src/test/java/beanconfig/DifferentFieldNameFromAccessorsConfig.java b/config/src/test/java/beanconfig/DifferentFieldNameFromAccessorsConfig.java new file mode 100644 index 00000000..0eb3518f --- /dev/null +++ b/config/src/test/java/beanconfig/DifferentFieldNameFromAccessorsConfig.java @@ -0,0 +1,24 @@ +package beanconfig; + +public class DifferentFieldNameFromAccessorsConfig { + + private String customStringField; + private Long number; + + + public String getStringField() { + return customStringField; + } + + public void setStringField(String stringField) { + this.customStringField = stringField; + } + + public Long getNumber() { + return number; + } + + public void setNumber(Long number) { + this.number = number; + } +} 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 c6d62782..8c85e455 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala @@ -237,6 +237,14 @@ class ConfigBeanFactoryTest extends TestUtils { assertTrue("error about the right property", e.getMessage.contains("'map'")) } + @Test + def testDifferentFieldNameFromAccessors(): Unit = { + val e = intercept[ConfigException.ValidationFailed] { + ConfigBeanFactory.create(ConfigFactory.empty(), classOf[DifferentFieldNameFromAccessorsConfig]) + } + assertTrue("only one missing value error", e.getMessage.contains("No setting")) + } + private def loadConfig(): Config = { val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") try { From 870dd28cc7da1cfee35ff16b536aa2baeb598d72 Mon Sep 17 00:00:00 2001 From: tzarouali Date: Mon, 20 Feb 2017 21:29:54 +0000 Subject: [PATCH 06/24] Fix for issue #447. Instead of ignoring fields without a proper getter/setter, avoid the NPE checking first that there's a field to work with. --- .../main/java/com/typesafe/config/impl/ConfigBeanImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 15341afc..69da879b 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java @@ -69,9 +69,7 @@ public class ConfigBeanImpl { try { List beanProps = new ArrayList(); for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { - if (beanProp.getReadMethod() == null - || beanProp.getWriteMethod() == null - || getField(clazz, beanProp.getName()) == null) { + if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { continue; } beanProps.add(beanProp); @@ -279,7 +277,7 @@ public class ConfigBeanImpl { private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) { Field field = getField(beanClass, beanProp.getName()); - return (field.getAnnotationsByType(Optional.class).length > 0); + return field != null && (field.getAnnotationsByType(Optional.class).length > 0); } private static Field getField(Class beanClass, String fieldName) { From 0cfdc8f6eda07a6c080709117371eeb3b79904b3 Mon Sep 17 00:00:00 2001 From: morihiro Date: Sun, 26 Feb 2017 08:16:23 +0900 Subject: [PATCH 07/24] Add config4k as kotlin wrapper in README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06a5792a..9f72b367 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ to merge it in. - [Java (yep!) wrappers for the Java library](#java-yep-wrappers-for-the-java-library) - [Scala wrappers for the Java library](#scala-wrappers-for-the-java-library) - [Clojure wrappers for the Java library](#clojure-wrappers-for-the-java-library) + - [Kotlin wrappers for the Java library](#kotlin-wrappers-for-the-java-library) - [Scala port](#scala-port) - [Ruby port](#ruby-port) - [Puppet module](#puppet-module) @@ -850,7 +851,10 @@ format. #### Clojure wrappers for the Java library * beamly-core.config https://github.com/beamly/beamly-core.config - + +#### Kotlin wrappers for the Java library + * config4k https://github.com/config4k/config4k + #### Scala port * SHocon https://github.com/unicredit/shocon (work with both Scala and Scala.Js) From 74a5edfab5e82567eb038aa9884e314861947f0c Mon Sep 17 00:00:00 2001 From: matsu-chara Date: Thu, 2 Mar 2017 15:44:32 +0900 Subject: [PATCH 08/24] fix broken link in hocon.md --- HOCON.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HOCON.md b/HOCON.md index 43ea803f..168a0a33 100644 --- a/HOCON.md +++ b/HOCON.md @@ -30,7 +30,7 @@ - [Include syntax](#include-syntax) - [Include semantics: merging](#include-semantics-merging) - [Include semantics: substitution](#include-semantics-substitution) - - [Include semantics: missing files](#include-semantics-missing-files) + - [Include semantics: missing files and required files](#include-semantics-missing-files-and-required-files) - [Include semantics: file formats and extensions](#include-semantics-file-formats-and-extensions) - [Include semantics: locating resources](#include-semantics-locating-resources) - [Conversion of numerically-indexed objects to arrays](#conversion-of-numerically-indexed-objects-to-arrays) @@ -47,6 +47,7 @@ - [Substitution fallback to environment variables](#substitution-fallback-to-environment-variables) - [hyphen-separated vs. camelCase](#hyphen-separated-vs-camelcase) - [Note on Java properties similarity](#note-on-java-properties-similarity) + - [Note on Windows and case sensitivity of environment variables](#note-on-windows-and-case-sensitivity-of-environment-variables) From 38dce6253c5eb8004fc8ae3e678d6640de448791 Mon Sep 17 00:00:00 2001 From: "piotr.sliwa" Date: Sun, 5 Mar 2017 19:21:51 +0100 Subject: [PATCH 09/24] fork jvm in tests --- config/build.sbt | 1 + .../config/impl/ConfigDocumentTest.scala | 2 +- .../com/typesafe/config/impl/PublicApiTest.scala | 16 ++++++++++++---- .../com/typesafe/config/impl/TestUtils.scala | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/config/build.sbt b/config/build.sbt index 1b467428..7f067740 100644 --- a/config/build.sbt +++ b/config/build.sbt @@ -14,6 +14,7 @@ ScalariformKeys.preferences in Compile := formatPrefs ScalariformKeys.preferences in Test := formatPrefs fork in test := true +fork in Test := true fork in run := true fork in run in Test := true diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala index a9323cdb..1106f604 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala @@ -292,7 +292,7 @@ class ConfigDocumentTest extends TestUtils { @Test def configDocumentFileParse { val configDocument = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf")) - val fileReader = new BufferedReader(new FileReader("config/src/test/resources/test03.conf")) + val fileReader = new BufferedReader(new FileReader("src/test/resources/test03.conf")) var line = fileReader.readLine() val sb = new StringBuilder() while (line != null) { diff --git a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 70870c66..7d63026f 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -7,8 +7,7 @@ import org.junit.Assert._ import org.junit._ import scala.collection.JavaConverters._ import com.typesafe.config._ -import java.util.Collections -import java.util.TreeSet +import java.util.{ Collections, TimeZone, TreeSet } import java.io.File import scala.collection.mutable import equiv03.SomethingInEquiv03 @@ -17,6 +16,15 @@ import java.net.URL import java.time.Duration class PublicApiTest extends TestUtils { + + @Before + def before(): Unit = { + // TimeZone.getDefault internally invokes System.setProperty("user.timezone", ) and it may + // cause flaky tests depending on tests order and jvm options. This method is invoked + // eg. by URLConnection.getContentType (it reads headers and gets default time zone). + TimeZone.getDefault + } + @Test def basicLoadAndGet() { val conf = ConfigFactory.load("test01") @@ -1016,8 +1024,8 @@ class PublicApiTest extends TestUtils { assertTrue("invalidate caches works on changed system props sys", sys2 ne sys3) assertTrue("invalidate caches works on changed system props conf", conf2 ne conf3) - assertTrue("invalidate caches doesn't change value if no system prop changes sys", sys1 == sys2) - assertTrue("invalidate caches doesn't change value if no system prop changes conf", conf1 == conf2) + assertEquals("invalidate caches doesn't change value if no system prop changes sys", sys1, sys2) + assertEquals("invalidate caches doesn't change value if no system prop changes conf", conf1, conf2) assertTrue("test system property is set sys", sys3.hasPath("invalidateCachesTest")) assertTrue("test system property is set conf", conf3.hasPath("invalidateCachesTest")) diff --git a/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala index cae20535..1160de0d 100644 --- a/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala +++ b/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala @@ -721,7 +721,7 @@ abstract trait TestUtils { def path(elements: String*) = new Path(elements: _*) val resourceDir = { - val f = new File("config/src/test/resources") + val f = new File("src/test/resources") if (!f.exists()) { val here = new File(".").getAbsolutePath throw new Exception(s"Tests must be run from the root project directory containing ${f.getPath()}, however the current directory is $here") @@ -871,7 +871,7 @@ abstract trait TestUtils { } protected def withScratchDirectory[T](testcase: String)(body: File => T): Unit = { - val target = new File("config/target") + val target = new File("target") if (!target.isDirectory) throw new RuntimeException(s"Expecting $target to exist") val suffix = java.lang.Integer.toHexString(java.util.concurrent.ThreadLocalRandom.current.nextInt) From 7473d8ca1b0db34bb8fd7783e303212a9011ca36 Mon Sep 17 00:00:00 2001 From: "piotr.sliwa" Date: Mon, 6 Mar 2017 21:13:13 +0100 Subject: [PATCH 10/24] Allow environment vars to be resolved to list --- config/build.sbt | 3 ++ .../com/typesafe/config/impl/ConfigImpl.java | 11 +------ .../config/impl/PropertiesParser.java | 17 +++++++++-- .../config/impl/ConfigSubstitutionTest.scala | 30 +++++++++++++++++++ 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/config/build.sbt b/config/build.sbt index 7f067740..7d3fbe21 100644 --- a/config/build.sbt +++ b/config/build.sbt @@ -18,6 +18,9 @@ fork in Test := true fork in run := true fork in run in Test := true +//env vars for tests +envVars in Test ++= Map("testList.0" -> "0", "testList.1" -> "1") + autoScalaLibrary := false crossPaths := false diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index e441204a..cc6de357 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -335,16 +335,7 @@ public class ConfigImpl { } private static AbstractConfigObject loadEnvVariables() { - Map env = System.getenv(); - Map m = new HashMap(); - for (Map.Entry entry : env.entrySet()) { - String key = entry.getKey(); - m.put(key, - new ConfigString.Quoted(SimpleConfigOrigin.newSimple("env var " + key), entry - .getValue())); - } - return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"), - m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); + return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); } private static class EnvVariablesHolder { diff --git a/config/src/main/java/com/typesafe/config/impl/PropertiesParser.java b/config/src/main/java/com/typesafe/config/impl/PropertiesParser.java index 5bc630da..ee26a393 100644 --- a/config/src/main/java/com/typesafe/config/impl/PropertiesParser.java +++ b/config/src/main/java/com/typesafe/config/impl/PropertiesParser.java @@ -56,15 +56,28 @@ final class PropertiesParser { static AbstractConfigObject fromProperties(ConfigOrigin origin, Properties props) { + return fromEntrySet(origin, props.entrySet()); + } + + private static AbstractConfigObject fromEntrySet(ConfigOrigin origin, Set> entries) { + final Map pathMap = getPathMap(entries); + return fromPathMap(origin, pathMap, true /* from properties */); + } + + private static Map getPathMap(Set> entries) { Map pathMap = new HashMap(); - for (Map.Entry entry : props.entrySet()) { + for (Map.Entry entry : entries) { Object key = entry.getKey(); if (key instanceof String) { Path path = pathFromPropertyKey((String) key); pathMap.put(path, entry.getValue()); } } - return fromPathMap(origin, pathMap, true /* from properties */); + return pathMap; + } + + static AbstractConfigObject fromStringMap(ConfigOrigin origin, Map stringMap) { + return fromEntrySet(origin, stringMap.entrySet()); } static AbstractConfigObject fromPathMap(ConfigOrigin origin, diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala index 7cc79e59..712e1c43 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala @@ -10,6 +10,7 @@ import com.typesafe.config.ConfigException import com.typesafe.config.ConfigResolveOptions import com.typesafe.config.Config import com.typesafe.config.ConfigFactory +import scala.collection.JavaConverters._ class ConfigSubstitutionTest extends TestUtils { @@ -723,6 +724,35 @@ class ConfigSubstitutionTest extends TestUtils { checkNotSerializable(substComplexObject) } + @Test + def resolveListFromSystemProps() { + val props = parseObject( + """ + |"a": ${testList} + """.stripMargin) + + System.setProperty("testList.0", "0") + System.setProperty("testList.1", "1") + ConfigImpl.reloadSystemPropertiesConfig() + + val resolved = resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject]) + + assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala) + } + + @Test + def resolveListFromEnvVars() { + val props = parseObject( + """ + |"a": ${testList} + """.stripMargin) + + //"testList.0" and "testList.1" are defined as envVars in build.sbt + val resolved = resolve(props) + + assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala) + } + // this is a weird test, it used to test fallback to system props which made more sense. // Now it just tests that if you override with system props, you can use system props // in substitutions. From da914846e42f22bbccb1f0f66d0b9a377980ec4d Mon Sep 17 00:00:00 2001 From: Maor Date: Sat, 18 Mar 2017 11:00:36 +0200 Subject: [PATCH 11/24] Add JavaScript port --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f72b367..8ceacb83 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ to merge it in. - [Puppet module](#puppet-module) - [Python port](#python-port) - [C++ port](#c-port) + - [JavaScript port](#javascript-port) - [Linting tool](#linting-tool) @@ -875,7 +876,10 @@ format. * https://github.com/puppetlabs/cpp-hocon +#### JavaScript port + + * https://github.com/yellowblood/hocon-js (missing features, under development) + #### Linting tool * A web based linting tool http://www.hoconlint.com/ - From 0659ab5985d4e96701b44f7278ae036286ee9c86 Mon Sep 17 00:00:00 2001 From: Cameron Mullen Date: Wed, 22 Mar 2017 04:23:01 -0700 Subject: [PATCH 12/24] ConfigFactory.invalidateCaches() also resets environment variables config --- .../src/main/java/com/typesafe/config/ConfigFactory.java | 1 + .../main/java/com/typesafe/config/impl/ConfigImpl.java | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 7538817d..7938dc91 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -496,6 +496,7 @@ public final class ConfigFactory { // We rely on this having the side effect that it drops // all caches ConfigImpl.reloadSystemPropertiesConfig(); + ConfigImpl.reloadEnvVariablesConfig(); } /** diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index cc6de357..9cf49913 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -339,7 +339,7 @@ public class ConfigImpl { } private static class EnvVariablesHolder { - static final AbstractConfigObject envVariables = loadEnvVariables(); + static volatile AbstractConfigObject envVariables = loadEnvVariables(); } static AbstractConfigObject envVariablesAsConfigObject() { @@ -354,6 +354,12 @@ public class ConfigImpl { return envVariablesAsConfigObject().toConfig(); } + public static void reloadEnvVariablesConfig() { + // ConfigFactory.invalidateCaches() relies on this having the side + // effect that it drops all caches + EnvVariablesHolder.envVariables = loadEnvVariables(); + } + public static Config defaultReference(final ClassLoader loader) { return computeCachedConfig(loader, "defaultReference", new Callable() { @Override From 26b9a66404608cd2c15eff56974c314ee2c882e9 Mon Sep 17 00:00:00 2001 From: The Alchemist Date: Wed, 12 Apr 2017 15:41:33 -0400 Subject: [PATCH 13/24] Fix hyperlink to checkValid() --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ceacb83..8771ae6f 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ There isn't a schema language or anything like that. However, two suggested tools are: - use the - [checkValid() method](http://typesafehub.github.com/config/latest/api/com/typesafe/config/Config.html#checkValid%28com.typesafe.config.Config,%20java.lang.String...%29) + [checkValid() method](http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html#checkValid-com.typesafe.config.Config-java.lang.String...-) - access your config through a Settings class with a field for each setting, and instantiate it on startup (immediately throwing an exception if any settings are missing) From 751ae8b1d3f7a5554174f96d3f4dadaf817df6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Ol=C3=A1h?= Date: Thu, 27 Apr 2017 14:35:24 +0200 Subject: [PATCH 14/24] direct link to pureconfig instead of redirect link (#469) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8771ae6f..b953d2bb 100644 --- a/README.md +++ b/README.md @@ -841,7 +841,7 @@ format. * configz https://github.com/arosien/configz * configs https://github.com/kxbmap/configs * config-annotation https://github.com/wacai/config-annotation - * PureConfig https://github.com/melrief/pureconfig + * PureConfig https://github.com/pureconfig/pureconfig * Simple Scala Config https://github.com/ElderResearch/ssc * konfig https://github.com/vpon/konfig * ScalaConfig https://github.com/andr83/scalaconfig From 6889c6ae604b56249bb21119655ab949ccf95056 Mon Sep 17 00:00:00 2001 From: Andrey Kornev Date: Fri, 17 Mar 2017 20:52:10 -0700 Subject: [PATCH 15/24] SerializedConfigValue.writeExternal and readExternal are inconsistent --- .../config/impl/SerializedConfigValue.java | 22 +++++++++----- .../config/impl/ConfigValueTest.scala | 17 +++++++++++ .../com/typesafe/config/impl/TestUtils.scala | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/SerializedConfigValue.java b/config/src/main/java/com/typesafe/config/impl/SerializedConfigValue.java index 4dc1747a..a208e8a6 100644 --- a/config/src/main/java/com/typesafe/config/impl/SerializedConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/SerializedConfigValue.java @@ -3,8 +3,10 @@ */ package com.typesafe.config.impl; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInput; +import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.Externalizable; @@ -465,19 +467,23 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl SerializedField code = readCode(in); if (code == SerializedField.END_MARKER) { return; - } else if (code == SerializedField.ROOT_VALUE) { - in.readInt(); // discard length - this.value = readValue(in, null /* baseOrigin */); + } + + DataInput input = fieldIn(in); + if (code == SerializedField.ROOT_VALUE) { + this.value = readValue(input, null /* baseOrigin */); } else if (code == SerializedField.ROOT_WAS_CONFIG) { - in.readInt(); // discard length - this.wasConfig = in.readBoolean(); - } else { - // ignore unknown field - skipField(in); + this.wasConfig = input.readBoolean(); } } } + private DataInput fieldIn(ObjectInput in) throws IOException { + byte[] bytes = new byte[in.readInt()]; + in.readFully(bytes); + return new DataInputStream(new ByteArrayInputStream(bytes)); + } + private static ConfigException shouldNotBeUsed() { return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName() + " should not exist outside of serialization"); diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index 059c1cad..50add678 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -279,6 +279,23 @@ class ConfigValueTest extends TestUtils { assertTrue(b.root.toConfig eq b) } + /** + * Reproduces the issue #461. + *

+ * We use a custom de-/serializer that encodes String objects in a JDK-incompatible way. Encoding used here + * is rather simplistic: a long indicating the length in bytes (JDK uses a variable length integer) followed + * by the string's bytes. Running this test with the original SerializedConfigValue.readExternal() + * implementation results in an EOFException thrown during deserialization. + */ + @Test + def configConfigCustomSerializable() { + val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) + val expected = new SimpleConfigObject(fakeOrigin(), aMap).toConfig + val actual = checkSerializableWithCustomSerializer(expected) + + assertEquals(expected, actual) + } + @Test def configListEquality() { val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue } diff --git a/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala index 1160de0d..13ccd160 100644 --- a/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala +++ b/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala @@ -16,6 +16,8 @@ import java.io.ObjectOutputStream import java.io.ByteArrayInputStream import java.io.ObjectInputStream import java.io.NotSerializableException +import java.io.OutputStream +import java.io.InputStream import scala.annotation.tailrec import java.net.URL import java.util.Locale @@ -883,4 +885,32 @@ abstract trait TestUtils { deleteRecursive(scratch) } } + + protected def checkSerializableWithCustomSerializer[T: Manifest](o: T): T = { + val byteStream = new ByteArrayOutputStream() + val objectStream = new CustomObjectOutputStream(byteStream) + objectStream.writeObject(o) + objectStream.close() + val inStream = new ByteArrayInputStream(byteStream.toByteArray) + val inObjectStream = new CustomObjectInputStream(inStream) + val copy = inObjectStream.readObject() + inObjectStream.close() + copy.asInstanceOf[T] + } + + class CustomObjectOutputStream(out: OutputStream) extends ObjectOutputStream(out) { + override def writeUTF(str: String): Unit = { + val bytes = str.getBytes + writeLong(bytes.length) + write(bytes) + } + } + + class CustomObjectInputStream(in: InputStream) extends ObjectInputStream(in) { + override def readUTF(): String = { + val bytes = new Array[Byte](readLong().toByte) + read(bytes) + new String(bytes) + } + } } From 2952f07c97b9f0f092ae4187850c00c34db501c6 Mon Sep 17 00:00:00 2001 From: Lunfu Zhong Date: Mon, 19 Jun 2017 15:17:53 +0800 Subject: [PATCH 16/24] Update the link to config-annotation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b953d2bb..ad06ab29 100644 --- a/README.md +++ b/README.md @@ -840,7 +840,7 @@ format. * Ficus https://github.com/ceedubs/ficus * configz https://github.com/arosien/configz * configs https://github.com/kxbmap/configs - * config-annotation https://github.com/wacai/config-annotation +  * config-annotation https://github.com/zhongl/config-annotation * PureConfig https://github.com/pureconfig/pureconfig * Simple Scala Config https://github.com/ElderResearch/ssc * konfig https://github.com/vpon/konfig From ac20019c2984c66958cc84183d4b74c611bbb672 Mon Sep 17 00:00:00 2001 From: Nathan Fischer Date: Sun, 2 Jul 2017 13:04:02 -0700 Subject: [PATCH 17/24] Add support for getting value as Period from issue #473 --- .../main/java/com/typesafe/config/Config.java | 40 +++++++ .../typesafe/config/impl/SimpleConfig.java | 102 ++++++++++++++++++ config/src/test/resources/test01.conf | 8 ++ .../com/typesafe/config/impl/ConfigTest.scala | 12 ++- 4 files changed, 161 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 0097eed6..68a36e78 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -4,6 +4,8 @@ package com.typesafe.config; import java.time.Duration; +import java.time.Period; +import java.time.temporal.TemporalAmount; import java.util.List; import java.util.Map; import java.util.Set; @@ -793,6 +795,44 @@ public interface Config extends ConfigMergeable { */ Duration getDuration(String path); + /** + * Gets a value as a java.time.Period. If the value is + * already a number, then it's taken as days; if it's + * a string, it's parsed understanding units suffixes like + * "10d" or "5w" as documented in the the + * spec. This method never returns null. + * + * @since 1.3.0 + * + * @param path + * path expression + * @return the period value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to Long or String + * @throws ConfigException.BadValue + * if value cannot be parsed as a number of the given TimeUnit + */ + Period getPeriod(String path); + + /** + * Gets a value as a java.time.temporal.TemporalAmount. + * This method will first try get get the value as a java.time.Duration, and if unsuccessful, + * then as a java.time.Period. + * This means that values like "5m" will be parsed as 5 minutes rather than 5 months + * @param path path expression + * @return the temporal value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to Long or String + * @throws ConfigException.BadValue + * if value cannot be parsed as a TemporalAmount + */ + TemporalAmount getTemporal(String path); + /** * Gets a list value (with any element type) as a {@link ConfigList}, which * implements {@code java.util.List}. Throws if the path is 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 c3147124..44164953 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -7,7 +7,11 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.DateTimeException; import java.time.Duration; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; @@ -322,6 +326,20 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return Duration.ofNanos(nanos); } + @Override + public Period getPeriod(String path){ + ConfigValue v = find(path, ConfigValueType.STRING); + return parsePeriod((String) v.unwrapped(), v.origin(), path); + } + + public TemporalAmount getTemporal(String path){ + try{ + return getDuration(path); + } catch (ConfigException.BadValue e){ + return getPeriod(path); + } + } + @SuppressWarnings("unchecked") private List getHomogeneousUnwrappedList(String path, ConfigValueType expected) { @@ -583,6 +601,90 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return s.substring(i + 1); } + /** + * Parses a period string. If no units are specified in the string, it is + * assumed to be in days. The returned period is in days. + * The purpose of this function is to implement the period-related methods + * in the ConfigObject interface. + * + * @param input + * the string to parse + * @param originForException + * origin of the value being parsed + * @param pathForException + * path to include in exceptions + * @return duration in days + * @throws ConfigException + * if string is invalid + */ + public static Period parsePeriod(String input, + ConfigOrigin originForException, String pathForException) { + String s = ConfigImplUtil.unicodeTrim(input); + String originalUnitString = getUnits(s); + String unitString = originalUnitString; + String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length() + - unitString.length())); + ChronoUnit units; + + // this would be caught later anyway, but the error message + // is more helpful if we check it here. + if (numberString.length() == 0) + throw new ConfigException.BadValue(originForException, + pathForException, "No number in period value '" + input + + "'"); + + if (unitString.length() > 2 && !unitString.endsWith("s")) + unitString = unitString + "s"; + + // note that this is deliberately case-sensitive + if (unitString.equals("") || unitString.equals("d") || unitString.equals("days")) { + units = ChronoUnit.DAYS; + + } else if (unitString.equals("w") || unitString.equals("weeks")) { + units = ChronoUnit.WEEKS; + + } else if (unitString.equals("m") || unitString.equals("mo") || unitString.equals("months")) { + units = ChronoUnit.MONTHS; + + } else if (unitString.equals("y") || unitString.equals("years")) { + units = ChronoUnit.YEARS; + + } else { + throw new ConfigException.BadValue(originForException, + pathForException, "Could not parse time unit '" + + originalUnitString + + "' (try d, w, mo, y)"); + } + + try { + return periodOf(Integer.parseInt(numberString), units); + } catch (NumberFormatException e) { + throw new ConfigException.BadValue(originForException, + pathForException, "Could not parse duration number '" + + numberString + "'"); + } + } + + + private static Period periodOf(int n, ChronoUnit unit){ + if(unit.isTimeBased()){ + throw new DateTimeException(unit + " cannot be converted to a java.time.Period"); + } + + switch (unit){ + case DAYS: + return Period.ofDays(n); + case WEEKS: + return Period.ofWeeks(n); + case MONTHS: + return Period.ofMonths(n); + case YEARS: + return Period.ofYears(n); + default: + throw new DateTimeException(unit + " cannot be converted to a java.time.Period"); + } + } + /** * Parses a duration string. If no units are specified in the string, it is * assumed to be in milliseconds. The returned duration is in nanoseconds. diff --git a/config/src/test/resources/test01.conf b/config/src/test/resources/test01.conf index 1370bf85..e40eb7f5 100644 --- a/config/src/test/resources/test01.conf +++ b/config/src/test/resources/test01.conf @@ -65,6 +65,14 @@ "minusLargeNanos" : -4878955355435272204ns }, + "periods" : { + "day" : 1d, + "dayAsNumber": 2, + "week": 3 weeks, + "month": 5 mo, + "year": 8y + }, + "memsizes" : { "meg" : 1M, "megsList" : [1M, 1024K, 1048576], diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index d88b365c..104c9e7c 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -3,13 +3,16 @@ */ package com.typesafe.config.impl +import java.time.temporal.{ ChronoUnit, TemporalUnit } + import org.junit.Assert._ import org.junit._ import com.typesafe.config._ import java.util.concurrent.TimeUnit + import scala.collection.JavaConverters._ import com.typesafe.config.ConfigResolveOptions -import java.util.concurrent.TimeUnit.{ SECONDS, NANOSECONDS, MICROSECONDS, MILLISECONDS, MINUTES, DAYS, HOURS } +import java.util.concurrent.TimeUnit.{ DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS } class ConfigTest extends TestUtils { @@ -811,6 +814,13 @@ class ConfigTest extends TestUtils { assertDurationAsTimeUnit(HOURS) assertDurationAsTimeUnit(DAYS) + // periods + assertEquals(1, conf.getPeriod("periods.day").get(ChronoUnit.DAYS)) + assertEquals(2, conf.getPeriod("periods.dayAsNumber").getDays) + assertEquals(3 * 7, conf.getTemporal("periods.week").get(ChronoUnit.DAYS)) + assertEquals(5, conf.getTemporal("periods.month").get(ChronoUnit.MONTHS)) + assertEquals(8, conf.getTemporal("periods.year").get(ChronoUnit.YEARS)) + // should get size in bytes assertEquals(1024 * 1024L, conf.getBytes("memsizes.meg")) assertEquals(1024 * 1024L, conf.getBytes("memsizes.megAsNumber")) From c5e77d575e4c7d2c2493fc0c46b21e7ccaa8010d Mon Sep 17 00:00:00 2001 From: Nathan Fischer Date: Sat, 22 Jul 2017 13:40:06 -0700 Subject: [PATCH 18/24] update HOCON.md and add test to UnitParserTest --- HOCON.md | 20 +++++++++++++ .../typesafe/config/impl/UnitParserTest.scala | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/HOCON.md b/HOCON.md index 168a0a33..f85c4b05 100644 --- a/HOCON.md +++ b/HOCON.md @@ -1296,7 +1296,27 @@ must be lowercase. Exactly these strings are supported: - `m`, `minute`, `minutes` - `h`, `hour`, `hours` - `d`, `day`, `days` + +### Period Format + +Similar to the `getDuration()` method, there is a `getPeriod()` method +available for getting time units as a `java.time.Period`. +This can use the general "units format" described above; bare +numbers are taken to be in days, while strings are +parsed as a number plus an optional unit string. + +The supported unit strings for period are case sensitive and +must be lowercase. Exactly these strings are supported: + + - `d`, `day`, `days` + - `w`, `week`, `weeks` + - `m`, `mo`, `month`, `months` (note that if you are using `getTemporal()` + which may return either a `java.time.Duration` or a `java.time.Period` + you will want to use `mo` rather than `m` to prevent your unit being + parsed as minutes) + - `y`, `year`, `years` + ### Size in bytes format Implementations may wish to support a `getBytes()` returning a diff --git a/config/src/test/scala/com/typesafe/config/impl/UnitParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/UnitParserTest.scala index b4ee9a1b..f03593d1 100644 --- a/config/src/test/scala/com/typesafe/config/impl/UnitParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/UnitParserTest.scala @@ -3,6 +3,9 @@ */ package com.typesafe.config.impl +import java.time.{ LocalDate, Period } +import java.time.temporal.ChronoUnit + import org.junit.Assert._ import org.junit._ import com.typesafe.config._ @@ -40,6 +43,33 @@ class UnitParserTest extends TestUtils { assertTrue(e2.getMessage.contains("duration number")) } + @Test + def parsePeriod() = { + val oneYears = List( + "1y", "1 y", "1year", "1 years", " 1y ", " 1 y ", + "365", "365d", "365 d", "365 days", " 365 days ", "365day", + "12m", "12mo", "12 m", " 12 mo ", "12 months", "12month") + val epochDate = LocalDate.ofEpochDay(0) + val oneYear = ChronoUnit.DAYS.between(epochDate, epochDate.plus(Period.ofYears(1))) + for (y <- oneYears) { + val period = SimpleConfig.parsePeriod(y, fakeOrigin(), "test") + val dayCount = ChronoUnit.DAYS.between(epochDate, epochDate.plus(period)) + assertEquals(oneYear, dayCount) + } + + // bad units + val e = intercept[ConfigException.BadValue] { + SimpleConfig.parsePeriod("100 dollars", fakeOrigin(), "test") + } + assertTrue(s"${e.getMessage} was not the expected error message", e.getMessage.contains("time unit")) + + // bad number + val e2 = intercept[ConfigException.BadValue] { + SimpleConfig.parsePeriod("1 00 seconds", fakeOrigin(), "test") + } + assertTrue(s"${e2.getMessage} was not the expected error message", e2.getMessage.contains("time unit 'seconds'")) + } + // https://github.com/typesafehub/config/issues/117 // this broke because "1d" is a valid double for parseDouble @Test From 69423abbfc522e7a57d6c9a8d0f40f7de491d520 Mon Sep 17 00:00:00 2001 From: Nathan Fischer Date: Sat, 22 Jul 2017 16:10:19 -0700 Subject: [PATCH 19/24] override --- config/src/main/java/com/typesafe/config/impl/SimpleConfig.java | 1 + 1 file changed, 1 insertion(+) 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 44164953..ce1913ad 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -332,6 +332,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return parsePeriod((String) v.unwrapped(), v.origin(), path); } + @Override public TemporalAmount getTemporal(String path){ try{ return getDuration(path); From d774bc2afec6d4878353c3400cf93deb37ae3505 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 3 Aug 2017 10:43:54 +0100 Subject: [PATCH 20/24] Migrate to sbt 0.13.16 --- build.sbt | 65 +++++++++++++++++ config/build.sbt | 2 +- project/Build.scala | 119 -------------------------------- project/PublishToSonatype.scala | 48 +++++++++++++ project/build.properties | 2 +- 5 files changed, 115 insertions(+), 121 deletions(-) delete mode 100644 project/Build.scala create mode 100644 project/PublishToSonatype.scala diff --git a/build.sbt b/build.sbt index 65be9256..1eba6413 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,58 @@ scalacOptions in GlobalScope in Test := Seq("-unchecked", "-deprecation", "-feat scalaVersion in ThisBuild := "2.10.4" +val sonatype = new PublishToSonatype { + def projectUrl = "https://github.com/typesafehub/config" + def developerId = "havocp" + def developerName = "Havoc Pennington" + def developerUrl = "http://ometer.com/" + def scmUrl = "git://github.com/typesafehub/config.git" +} + +lazy val commonSettings: Seq[Setting[_]] = Def.settings( + unpublished, + javaVersionPrefix in javaVersionCheck := None +) + +lazy val root = (project in file(".")) + .settings( + commonSettings, + aggregate in doc := false, + doc := (doc in (configLib, Compile)).value, + aggregate in packageDoc := false, + packageDoc := (packageDoc in (configLib, Compile)).value, + aggregate in checkstyle := false, + checkstyle := (checkstyle in (configLib, Compile)).value + ) + .aggregate( + testLib, configLib, + simpleLibScala, simpleAppScala, complexAppScala, + simpleLibJava, simpleAppJava, complexAppJava + ) + +lazy val configLib = Project("config", file("config")) + .settings( + sonatype.settings, + osgiSettings, + OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl"), + publish := sys.error("use publishSigned instead of plain publish"), + publishLocal := sys.error("use publishLocalSigned instead of plain publishLocal") + ) + .enablePlugins(SbtOsgi) + .dependsOn(testLib % "test->test") + +def proj(id: String, base: File) = Project(id, base) settings commonSettings + +lazy val testLib = proj("config-test-lib", file("test-lib")) + +lazy val simpleLibScala = proj("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib +lazy val simpleAppScala = proj("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala +lazy val complexAppScala = proj("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala + +lazy val simpleLibJava = proj("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib +lazy val simpleAppJava = proj("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava +lazy val complexAppJava = proj("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava + useGpg := true aggregate in PgpKeys.publishSigned := false @@ -20,3 +72,16 @@ PgpKeys.publishSigned := (PgpKeys.publishSigned in configLib).value aggregate in PgpKeys.publishLocalSigned := false PgpKeys.publishLocalSigned := (PgpKeys.publishLocalSigned in configLib).value + +val unpublished = Seq( + // no artifacts in this project + publishArtifact := false, + // make-pom has a more specific publishArtifact setting already + // so needs specific override + publishArtifact in makePom := false, + // no docs to publish + publishArtifact in packageDoc := false, + // can't seem to get rid of ivy files except by no-op'ing the entire publish task + publish := {}, + publishLocal := {} +) diff --git a/config/build.sbt b/config/build.sbt index 7d3fbe21..5249ee59 100644 --- a/config/build.sbt +++ b/config/build.sbt @@ -62,7 +62,7 @@ checkstyle in Compile := { } // add checkstyle as a dependency of doc -doc in Compile <<= (doc in Compile).dependsOn(checkstyle in Compile) +doc in Compile := ((doc in Compile).dependsOn(checkstyle in Compile)).value findbugsSettings findbugsReportType := Some(ReportType.Html) diff --git a/project/Build.scala b/project/Build.scala deleted file mode 100644 index 00d33767..00000000 --- a/project/Build.scala +++ /dev/null @@ -1,119 +0,0 @@ -import sbt._ -import Keys._ -import com.etsy.sbt.checkstyle.CheckstylePlugin.autoImport._ -import com.typesafe.sbt.osgi.SbtOsgi -import com.typesafe.sbt.osgi.SbtOsgi.autoImport._ -import com.typesafe.sbt.JavaVersionCheckPlugin.autoImport._ - -object ConfigBuild extends Build { - val unpublished = Seq( - // no artifacts in this project - publishArtifact := false, - // make-pom has a more specific publishArtifact setting already - // so needs specific override - publishArtifact in makePom := false, - // no docs to publish - publishArtifact in packageDoc := false, - // can't seem to get rid of ivy files except by no-op'ing the entire publish task - publish := {}, - publishLocal := {} - ) - - object sonatype extends PublishToSonatype { - def projectUrl = "https://github.com/typesafehub/config" - def developerId = "havocp" - def developerName = "Havoc Pennington" - def developerUrl = "http://ometer.com/" - def scmUrl = "git://github.com/typesafehub/config.git" - } - - override val settings = super.settings ++ Seq(isSnapshot <<= isSnapshot or version(_ endsWith "-SNAPSHOT")) - - lazy val commonSettings: Seq[Setting[_]] = unpublished ++ Seq(javaVersionPrefix in javaVersionCheck := None) - - lazy val rootSettings: Seq[Setting[_]] = - commonSettings ++ - Seq(aggregate in doc := false, - doc := (doc in (configLib, Compile)).value, - aggregate in packageDoc := false, - packageDoc := (packageDoc in (configLib, Compile)).value, - aggregate in checkstyle := false, - checkstyle := (checkstyle in (configLib, Compile)).value) - - lazy val root = Project(id = "root", - base = file("."), - settings = rootSettings) aggregate(testLib, configLib, - simpleLibScala, simpleAppScala, complexAppScala, - simpleLibJava, simpleAppJava, complexAppJava) - - lazy val configLib = Project(id = "config", - base = file("config"), - settings = - sonatype.settings ++ - osgiSettings ++ - Seq( - OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl"), - publish := sys.error("use publishSigned instead of plain publish"), - publishLocal := sys.error("use publishLocalSigned instead of plain publishLocal") - )).enablePlugins(SbtOsgi) dependsOn testLib % "test->test" - - def project(id: String, base: File) = Project(id, base, settings = commonSettings) - - lazy val testLib = project("config-test-lib", file("test-lib")) - - lazy val simpleLibScala = project("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib - lazy val simpleAppScala = project("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala - lazy val complexAppScala = project("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala - - lazy val simpleLibJava = project("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib - lazy val simpleAppJava = project("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava - lazy val complexAppJava = project("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava -} - -// from https://raw.github.com/paulp/scala-improving/master/project/PublishToSonatype.scala - -abstract class PublishToSonatype { - val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" - val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - - def projectUrl: String - def developerId: String - def developerName: String - def developerUrl: String - - def licenseName = "Apache License, Version 2.0" - def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0" - def licenseDistribution = "repo" - def scmUrl: String - def scmConnection = "scm:git:" + scmUrl - - def generatePomExtra: xml.NodeSeq = { - { projectUrl } - - - { licenseName } - { licenseUrl } - { licenseDistribution } - - - - { scmUrl } - { scmConnection } - - - - { developerId } - { developerName } - { developerUrl } - - - } - - def settings: Seq[Setting[_]] = Seq( - publishMavenStyle := true, - publishTo <<= isSnapshot { (snapshot) => Some(if (snapshot) ossSnapshots else ossStaging) }, - publishArtifact in Test := false, - pomIncludeRepository := (_ => false), - pomExtra := generatePomExtra - ) -} diff --git a/project/PublishToSonatype.scala b/project/PublishToSonatype.scala new file mode 100644 index 00000000..b6b7ca54 --- /dev/null +++ b/project/PublishToSonatype.scala @@ -0,0 +1,48 @@ +import sbt._, Keys._ + +// from https://raw.github.com/paulp/scala-improving/master/project/PublishToSonatype.scala +abstract class PublishToSonatype { + val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" + val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + + def projectUrl: String + def developerId: String + def developerName: String + def developerUrl: String + + def licenseName = "Apache License, Version 2.0" + def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0" + def licenseDistribution = "repo" + def scmUrl: String + def scmConnection = "scm:git:" + scmUrl + + def generatePomExtra: xml.NodeSeq = { + { projectUrl } + + + { licenseName } + { licenseUrl } + { licenseDistribution } + + + + { scmUrl } + { scmConnection } + + + + { developerId } + { developerName } + { developerUrl } + + + } + + def settings: Seq[Setting[_]] = Seq( + publishMavenStyle := true, + publishTo := Some(if (isSnapshot.value) ossSnapshots else ossStaging), + publishArtifact in Test := false, + pomIncludeRepository := (_ => false), + pomExtra := generatePomExtra + ) +} diff --git a/project/build.properties b/project/build.properties index 43b8278c..c091b86c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.16 From fa44e9f04c2c3815bb0b24bad87d0b409ce095a8 Mon Sep 17 00:00:00 2001 From: Carlos Rueda Date: Mon, 7 Aug 2017 09:42:04 -0700 Subject: [PATCH 21/24] add carueda/cfg Scala wrapper --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ad06ab29..b291be54 100644 --- a/README.md +++ b/README.md @@ -848,6 +848,7 @@ format. * static-config https://github.com/Krever/static-config * validated-config https://github.com/carlpulley/validated-config * Cedi Config https://github.com/ccadllc/cedi-config + * Cfg https://github.com/carueda/cfg #### Clojure wrappers for the Java library From 002de454d08f93f770bf9a5c37ff89f70f983dbf Mon Sep 17 00:00:00 2001 From: Karthick Sankarachary Date: Fri, 15 Sep 2017 18:13:33 -0700 Subject: [PATCH 22/24] #495 Add Support For Set Types In Config Beans --- .../typesafe/config/impl/ConfigBeanImpl.java | 8 + .../src/test/java/beanconfig/SetsConfig.java | 139 ++++++++++++++++++ .../resources/beanconfig/beanconfig01.conf | 26 ++++ .../config/impl/ConfigBeanFactoryTest.scala | 33 +++++ 4 files changed, 206 insertions(+) create mode 100644 config/src/test/java/beanconfig/SetsConfig.java 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 69da879b..66d059ba 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java @@ -11,9 +11,11 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.time.Duration; +import java.util.Set; import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; @@ -160,6 +162,8 @@ public class ConfigBeanImpl { return config.getAnyRef(configPropName); } else if (parameterClass == List.class) { return getListValue(beanClass, parameterType, parameterClass, config, configPropName); + } else if (parameterClass == Set.class) { + return getSetValue(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(); @@ -186,6 +190,10 @@ public class ConfigBeanImpl { } } + private static Object getSetValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) { + return new HashSet((List) getListValue(beanClass, parameterType, parameterClass, config, configPropName)); + } + private static Object getListValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) { Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0]; diff --git a/config/src/test/java/beanconfig/SetsConfig.java b/config/src/test/java/beanconfig/SetsConfig.java new file mode 100644 index 00000000..b81540df --- /dev/null +++ b/config/src/test/java/beanconfig/SetsConfig.java @@ -0,0 +1,139 @@ +package beanconfig; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigMemorySize; +import com.typesafe.config.ConfigObject; +import com.typesafe.config.ConfigValue; + +import java.time.Duration; +import java.util.Set; + +public class SetsConfig { + + Set empty; + Set ofInt; + Set ofString; + Set ofDouble; + Set ofLong; + Set ofNull; + Set ofBoolean; + Set ofObject; + Set ofConfig; + Set ofConfigObject; + Set ofConfigValue; + Set ofDuration; + Set ofMemorySize; + Set ofStringBean; + + public Set getEmpty() { + return empty; + } + + public void setEmpty(Set empty) { + this.empty = empty; + } + + public Set getOfInt() { + return ofInt; + } + + public void setOfInt(Set ofInt) { + this.ofInt = ofInt; + } + + public Set getOfString() { + return ofString; + } + + public void setOfString(Set ofString) { + this.ofString = ofString; + } + + public Set getOfDouble() { + return ofDouble; + } + + public void setOfDouble(Set ofDouble) { + this.ofDouble = ofDouble; + } + + public Set getOfNull() { + return ofNull; + } + + public void setOfNull(Set ofNull) { + this.ofNull = ofNull; + } + + public Set getOfBoolean() { + return ofBoolean; + } + + public void setOfBoolean(Set ofBoolean) { + this.ofBoolean = ofBoolean; + } + + public Set getOfObject() { + return ofObject; + } + + public void setOfObject(Set ofObject) { + this.ofObject = ofObject; + } + + public Set getOfLong() { + return ofLong; + } + + public void setOfLong(Set ofLong) { + this.ofLong = ofLong; + } + + public Set getOfConfig() { + return ofConfig; + } + + public void setOfConfig(Set ofConfig) { + this.ofConfig = ofConfig; + } + + public Set getOfConfigObject() { + return ofConfigObject; + } + + public void setOfConfigObject(Set ofConfigObject) { + this.ofConfigObject = ofConfigObject; + } + + public Set getOfConfigValue() { + return ofConfigValue; + } + + public void setOfConfigValue(Set ofConfigValue) { + this.ofConfigValue = ofConfigValue; + } + + public Set getOfDuration() { + return ofDuration; + } + + public void setOfDuration(Set ofDuration) { + this.ofDuration = ofDuration; + } + + public Set getOfMemorySize() { + return ofMemorySize; + } + + public void setOfMemorySize(Set ofMemorySize) { + this.ofMemorySize = ofMemorySize; + } + + public Set getOfStringBean() { + return ofStringBean; + } + + public void setOfStringBean(Set ofStringBean) { + this.ofStringBean = ofStringBean; + } +} diff --git a/config/src/test/resources/beanconfig/beanconfig01.conf b/config/src/test/resources/beanconfig/beanconfig01.conf index 791553de..2a2ea2ed 100644 --- a/config/src/test/resources/beanconfig/beanconfig01.conf +++ b/config/src/test/resources/beanconfig/beanconfig01.conf @@ -101,5 +101,31 @@ "valueObject": { "mandatoryValue": "notNull" } + }, + "sets" : { + "empty" : [], + "ofInt" : [1, 2, 3, 2, 3], + "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ], + "of-double" : [3.14, 4.14, 4.14, 5.14], + "of-long" : { "1" : 32, "2" : 42, "3" : 52 }, // object-to-list conversion + "ofNull" : [null, null, null], + "ofBoolean" : [true, false, 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], + "ofStringBean" : [ + { + abcd : "testAbcdOne" + yes : "testYesOne" + }, + { + abcd : "testAbcdTwo" + yes : "testYesTwo" + } + ] } } 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 8c85e455..00e5a093 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala @@ -132,6 +132,39 @@ class ConfigBeanFactoryTest extends TestUtils { assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean) } + @Test + def testCreateSet() { + val beanConfig: SetsConfig = ConfigBeanFactory.create(loadConfig().getConfig("sets"), classOf[SetsConfig]) + assertNotNull(beanConfig) + assertEquals(Set().asJava, beanConfig.getEmpty) + assertEquals(Set(1, 2, 3).asJava, beanConfig.getOfInt) + assertEquals(Set(32L, 42L, 52L).asJava, beanConfig.getOfLong) + assertEquals(Set("a", "b", "c").asJava, beanConfig.getOfString) + assertEquals(3, beanConfig.getOfObject.size) + assertEquals(3, beanConfig.getOfDouble.size) + assertEquals(3, beanConfig.getOfConfig.size) + assertTrue(beanConfig.getOfConfig.iterator().next().isInstanceOf[Config]) + assertEquals(3, beanConfig.getOfConfigObject.size) + assertTrue(beanConfig.getOfConfigObject.iterator().next().isInstanceOf[ConfigObject]) + assertEquals(Set(intValue(1), intValue(2), stringValue("a")), + beanConfig.getOfConfigValue.asScala) + assertEquals(Set(Duration.ofMillis(1), Duration.ofHours(2), Duration.ofDays(3)), + beanConfig.getOfDuration.asScala) + assertEquals(Set(ConfigMemorySize.ofBytes(1024), + ConfigMemorySize.ofBytes(1048576), + ConfigMemorySize.ofBytes(1073741824)), + beanConfig.getOfMemorySize.asScala) + + val stringsConfigOne = new StringsConfig(); + stringsConfigOne.setAbcd("testAbcdOne") + stringsConfigOne.setYes("testYesOne") + val stringsConfigTwo = new StringsConfig(); + stringsConfigTwo.setAbcd("testAbcdTwo") + stringsConfigTwo.setYes("testYesTwo") + + assertEquals(Set(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean) + } + @Test def testCreateDuration() { val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig]) From e1b327f83717034b7890f62b95c022b25e06a5e9 Mon Sep 17 00:00:00 2001 From: Christian Plesner Hansen Date: Mon, 25 Sep 2017 20:09:58 +0200 Subject: [PATCH 23/24] Add fallback ConfigReferenceResolver --- .../typesafe/config/ConfigResolveOptions.java | 77 ++++++++++++++++++- .../com/typesafe/config/ConfigResolver.java | 38 +++++++++ .../typesafe/config/impl/ConfigReference.java | 5 +- .../com/typesafe/config/impl/ConfigTest.scala | 66 ++++++++++++++++ 4 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 config/src/main/java/com/typesafe/config/ConfigResolver.java diff --git a/config/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/config/src/main/java/com/typesafe/config/ConfigResolveOptions.java index 2b714041..96e0eca0 100644 --- a/config/src/main/java/com/typesafe/config/ConfigResolveOptions.java +++ b/config/src/main/java/com/typesafe/config/ConfigResolveOptions.java @@ -29,10 +29,13 @@ package com.typesafe.config; public final class ConfigResolveOptions { private final boolean useSystemEnvironment; private final boolean allowUnresolved; + private final ConfigResolver resolver; - private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) { + private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved, + ConfigResolver resolver) { this.useSystemEnvironment = useSystemEnvironment; this.allowUnresolved = allowUnresolved; + this.resolver = resolver; } /** @@ -42,7 +45,7 @@ public final class ConfigResolveOptions { * @return the default resolve options */ public static ConfigResolveOptions defaults() { - return new ConfigResolveOptions(true, false); + return new ConfigResolveOptions(true, false, NULL_RESOLVER); } /** @@ -64,7 +67,7 @@ public final class ConfigResolveOptions { * @return options with requested setting for use of environment variables */ public ConfigResolveOptions setUseSystemEnvironment(boolean value) { - return new ConfigResolveOptions(value, allowUnresolved); + return new ConfigResolveOptions(value, allowUnresolved, resolver); } /** @@ -91,7 +94,55 @@ public final class ConfigResolveOptions { * @since 1.2.0 */ public ConfigResolveOptions setAllowUnresolved(boolean value) { - return new ConfigResolveOptions(useSystemEnvironment, value); + return new ConfigResolveOptions(useSystemEnvironment, value, resolver); + } + + /** + * Returns options where the given resolver used as a fallback if a + * reference cannot be otherwise resolved. This resolver will only be called + * after resolution has failed to substitute with a value from within the + * config itself and with any other resolvers that have been appended before + * this one. Multiple resolvers can be added using, + * + *
+     *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
+     *         .appendResolver(primary)
+     *         .appendResolver(secondary)
+     *         .appendResolver(tertiary);
+     * 
+ * + * With this config unresolved references will first be resolved with the + * primary resolver, if that fails then the secondary, and finally if that + * also fails the tertiary. + * + * If all fallbacks fail to return a substitution "allow unresolved" + * determines whether resolution fails or continues. + *` + * @param value the resolver to fall back to + * @return options that use the given resolver as a fallback + * @since 1.3.2 + */ + public ConfigResolveOptions appendResolver(ConfigResolver value) { + if (value == null) { + throw new ConfigException.BugOrBroken("null resolver passed to appendResolver"); + } else if (value == this.resolver) { + return this; + } else { + return new ConfigResolveOptions(useSystemEnvironment, allowUnresolved, + this.resolver.withFallback(value)); + } + } + + /** + * Returns the resolver to use as a fallback if a substitution cannot be + * otherwise resolved. Never returns null. This method is mostly used by the + * config lib internally, not by applications. + * + * @return the non-null fallback resolver + * @since 1.3.2 + */ + public ConfigResolver getResolver() { + return this.resolver; } /** @@ -104,4 +155,22 @@ public final class ConfigResolveOptions { public boolean getAllowUnresolved() { return allowUnresolved; } + + /** + * Singleton resolver that never resolves paths. + */ + private static final ConfigResolver NULL_RESOLVER = new ConfigResolver() { + + @Override + public ConfigValue lookup(String path) { + return null; + } + + @Override + public ConfigResolver withFallback(ConfigResolver fallback) { + return fallback; + } + + }; + } diff --git a/config/src/main/java/com/typesafe/config/ConfigResolver.java b/config/src/main/java/com/typesafe/config/ConfigResolver.java new file mode 100644 index 00000000..a380a04f --- /dev/null +++ b/config/src/main/java/com/typesafe/config/ConfigResolver.java @@ -0,0 +1,38 @@ +package com.typesafe.config; + +/** + * Implement this interface and provide an instance to + * {@link ConfigResolveOptions#appendResolver ConfigResolveOptions.appendResolver()} + * to provide custom behavior when unresolved substitutions are encountered + * during resolution. + * @since 1.3.2 + */ +public interface ConfigResolver { + + /** + * Returns the value to substitute for the given unresolved path. To get the + * components of the path use {@link ConfigUtil#splitPath(String)}. If a + * non-null value is returned that value will be substituted, otherwise + * resolution will continue to consider the substitution as still + * unresolved. + * + * @param path the unresolved path + * @return the value to use as a substitution or null + */ + public ConfigValue lookup(String path); + + /** + * Returns a new resolver that falls back to the given resolver if this + * one doesn't provide a substitution itself. + * + * It's important to handle the case where you already have the fallback + * with a "return this", i.e. this method should not create a new object if + * the fallback is the same one you already have. The same fallback may be + * added repeatedly. + * + * @param fallback the previous includer for chaining + * @return a new resolver + */ + public ConfigResolver withFallback(ConfigResolver fallback); + +} diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java index 8d3c7c0c..077503d4 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java @@ -6,6 +6,8 @@ import java.util.Collections; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; +import com.typesafe.config.ConfigResolveOptions; +import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; /** @@ -88,7 +90,8 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable { v = result.value; newContext = result.context; } else { - v = null; + ConfigValue fallback = context.options().getResolver().lookup(expr.path().render()); + v = (AbstractConfigValue) fallback; } } catch (NotPossibleToResolve e) { if (ConfigImpl.traceSubstitutionsEnabled()) diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index d88b365c..96b55835 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -1233,4 +1233,70 @@ class ConfigTest extends TestUtils { val resolved = unresolved.resolveWith(source) assertEquals(43, resolved.getInt("foo")) } + + /** + * A resolver that replaces paths that start with a particular prefix with + * strings where that prefix has been replaced with another prefix. + */ + class DummyResolver(prefix: String, newPrefix: String, fallback: ConfigResolver) extends ConfigResolver { + + override def lookup(path: String): ConfigValue = { + if (path.startsWith(prefix)) + ConfigValueFactory.fromAnyRef(newPrefix + path.substring(prefix.length)) + else if (fallback != null) + fallback.lookup(path) + else + null + } + + override def withFallback(f: ConfigResolver): ConfigResolver = { + if (fallback == null) + new DummyResolver(prefix, newPrefix, f) + else + new DummyResolver(prefix, newPrefix, fallback.withFallback(f)) + } + + } + + private def runFallbackTest(expected: String, source: String, + allowUnresolved: Boolean, resolvers: ConfigResolver*) = { + val unresolved = ConfigFactory.parseString(source) + var options = ConfigResolveOptions.defaults().setAllowUnresolved(allowUnresolved) + for (resolver <- resolvers) + options = options.appendResolver(resolver) + val obj = unresolved.resolve(options).root() + assertEquals(expected, obj.render(ConfigRenderOptions.concise().setJson(false))) + } + + @Test + def resolveFallback(): Unit = { + runFallbackTest( + "x=a,y=b", + "x=${a},y=${b}", false, + new DummyResolver("", "", null)) + runFallbackTest( + "x=\"a.b.c\",y=\"a.b.d\"", + "x=${a.b.c},y=${a.b.d}", false, + new DummyResolver("", "", null)) + runFallbackTest( + "x=${a.b.c},y=${a.b.d}", + "x=${a.b.c},y=${a.b.d}", true, + new DummyResolver("x.", "", null)) + runFallbackTest( + "x=${a.b.c},y=\"e.f\"", + "x=${a.b.c},y=${d.e.f}", true, + new DummyResolver("d.", "", null)) + runFallbackTest( + "w=\"Y.c.d\",x=${a},y=\"X.b\",z=\"Y.c\"", + "x=${a},y=${a.b},z=${a.b.c},w=${a.b.c.d}", true, + new DummyResolver("a.b.", "Y.", null), + new DummyResolver("a.", "X.", null)) + + runFallbackTest("x=${a.b.c}", "x=${a.b.c}", true, new DummyResolver("x.", "", null)) + val e = intercept[ConfigException.UnresolvedSubstitution] { + runFallbackTest("x=${a.b.c}", "x=${a.b.c}", false, new DummyResolver("x.", "", null)) + } + assertTrue(e.getMessage.contains("${a.b.c}")) + } + } From 5c3e6f83204a9fd78ce44db33b43822c7506df84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Fri, 6 Oct 2017 16:10:14 +0300 Subject: [PATCH 24/24] Release notes for 1.3.2 --- NEWS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS.md b/NEWS.md index 22d4cfb6..69790320 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,17 @@ +# 1.3.2: October 6, 2017 + +- environment variables are now able to be resolved to lists in + the same fashion as system properties. +- added `getPeriod()` which returns time units as + `java.time.Period`. Currently supported periods are days, weeks, + months and years. [More information here](HOCON.md#period-format). +- `ConfigResolveOptions` now has `appendResolver(...)` which allows + having custom behavior when unresolved substitutions are encountered + during resolution. +- Config Beans now support `Set` collection. +- a few other small bugfixes. All of the fixed issues can be found + in the [milestone page](https://github.com/typesafehub/config/milestone/1?closed=1). + # 1.3.1: September 24, 2016 - added `include required("foo")` syntax to specify includes that