Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
do {
result.append(input, endIdx + 1, startIdx);

endIdx = input.indexOf(endExpr, startIdx + 1);
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
if (endIdx < 0) {
break;
}
Expand Down Expand Up @@ -178,6 +178,33 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
throw new InterpolationCycleException(recursionInterceptor, realExpr, wholeExpr);
}

// If value is null, try to extract a default value (format: key:default)
if (value == null) {
int colonIndex = realExpr.indexOf(':');
if (colonIndex > 0) {
String key = realExpr.substring(0, colonIndex);
String defaultValue = realExpr.substring(colonIndex + 1);

// Try to resolve the key part only
for (ValueSource valueSource : valueSources) {
if (value != null) {
break;
}
value = valueSource.getValue(key, startExpr, endExpr);

if (value != null && value.toString().contains(wholeExpr)) {
bestAnswer = value;
value = null;
}
}

// If still null, use the default value
if (value == null) {
value = defaultValue;
}
}
}

if (value != null) {
value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable);

Expand Down Expand Up @@ -268,6 +295,44 @@ public void setCacheAnswers(boolean cacheAnswers) {
this.cacheAnswers = cacheAnswers;
}

/**
* Find the matching end expression, accounting for nested expressions.
* @param input The input string
* @param startIdx The index of the start expression
* @param startExpr The start expression delimiter
* @param endExpr The end expression delimiter
* @return The index of the matching end expression, or -1 if not found
*/
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
int depth = 1;
int searchFrom = startIdx + startExpr.length();

while (depth > 0 && searchFrom < input.length()) {
int nextStart = input.indexOf(startExpr, searchFrom);
int nextEnd = input.indexOf(endExpr, searchFrom);

if (nextEnd < 0) {
// No more end delimiters found
return -1;
}

if (nextStart >= 0 && nextStart < nextEnd) {
// Found a nested start expression
depth++;
searchFrom = nextStart + startExpr.length();
} else {
// Found an end expression
depth--;
if (depth == 0) {
return nextEnd;
}
searchFrom = nextEnd + endExpr.length();
}
}

return -1;
}

public void clearAnswers() {
existingAnswers.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public String interpolate(String input, InterpolationState interpolationState) t
while ((startIdx = input.indexOf(startExpr, endIdx + 1)) > -1) {
result.append(input, endIdx + 1, startIdx);

endIdx = input.indexOf(endExpr, startIdx + 1);
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
if (endIdx < 0) {
break;
}
Expand Down Expand Up @@ -212,6 +212,24 @@ public String interpolate(String input, InterpolationState interpolationState) t
}

Object value = getValue(realExpr, interpolationState);

// If value is null, try to extract a default value (format: key:default)
if (value == null) {
int colonIndex = realExpr.indexOf(':');
if (colonIndex > 0) {
String key = realExpr.substring(0, colonIndex);
String defaultValue = realExpr.substring(colonIndex + 1);

// Try to resolve the key part only
value = getValue(key, interpolationState);

// If still null, use the default value
if (value == null) {
value = defaultValue;
}
}
}

if (value != null) {
value = interpolate(String.valueOf(value), interpolationState);

Expand Down Expand Up @@ -246,4 +264,42 @@ public String interpolate(String input, InterpolationState interpolationState) t

return result.toString();
}

/**
* Find the matching end expression, accounting for nested expressions.
* @param input The input string
* @param startIdx The index of the start expression
* @param startExpr The start expression delimiter
* @param endExpr The end expression delimiter
* @return The index of the matching end expression, or -1 if not found
*/
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
int depth = 1;
int searchFrom = startIdx + startExpr.length();

while (depth > 0 && searchFrom < input.length()) {
int nextStart = input.indexOf(startExpr, searchFrom);
int nextEnd = input.indexOf(endExpr, searchFrom);

if (nextEnd < 0) {
// No more end delimiters found
return -1;
}

if (nextStart >= 0 && nextStart < nextEnd) {
// Found a nested start expression
depth++;
searchFrom = nextStart + startExpr.length();
} else {
// Found an end expression
depth--;
if (depth == 0) {
return nextEnd;
}
searchFrom = nextEnd + endExpr.length();
}
}

return -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
startIdx = selectedSpec.getNextStartIndex();
result.append(input, endIdx + 1, startIdx);

endIdx = input.indexOf(endExpr, startIdx + 1);
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
if (endIdx < 0) {
break;
}
Expand Down Expand Up @@ -216,6 +216,32 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
throw new InterpolationCycleException(recursionInterceptor, realExpr, wholeExpr);
}

// If value is null, try to extract a default value (format: key:default)
if (value == null) {
int colonIndex = realExpr.indexOf(':');
if (colonIndex > 0) {
String key = realExpr.substring(0, colonIndex);
String defaultValue = realExpr.substring(colonIndex + 1);

// Try to resolve the key part only
for (ValueSource vs : valueSources) {
if (value != null) break;

value = vs.getValue(key, startExpr, endExpr);

if (value != null && value.toString().contains(wholeExpr)) {
bestAnswer = value;
value = null;
}
}

// If still null, use the default value
if (value == null) {
value = defaultValue;
}
}
}

if (value != null) {
value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable);

Expand Down Expand Up @@ -303,6 +329,44 @@ public List getFeedback() {
return messages;
}

/**
* Find the matching end expression, accounting for nested expressions.
* @param input The input string
* @param startIdx The index of the start expression
* @param startExpr The start expression delimiter
* @param endExpr The end expression delimiter
* @return The index of the matching end expression, or -1 if not found
*/
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
int depth = 1;
int searchFrom = startIdx + startExpr.length();

while (depth > 0 && searchFrom < input.length()) {
int nextStart = input.indexOf(startExpr, searchFrom);
int nextEnd = input.indexOf(endExpr, searchFrom);

if (nextEnd < 0) {
// No more end delimiters found
return -1;
}

if (nextStart >= 0 && nextStart < nextEnd) {
// Found a nested start expression
depth++;
searchFrom = nextStart + startExpr.length();
} else {
// Found an end expression
depth--;
if (depth == 0) {
return nextEnd;
}
searchFrom = nextEnd + endExpr.length();
}
}

return -1;
}

/**
* Clear the feedback messages from previous interpolate(..) calls.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,72 @@ public String getName() {
return name;
}
}

@Test
public void testDefaultValueWithExistingKey() throws InterpolationException {
Properties p = new Properties();
p.setProperty("key", "value");

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

assertEquals("This is a test value.", interpolator.interpolate("This is a test ${key:default}."));
}

@Test
public void testDefaultValueWithMissingKey() throws InterpolationException {
Properties p = new Properties();

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

assertEquals("This is a test default.", interpolator.interpolate("This is a test ${missingkey:default}."));
}

@Test
public void testDefaultValueWithEmptyDefault() throws InterpolationException {
Properties p = new Properties();

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

assertEquals("This is a test .", interpolator.interpolate("This is a test ${missingkey:}."));
}

@Test
public void testDefaultValueWithColonInDefault() throws InterpolationException {
Properties p = new Properties();

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

assertEquals(
"This is a test http://example.com.",
interpolator.interpolate("This is a test ${missingkey:http://example.com}."));
}

@Test
public void testDefaultValueWithNestedExpression() throws InterpolationException {
Properties p = new Properties();
p.setProperty("fallback.key", "fallbackValue");

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

assertEquals(
"This is a test fallbackValue.",
interpolator.interpolate("This is a test ${missingkey:${fallback.key}}."));
}

@Test
public void testNoDefaultValueSyntax() throws InterpolationException {
Properties p = new Properties();
p.setProperty("key:with:colons", "colonValue");

StringSearchInterpolator interpolator = new StringSearchInterpolator();
interpolator.addValueSource(new PropertiesBasedValueSource(p));

// When a key actually contains colons, it should still work if the key exists
assertEquals("This is a test colonValue.", interpolator.interpolate("This is a test ${key:with:colons}."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,74 @@ void fixedInjectedIntoRegular() throws InterpolationException {
assertEquals("v1X", interpolator.interpolate("${key1}${key}"));
}

@Test
void testDefaultValueWithExistingKey() {
Properties p = new Properties();
p.setProperty("key", "value");

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

assertEquals("This is a test value.", interpolator.interpolate("This is a test ${key:default}."));
}

@Test
void testDefaultValueWithMissingKey() {
Properties p = new Properties();

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

assertEquals("This is a test default.", interpolator.interpolate("This is a test ${missingkey:default}."));
}

@Test
void testDefaultValueWithEmptyDefault() {
Properties p = new Properties();

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

assertEquals("This is a test .", interpolator.interpolate("This is a test ${missingkey:}."));
}

@Test
void testDefaultValueWithColonInDefault() {
Properties p = new Properties();

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

assertEquals(
"This is a test http://example.com.",
interpolator.interpolate("This is a test ${missingkey:http://example.com}."));
}

@Test
void testDefaultValueWithNestedExpression() {
Properties p = new Properties();
p.setProperty("fallback.key", "fallbackValue");

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

assertEquals(
"This is a test fallbackValue.",
interpolator.interpolate("This is a test ${missingkey:${fallback.key}}."));
}

@Test
void testNoDefaultValueSyntax() {
Properties p = new Properties();
p.setProperty("key:with:colons", "colonValue");

FixedStringSearchInterpolator interpolator =
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));

// When a key actually contains colons, it should still work if the key exists
assertEquals("This is a test colonValue.", interpolator.interpolate("This is a test ${key:with:colons}."));
}

private PropertiesBasedValueSource properttyBasedValueSource(String... values) {
Properties p = new Properties();
for (int i = 0; i < values.length; i += 2) {
Expand Down
Loading