diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java index 1a561def..806e57b8 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java @@ -68,33 +68,26 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab @Override AbstractConfigValue resolveSubstitutions(ResolveContext context) throws NotPossibleToResolve { List<AbstractConfigValue> resolved = new ArrayList<AbstractConfigValue>(pieces.size()); - // if you have "foo = ${?foo}bar" then we will - // self-referentially look up foo and we need to - // get undefined, rather than "bar" - context.source().replace(this, ResolveReplacer.cycleResolveReplacer); - try { - for (AbstractConfigValue p : pieces) { - // to concat into a string we have to do a full resolve, - // so unrestrict the context - AbstractConfigValue r = context.unrestricted().resolve(p); - if (r == null) { - // it was optional... omit - } else { - switch (r.valueType()) { - case LIST: - case OBJECT: - // cannot substitute lists and objects into strings - // we know p was a ConfigReference since it wasn't - // a ConfigString - String pathString = ((ConfigReference) p).expression().toString(); - throw new ConfigException.WrongType(r.origin(), pathString, "not a list or object", r.valueType().name()); - default: - resolved.add(r); - } + for (AbstractConfigValue p : pieces) { + // to concat into a string we have to do a full resolve, + // so unrestrict the context + AbstractConfigValue r = context.unrestricted().resolve(p); + if (r == null) { + // it was optional... omit + } else { + switch (r.valueType()) { + case LIST: + case OBJECT: + // cannot substitute lists and objects into strings + // we know p was a ConfigReference since it wasn't + // a ConfigString + String pathString = ((ConfigReference) p).expression().toString(); + throw new ConfigException.WrongType(r.origin(), pathString, + "not a list or object", r.valueType().name()); + default: + resolved.add(r); } } - } finally { - context.source().unreplace(this); } // now need to concat everything 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 026c0338..7c88994c 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala @@ -1053,6 +1053,19 @@ class ConfigSubstitutionTest extends TestUtils { assertEquals(1, resolved.getInt("a.b.c")) } + @Test + def substSelfReferenceAlongPathMoreComplex() { + // this is an example from the spec + val obj = parseObject(""" + foo : { a : { c : 1 } } + foo : ${foo.a} + foo : { a : 2 } + """) + val resolved = resolve(obj) + assertEquals(1, resolved.getInt("foo.c")) + assertEquals(2, resolved.getInt("foo.a")) + } + @Test def substSelfReferenceIndirect() { val obj = parseObject("""a=1, b=${a}, a=${b}""") @@ -1131,6 +1144,27 @@ class ConfigSubstitutionTest extends TestUtils { assertEquals("foo", resolved.getString("a")) } + @Test + def substOptionalIndirectSelfReferenceInConcat() { + val obj = parseObject("""a=${?b}foo,b=${a}""") + val resolved = resolve(obj) + assertEquals("foo", resolved.getString("a")) + } + + @Test + def substTwoOptionalSelfReferencesInConcat() { + val obj = parseObject("""a=${?a}foo${?a}""") + val resolved = resolve(obj) + assertEquals("foo", resolved.getString("a")) + } + + @Test + def substTwoOptionalSelfReferencesInConcatWithPriorValue() { + val obj = parseObject("""a=1,a=${?a}foo${?a}""") + val resolved = resolve(obj) + assertEquals("1foo1", resolved.getString("a")) + } + @Test def substSelfReferenceMiddleOfStack() { val obj = parseObject("""a=1, a=${a}, a=2""")