From 4ac4c7addf48d0f7a2d59dd457950a8193a63585 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sat, 3 Dec 2022 19:02:12 +0100 Subject: [PATCH 01/11] add support for nested keys --- owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index 78d6e885..a25d5ff5 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -48,7 +48,7 @@ class StrSubstitutor implements Serializable { private final Properties values; - private static final Pattern PATTERN = compile("\\$\\{(.+?)\\}"); + private static final Pattern PATTERN = compile("\\$\\{((\\$\\{.+}|.)+?)}"); /** * Creates a new instance and initializes it. Uses defaults for variable prefix and suffix and the escaping @@ -75,7 +75,7 @@ String replace(String source) { while (m.find()) { String var = m.group(1); String value = values.getProperty(var); - String replacement = (value != null) ? replace(value) : ""; + String replacement = (value != null) ? replace(value) : (var.matches(PATTERN.pattern())? replace(var): ""); m.appendReplacement(sb, Matcher.quoteReplacement(replacement)); } m.appendTail(sb); From 2d7706fdf896ac6b97a58484bd57c1772ac68c90 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sat, 3 Dec 2022 19:02:21 +0100 Subject: [PATCH 02/11] add tests for nested keys --- .../aeonbits/owner/StrSubstitutorTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java index 49a3c0f9..d4f2a8d9 100644 --- a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java +++ b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java @@ -81,6 +81,40 @@ public void testRecoursiveResolution() { assertEquals("The quick brown fox jumped over the lazy dog.", resolvedString); } + @Test + public void testNestedRecursiveResolution() { + Properties values = new Properties(); + values.setProperty("environment", "dev"); + values.setProperty("environments.${environment}.browser", "chrome"); + values.setProperty("template", "environments.${environment}.webdriver.${environments.${environment}.browser}.switches"); + String templateString = "${template}"; + StrSubstitutor sub = new StrSubstitutor(values); + String resolvedString = sub.replace(templateString); + assertEquals("environments.dev.webdriver.chrome.switches", resolvedString); + } + @Test + public void testNestedRecursiveResolutionDeepLevel() { + Properties values = new Properties(); + values.setProperty("first", "1"); + values.setProperty("${first}.foo", "2"); + values.setProperty("${${${first}.foo}}.baz", "3"); + values.setProperty("${${${${first}.foo}}.baz}.bar", "4"); + String templateString = "${${${${${first}.foo}}.baz}.bar}"; + StrSubstitutor sub = new StrSubstitutor(values); + String resolvedString = sub.replace(templateString); + assertEquals("4", resolvedString); + } + + @Test + public void testMultipleNestedResolution() { + Properties values = new Properties(); + values.setProperty("foo", "1"); + String templateString = "${${${${${${${${foo}}}}}}}}"; + StrSubstitutor sub = new StrSubstitutor(values); + String resolvedString = sub.replace(templateString); + assertEquals("1", resolvedString); + } + @Test public void testMissingPropertyIsReplacedWithEmptyString() { Properties values = new Properties() {{ From 7d87607740b8ff994437fabdb104a58d2859dc67 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 20:26:44 +0100 Subject: [PATCH 03/11] modify 'replace' method in StrSubstitutor.java to allow nested expresions --- .../org/aeonbits/owner/StrSubstitutor.java | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index a25d5ff5..4a34185e 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -9,6 +9,8 @@ package org.aeonbits.owner; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,7 +50,7 @@ class StrSubstitutor implements Serializable { private final Properties values; - private static final Pattern PATTERN = compile("\\$\\{((\\$\\{.+}|.)+?)}"); + private static final Pattern PATTERN = compile("\\$\\{(.+?)}"); /** * Creates a new instance and initializes it. Uses defaults for variable prefix and suffix and the escaping @@ -70,18 +72,52 @@ class StrSubstitutor implements Serializable { String replace(String source) { if (source == null) return null; - Matcher m = PATTERN.matcher(source); - StringBuffer sb = new StringBuffer(); - while (m.find()) { - String var = m.group(1); - String value = values.getProperty(var); - String replacement = (value != null) ? replace(value) : (var.matches(PATTERN.pattern())? replace(var): ""); - m.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + StringBuilder sb = new StringBuilder(); + List groups = getVariableExpansions(source); + System.out.println(groups); + if (groups.isEmpty()) return source; + for (String group : groups) { + String value = values.getProperty(group); + String replacement = (value != null) ? replace(value) : (group.matches(PATTERN.pattern()) ? replace(group) : ""); + source = source.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacement)); } - m.appendTail(sb); + sb.append(source); return sb.toString(); } + /** + * Finds all top level variable expansion expressions and returns it as a list. + * E.g.: foo.${bar.${baz}}.${biz} -> [bar.${baz}, biz] + * + * @param expression the string for which variable expansion expressions are queried, null returns null + * @return list of top level variable expansion expressions + */ + private List getVariableExpansions(String expression) { + if (expression == null) return null; + + List variables = new ArrayList(); + int indexOfFirstVariableExpansion = expression.indexOf("${"); + if (indexOfFirstVariableExpansion == -1) return variables; + + int expressionLength = expression.length(); + indexOfFirstVariableExpansion += 2; + int bracketCounter = 1; + int variableStartIndex = indexOfFirstVariableExpansion; + + for (int index = indexOfFirstVariableExpansion; index < expressionLength; index++) { + if (expression.charAt(index) == '{') { + bracketCounter += 1; + } + if (expression.charAt(index) == '}') bracketCounter -= 1; + if (bracketCounter == 0) { + variables.add(expression.substring(variableStartIndex, index)); + variables.addAll(getVariableExpansions(expression.substring(index + 1, expressionLength))); + break; + } + } + return variables; + } + /** * Returns a string modified in according to supplied source and arguments.
* If the source string has pattern-replacement content like {@code "a.${var}.b"}, @@ -89,7 +125,7 @@ String replace(String source) { * Otherwise the return string is formatted by source and arguments as with {@link String#format(String, Object...)} * * @param source A source formatting format string. {@code null} returns {@code null} - * @param args Arguments referenced by the format specifiers in the source string. + * @param args Arguments referenced by the format specifiers in the source string. * @return formatted string */ String replace(String source, Object... args) { @@ -98,4 +134,4 @@ String replace(String source, Object... args) { Matcher m = PATTERN.matcher(source); return m.find() ? replace(source) : String.format(source, args); } -} +} \ No newline at end of file From 51acf79efbd1ccdebf3706e931edcdc2890b5149 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 20:28:24 +0100 Subject: [PATCH 04/11] add test to verify nested expresions add test to verify property with curly brackets in name --- .../org/aeonbits/owner/StrSubstitutorTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java index d4f2a8d9..05945214 100644 --- a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java +++ b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java @@ -86,12 +86,13 @@ public void testNestedRecursiveResolution() { Properties values = new Properties(); values.setProperty("environment", "dev"); values.setProperty("environments.${environment}.browser", "chrome"); - values.setProperty("template", "environments.${environment}.webdriver.${environments.${environment}.browser}.switches"); + values.setProperty("template", "environments.${environment}.webdriver.${environments.${environment}.browser}.switches.${environment}"); String templateString = "${template}"; StrSubstitutor sub = new StrSubstitutor(values); String resolvedString = sub.replace(templateString); - assertEquals("environments.dev.webdriver.chrome.switches", resolvedString); + assertEquals("environments.dev.webdriver.chrome.switches.dev", resolvedString); } + @Test public void testNestedRecursiveResolutionDeepLevel() { Properties values = new Properties(); @@ -105,6 +106,17 @@ public void testNestedRecursiveResolutionDeepLevel() { assertEquals("4", resolvedString); } + @Test + public void testPropertyWithCurlyBracketsInName() { + Properties values = new Properties(); + values.setProperty("{foo}", "bar"); + values.setProperty("template", "foo.${{foo}}"); + String templateString = "${template}"; + StrSubstitutor sub = new StrSubstitutor(values); + String resolvedString = sub.replace(templateString); + assertEquals("foo.bar", resolvedString); + } + @Test public void testMultipleNestedResolution() { Properties values = new Properties(); From c36f5bdbca67f4afbd3930fc56651f0918e480c0 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 20:30:10 +0100 Subject: [PATCH 05/11] remove debug println --- owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index 4a34185e..99bf7a1b 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -74,7 +74,6 @@ String replace(String source) { return null; StringBuilder sb = new StringBuilder(); List groups = getVariableExpansions(source); - System.out.println(groups); if (groups.isEmpty()) return source; for (String group : groups) { String value = values.getProperty(group); From 591a62f07d1afa2d22431455d2e9520f0851a211 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 20:36:30 +0100 Subject: [PATCH 06/11] refactor getVariableExpansions(String) method to increase readability --- .../main/java/org/aeonbits/owner/StrSubstitutor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index 99bf7a1b..4c511a40 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -94,14 +94,15 @@ String replace(String source) { private List getVariableExpansions(String expression) { if (expression == null) return null; - List variables = new ArrayList(); - int indexOfFirstVariableExpansion = expression.indexOf("${"); + final List variables = new ArrayList(); + final String variableExpressionBeginning = "${"; + int indexOfFirstVariableExpansion = expression.indexOf(variableExpressionBeginning); if (indexOfFirstVariableExpansion == -1) return variables; - int expressionLength = expression.length(); - indexOfFirstVariableExpansion += 2; + final int expressionLength = expression.length(); + indexOfFirstVariableExpansion += variableExpressionBeginning.length(); + final int variableStartIndex = indexOfFirstVariableExpansion; int bracketCounter = 1; - int variableStartIndex = indexOfFirstVariableExpansion; for (int index = indexOfFirstVariableExpansion; index < expressionLength; index++) { if (expression.charAt(index) == '{') { From 52b89ec61225d975133ed715a2b13a4d58b2dc91 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 20:58:01 +0100 Subject: [PATCH 07/11] change order of methods to match codefactor rules --- .../org/aeonbits/owner/StrSubstitutor.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index 4c511a40..df49ee59 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -84,6 +84,23 @@ String replace(String source) { return sb.toString(); } + /** + * Returns a string modified in according to supplied source and arguments.
+ * If the source string has pattern-replacement content like {@code "a.${var}.b"}, + * the pattern is replaced property value of "var".
+ * Otherwise the return string is formatted by source and arguments as with {@link String#format(String, Object...)} + * + * @param source A source formatting format string. {@code null} returns {@code null} + * @param args Arguments referenced by the format specifiers in the source string. + * @return formatted string + */ + String replace(String source, Object... args) { + if (source == null) + return null; + Matcher m = PATTERN.matcher(source); + return m.find() ? replace(source) : String.format(source, args); + } + /** * Finds all top level variable expansion expressions and returns it as a list. * E.g.: foo.${bar.${baz}}.${biz} -> [bar.${baz}, biz] @@ -117,21 +134,4 @@ private List getVariableExpansions(String expression) { } return variables; } - - /** - * Returns a string modified in according to supplied source and arguments.
- * If the source string has pattern-replacement content like {@code "a.${var}.b"}, - * the pattern is replaced property value of "var".
- * Otherwise the return string is formatted by source and arguments as with {@link String#format(String, Object...)} - * - * @param source A source formatting format string. {@code null} returns {@code null} - * @param args Arguments referenced by the format specifiers in the source string. - * @return formatted string - */ - String replace(String source, Object... args) { - if (source == null) - return null; - Matcher m = PATTERN.matcher(source); - return m.find() ? replace(source) : String.format(source, args); - } } \ No newline at end of file From e9ead028d7a66a4437f2d37ee28da1338fc915d6 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 4 Dec 2022 21:11:05 +0100 Subject: [PATCH 08/11] refactor replace(String) method to follow Codacy Static Code Analysis rules --- owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index df49ee59..e5761574 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -74,13 +74,13 @@ String replace(String source) { return null; StringBuilder sb = new StringBuilder(); List groups = getVariableExpansions(source); - if (groups.isEmpty()) return source; + String replacedSource = source; for (String group : groups) { String value = values.getProperty(group); String replacement = (value != null) ? replace(value) : (group.matches(PATTERN.pattern()) ? replace(group) : ""); - source = source.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacement)); + replacedSource = replacedSource.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacement)); } - sb.append(source); + sb.append(replacedSource); return sb.toString(); } From 515aee7958b1b35e1266ffbf2b1e9ab00e421609 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Tue, 6 Dec 2022 14:08:55 +0100 Subject: [PATCH 09/11] fix nesting issue for Config interfaces --- .../org/aeonbits/owner/StrSubstitutor.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index e5761574..573d2553 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -77,8 +77,16 @@ String replace(String source) { String replacedSource = source; for (String group : groups) { String value = values.getProperty(group); - String replacement = (value != null) ? replace(value) : (group.matches(PATTERN.pattern()) ? replace(group) : ""); - replacedSource = replacedSource.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacement)); + String replacement = isKeyExpansionExpression(group) ? replace(group) : (value != null) ? replace(value) : ""; + String replacementValue = values.getProperty(replacement); + if (replacementValue == null) { + if (isKeyExpansionExpression(group)) { + replacementValue = value != null ? value : ""; + } else { + replacementValue = replacement; + } + } + replacedSource = replacedSource.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacementValue)); } sb.append(replacedSource); return sb.toString(); @@ -97,21 +105,20 @@ String replace(String source) { String replace(String source, Object... args) { if (source == null) return null; - Matcher m = PATTERN.matcher(source); - return m.find() ? replace(source) : String.format(source, args); + return isKeyExpansionExpression(source) ? replace(source) : String.format(source, args); } /** * Finds all top level variable expansion expressions and returns it as a list. * E.g.: foo.${bar.${baz}}.${biz} -> [bar.${baz}, biz] * - * @param expression the string for which variable expansion expressions are queried, null returns null + * @param expression the string for which variable expansion expressions are queried, null returns empty list * @return list of top level variable expansion expressions */ private List getVariableExpansions(String expression) { - if (expression == null) return null; - final List variables = new ArrayList(); + if (expression == null) return variables; + final String variableExpressionBeginning = "${"; int indexOfFirstVariableExpansion = expression.indexOf(variableExpressionBeginning); if (indexOfFirstVariableExpansion == -1) return variables; @@ -134,4 +141,14 @@ private List getVariableExpansions(String expression) { } return variables; } + + /** + * Checks if given expression matches PATTERN expression - regex for key expansion expression + * @param expression expression to be checked, null returns false + * @return true if expression matches PATTERN, false otherwise + */ + private boolean isKeyExpansionExpression(String expression) { + if(expression == null) return false; + return PATTERN.matcher(expression).find(); + } } \ No newline at end of file From cbc1901f69bae36163d22d2e1c7d86d542617324 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Tue, 6 Dec 2022 14:09:33 +0100 Subject: [PATCH 10/11] add tests for Config interface amend tests for StrSubstitutor class --- .../java/org/aeonbits/owner/ConfigTest.java | 22 ++++++++++++++++ .../aeonbits/owner/StrSubstitutorTest.java | 25 +------------------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/owner/src/test/java/org/aeonbits/owner/ConfigTest.java b/owner/src/test/java/org/aeonbits/owner/ConfigTest.java index e5873cc1..721e311b 100644 --- a/owner/src/test/java/org/aeonbits/owner/ConfigTest.java +++ b/owner/src/test/java/org/aeonbits/owner/ConfigTest.java @@ -52,6 +52,16 @@ public interface SampleConfig extends Config { void voidMethodWithValue(); void voidMethodWithoutValue(); + + @Key("environment") + String env(); + + @Key("environments.${environment}.browser") + @DefaultValue("chrome") + String usedBrowser(); + + @Key("environments.${environment}.webdriver.${environments.${environment}.browser}.switches") + String webDriverOptions(); } @Test @@ -141,4 +151,16 @@ public void whenPropertyValueIsNotValidFormatString_thenPropertyValueShouldRemai assertEquals ("@#$%^&*()", config.password()); } + @Test + public void nestedKeyExpansion() { + Properties values = new Properties() {{ + setProperty("environment", "dev"); + setProperty("environments.dev.browser", "opera"); + setProperty("environments.dev.webdriver.opera.switches", "foo bar"); + setProperty("environments.dev.webdriver.chrome.switches", "foo baz"); + }}; + + SampleConfig config = ConfigFactory.create(SampleConfig.class, values); + assertEquals("foo bar", config.webDriverOptions()); + } } diff --git a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java index 05945214..16c54818 100644 --- a/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java +++ b/owner/src/test/java/org/aeonbits/owner/StrSubstitutorTest.java @@ -85,7 +85,7 @@ public void testRecoursiveResolution() { public void testNestedRecursiveResolution() { Properties values = new Properties(); values.setProperty("environment", "dev"); - values.setProperty("environments.${environment}.browser", "chrome"); + values.setProperty("environments.dev.browser", "chrome"); values.setProperty("template", "environments.${environment}.webdriver.${environments.${environment}.browser}.switches.${environment}"); String templateString = "${template}"; StrSubstitutor sub = new StrSubstitutor(values); @@ -93,19 +93,6 @@ public void testNestedRecursiveResolution() { assertEquals("environments.dev.webdriver.chrome.switches.dev", resolvedString); } - @Test - public void testNestedRecursiveResolutionDeepLevel() { - Properties values = new Properties(); - values.setProperty("first", "1"); - values.setProperty("${first}.foo", "2"); - values.setProperty("${${${first}.foo}}.baz", "3"); - values.setProperty("${${${${first}.foo}}.baz}.bar", "4"); - String templateString = "${${${${${first}.foo}}.baz}.bar}"; - StrSubstitutor sub = new StrSubstitutor(values); - String resolvedString = sub.replace(templateString); - assertEquals("4", resolvedString); - } - @Test public void testPropertyWithCurlyBracketsInName() { Properties values = new Properties(); @@ -117,16 +104,6 @@ public void testPropertyWithCurlyBracketsInName() { assertEquals("foo.bar", resolvedString); } - @Test - public void testMultipleNestedResolution() { - Properties values = new Properties(); - values.setProperty("foo", "1"); - String templateString = "${${${${${${${${foo}}}}}}}}"; - StrSubstitutor sub = new StrSubstitutor(values); - String resolvedString = sub.replace(templateString); - assertEquals("1", resolvedString); - } - @Test public void testMissingPropertyIsReplacedWithEmptyString() { Properties values = new Properties() {{ From f7416defb3fc71ab700b78793859d659eec81ed1 Mon Sep 17 00:00:00 2001 From: tomkowski Date: Sun, 11 Dec 2022 10:53:41 +0100 Subject: [PATCH 11/11] refactor replace(String) method for readability --- .../org/aeonbits/owner/StrSubstitutor.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java index 573d2553..04a47b07 100644 --- a/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java +++ b/owner/src/main/java/org/aeonbits/owner/StrSubstitutor.java @@ -78,14 +78,7 @@ String replace(String source) { for (String group : groups) { String value = values.getProperty(group); String replacement = isKeyExpansionExpression(group) ? replace(group) : (value != null) ? replace(value) : ""; - String replacementValue = values.getProperty(replacement); - if (replacementValue == null) { - if (isKeyExpansionExpression(group)) { - replacementValue = value != null ? value : ""; - } else { - replacementValue = replacement; - } - } + String replacementValue = calculateReplacementValue(group, replacement); replacedSource = replacedSource.replaceFirst(Pattern.quote(String.format("${%s}", group)), Matcher.quoteReplacement(replacementValue)); } sb.append(replacedSource); @@ -144,11 +137,30 @@ private List getVariableExpansions(String expression) { /** * Checks if given expression matches PATTERN expression - regex for key expansion expression + * * @param expression expression to be checked, null returns false * @return true if expression matches PATTERN, false otherwise */ private boolean isKeyExpansionExpression(String expression) { - if(expression == null) return false; + if (expression == null) return false; return PATTERN.matcher(expression).find(); } + + /** + * calculates value of a replacement + * + * @param group initial possible key expansion expression + * @param replacement evaluation of group. + * @return if replacement represents a property stored in Config, then the property value is returned. + * if group represents a key expansion expression, then if the key expansion represents a property, the property value is returned, otherwise key expansion expression is invalid and thus value should be an empty string. + * If neither replacement nor group represents a property value, then return replacement as a string value + */ + private String calculateReplacementValue(String group, String replacement) { + String groupValue = values.getProperty(group); + String replacementValue = values.getProperty(replacement); + if (replacementValue != null) return replacementValue; + if (isKeyExpansionExpression(group)) return groupValue != null ? groupValue : ""; + return replacement; + + } } \ No newline at end of file