diff --git a/src/main/java/com/typesafe/config/Config.java b/src/main/java/com/typesafe/config/Config.java index c7443d80..de1d48e3 100644 --- a/src/main/java/com/typesafe/config/Config.java +++ b/src/main/java/com/typesafe/config/Config.java @@ -52,6 +52,12 @@ public final class Config { return loadWithoutResolving(rootPath).resolve(); } + public static ConfigRoot load(String rootPath, + ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { + return loadWithoutResolving(rootPath, parseOptions).resolve( + resolveOptions); + } + /** * Like load() but does not resolve the object, so you can go ahead and add * more fallbacks and stuff and have them seen by substitutions when you do @@ -61,11 +67,15 @@ public final class Config { * @return */ public static ConfigRoot loadWithoutResolving(String rootPath) { + return loadWithoutResolving(rootPath, ConfigParseOptions.defaults()); + } + + public static ConfigRoot loadWithoutResolving(String rootPath, + ConfigParseOptions options) { ConfigRoot system = systemPropertiesRoot(rootPath); - ConfigValue mainFiles = parse(rootPath, ConfigParseOptions.defaults()); - ConfigValue referenceFiles = parse(rootPath + ".reference", - ConfigParseOptions.defaults()); + ConfigValue mainFiles = parse(rootPath, options); + ConfigValue referenceFiles = parse(rootPath + ".reference", options); return system.withFallbacks(mainFiles, referenceFiles); } diff --git a/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 389acb11..0db0fe7c 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -28,6 +28,7 @@ public class ConfigImpl { if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) { ConfigParseable p = source.nameToParseable(name); + if (p != null) { obj = p.parse(p.options().setAllowMissing( options.getAllowMissing())); @@ -47,17 +48,30 @@ public class ConfigImpl { "No config files {.conf,.json,.properties} found"); } + ConfigSyntax syntax = options.getSyntax(); + obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name)); - if (confHandle != null) + if (confHandle != null + && (syntax == null || syntax == ConfigSyntax.CONF)) { obj = confHandle.parse(confHandle.options() .setAllowMissing(true).setSyntax(ConfigSyntax.CONF)); - if (jsonHandle != null) - obj = obj.withFallback(jsonHandle.parse(jsonHandle.options() - .setAllowMissing(true).setSyntax(ConfigSyntax.JSON))); - if (propsHandle != null) - obj = obj.withFallback(propsHandle.parse(propsHandle.options() + } + + if (jsonHandle != null + && (syntax == null || syntax == ConfigSyntax.JSON)) { + ConfigObject parsed = jsonHandle.parse(jsonHandle + .options().setAllowMissing(true) + .setSyntax(ConfigSyntax.JSON)); + obj = obj.withFallback(parsed); + } + + if (propsHandle != null + && (syntax == null || syntax == ConfigSyntax.PROPERTIES)) { + ConfigObject parsed = propsHandle.parse(propsHandle.options() .setAllowMissing(true) - .setSyntax(ConfigSyntax.PROPERTIES))); + .setSyntax(ConfigSyntax.PROPERTIES)); + obj = obj.withFallback(parsed); + } } return obj; diff --git a/src/main/java/com/typesafe/config/impl/Parseable.java b/src/main/java/com/typesafe/config/impl/Parseable.java index 7bd92511..45a9aa69 100644 --- a/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/src/main/java/com/typesafe/config/impl/Parseable.java @@ -200,39 +200,28 @@ public abstract class Parseable implements ConfigParseable { }; } - private static URL urlParent(URL url) { - String path = url.getPath(); - if (path == null) - return null; - - File f = new File(path); - - String parent = f.getParent(); - - try { - return new URL(url.getProtocol(), url.getHost(), url.getPort(), - parent); - } catch (MalformedURLException e) { - return null; - } - } - static URL relativeTo(URL url, String filename) { // I'm guessing this completely fails on Windows, help wanted if (new File(filename).isAbsolute()) return null; - URL parentURL = urlParent(url); - if (parentURL == null) - return null; try { - URI parent = parentURL.toURI(); - URI relative = new URI(null, null, "/" + filename, null); - return parent.relativize(relative).toURL(); + URI siblingURI = url.toURI(); + URI relative = new URI(filename); + + // this seems wrong, but it's documented that the last + // element of the path in siblingURI gets stripped out, + // so to get something in the same directory as + // siblingURI we just call resolve(). + URL resolved = siblingURI.resolve(relative).toURL(); + + return resolved; } catch (MalformedURLException e) { return null; } catch (URISyntaxException e) { return null; + } catch (IllegalArgumentException e) { + return null; } } @@ -335,7 +324,10 @@ public abstract class Parseable implements ConfigParseable { @Override ConfigParseable relativeTo(String filename) { - return newURL(relativeTo(input, filename), options() + URL url = relativeTo(input, filename); + if (url == null) + return null; + return newURL(url, options() .setOriginDescription(null)); } @@ -382,8 +374,10 @@ public abstract class Parseable implements ConfigParseable { @Override ConfigParseable relativeTo(String filename) { try { - return newURL(relativeTo(input.toURI().toURL(), filename), - options().setOriginDescription(null)); + URL url = relativeTo(input.toURI().toURL(), filename); + if (url == null) + return null; + return newURL(url, options().setOriginDescription(null)); } catch (MalformedURLException e) { return null; } diff --git a/src/test/resources/equiv03/includes.conf b/src/test/resources/equiv03/includes.conf new file mode 100644 index 00000000..ce9404e3 --- /dev/null +++ b/src/test/resources/equiv03/includes.conf @@ -0,0 +1,5 @@ +letters { + include "letters/a.conf" + include "letters/b.json" + include "letters/c" +} diff --git a/src/test/resources/equiv03/letters/a.conf b/src/test/resources/equiv03/letters/a.conf new file mode 100644 index 00000000..98c4c258 --- /dev/null +++ b/src/test/resources/equiv03/letters/a.conf @@ -0,0 +1,6 @@ +numbers { + include "numbers/1.conf" + include "numbers/2" +} + +a=ok diff --git a/src/test/resources/equiv03/letters/b.json b/src/test/resources/equiv03/letters/b.json new file mode 100644 index 00000000..8fff1bba --- /dev/null +++ b/src/test/resources/equiv03/letters/b.json @@ -0,0 +1,3 @@ +{ + "b" : 507 +} \ No newline at end of file diff --git a/src/test/resources/equiv03/letters/c.conf b/src/test/resources/equiv03/letters/c.conf new file mode 100644 index 00000000..4fde2846 --- /dev/null +++ b/src/test/resources/equiv03/letters/c.conf @@ -0,0 +1,2 @@ +c.fromConf=89 + diff --git a/src/test/resources/equiv03/letters/c.properties b/src/test/resources/equiv03/letters/c.properties new file mode 100644 index 00000000..42590584 --- /dev/null +++ b/src/test/resources/equiv03/letters/c.properties @@ -0,0 +1 @@ +c.fromProp=true diff --git a/src/test/resources/equiv03/letters/numbers/1.conf b/src/test/resources/equiv03/letters/numbers/1.conf new file mode 100644 index 00000000..c96bc975 --- /dev/null +++ b/src/test/resources/equiv03/letters/numbers/1.conf @@ -0,0 +1 @@ +"1"=1 diff --git a/src/test/resources/equiv03/letters/numbers/2.properties b/src/test/resources/equiv03/letters/numbers/2.properties new file mode 100644 index 00000000..e6025928 --- /dev/null +++ b/src/test/resources/equiv03/letters/numbers/2.properties @@ -0,0 +1 @@ +2=abcd diff --git a/src/test/resources/equiv03/original.json b/src/test/resources/equiv03/original.json new file mode 100644 index 00000000..6757679d --- /dev/null +++ b/src/test/resources/equiv03/original.json @@ -0,0 +1,14 @@ +{ + "letters" : { + "numbers" : { + "2" : "abcd", + "1" : 1 + }, + "a" : "ok", + "b" : 507, + "c" : { + "fromConf" : 89, + "fromProp" : true + } + } +} diff --git a/src/test/resources/test03.conf b/src/test/resources/test03.conf index 54cafe30..c7e64f7d 100644 --- a/src/test/resources/test03.conf +++ b/src/test/resources/test03.conf @@ -13,5 +13,13 @@ "equiv01" : { include "equiv01/original.json" + }, + + # missing includes are supposed to be silently ignored + nonexistent { + include "nothere" + include "nothere.conf" + include "nothere.json" + include "nothere.properties" } } diff --git a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala index 5d881e8c..f29a7d39 100644 --- a/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala +++ b/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala @@ -87,8 +87,8 @@ class EquivalentsTest extends TestUtils { // This is a little "checksum" to be sure we really tested what we were expecting. // it breaks every time you add a file, so you have to update it. - assertEquals(2, dirCount) + assertEquals(3, dirCount) // this is the number of files not named original.* - assertEquals(12, fileCount) + assertEquals(13, fileCount) } } diff --git a/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala new file mode 100644 index 00000000..d14ec94a --- /dev/null +++ b/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -0,0 +1,74 @@ +package com.typesafe.config.impl + +import org.junit.Assert._ +import org.junit._ +import scala.collection.JavaConverters._ +import com.typesafe.config._ + +class PublicApiTest extends TestUtils { + @Test + def basicLoadAndGet() { + val conf = Config.load("test01") + + val a = conf.getInt("ints.fortyTwo") + val obj = conf.getObject("ints") + val c = obj.getInt("fortyTwo") + val ms = conf.getMilliseconds("durations.halfSecond") + + // should have used system variables + if (System.getenv("HOME") != null) + assertEquals(System.getenv("HOME"), conf.getString("system.home")) + + assertEquals(System.getProperty("java.version"), conf.getString("system.javaversion")) + } + + @Test + def noSystemVariables() { + // should not have used system variables + val conf = Config.load("test01", ConfigParseOptions.defaults(), + ConfigResolveOptions.noSystem()) + + intercept[ConfigException.Null] { + conf.getString("system.home") + } + intercept[ConfigException.Null] { + conf.getString("system.javaversion") + } + } + + @Test + def canLimitLoadToJson { + val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON); + val conf = Config.load("test01", options, ConfigResolveOptions.defaults()) + + assertEquals(1, conf.getInt("fromJson1")) + intercept[ConfigException.Missing] { + conf.getInt("ints.fortyTwo") + } + } + + @Test + def canLimitLoadToProperties { + val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.PROPERTIES); + val conf = Config.load("test01", options, ConfigResolveOptions.defaults()) + + assertEquals(1, conf.getInt("fromProps.one")) + intercept[ConfigException.Missing] { + conf.getInt("ints.fortyTwo") + } + } + + @Test + def canLimitLoadToConf { + val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF); + val conf = Config.load("test01", options, ConfigResolveOptions.defaults()) + + assertEquals(42, conf.getInt("ints.fortyTwo")) + intercept[ConfigException.Missing] { + conf.getInt("fromJson1") + } + intercept[ConfigException.Missing] { + conf.getInt("fromProps.one") + } + } +}