Skip to content

Commit b8633a2

Browse files
Copilotslachiewicz
andcommitted
Add default value support in interpolation
Co-authored-by: slachiewicz <[email protected]>
1 parent db9d5a2 commit b8633a2

File tree

6 files changed

+394
-3
lines changed

6 files changed

+394
-3
lines changed

src/main/java/org/codehaus/plexus/interpolation/StringSearchInterpolator.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
124124
do {
125125
result.append(input, endIdx + 1, startIdx);
126126

127-
endIdx = input.indexOf(endExpr, startIdx + 1);
127+
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
128128
if (endIdx < 0) {
129129
break;
130130
}
@@ -178,6 +178,33 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
178178
throw new InterpolationCycleException(recursionInterceptor, realExpr, wholeExpr);
179179
}
180180

181+
// If value is null, try to extract a default value (format: key:default)
182+
if (value == null) {
183+
int colonIndex = realExpr.indexOf(':');
184+
if (colonIndex > 0) {
185+
String key = realExpr.substring(0, colonIndex);
186+
String defaultValue = realExpr.substring(colonIndex + 1);
187+
188+
// Try to resolve the key part only
189+
for (ValueSource valueSource : valueSources) {
190+
if (value != null) {
191+
break;
192+
}
193+
value = valueSource.getValue(key, startExpr, endExpr);
194+
195+
if (value != null && value.toString().contains(wholeExpr)) {
196+
bestAnswer = value;
197+
value = null;
198+
}
199+
}
200+
201+
// If still null, use the default value
202+
if (value == null) {
203+
value = defaultValue;
204+
}
205+
}
206+
}
207+
181208
if (value != null) {
182209
value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable);
183210

@@ -268,6 +295,44 @@ public void setCacheAnswers(boolean cacheAnswers) {
268295
this.cacheAnswers = cacheAnswers;
269296
}
270297

298+
/**
299+
* Find the matching end expression, accounting for nested expressions.
300+
* @param input The input string
301+
* @param startIdx The index of the start expression
302+
* @param startExpr The start expression delimiter
303+
* @param endExpr The end expression delimiter
304+
* @return The index of the matching end expression, or -1 if not found
305+
*/
306+
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
307+
int depth = 1;
308+
int searchFrom = startIdx + startExpr.length();
309+
310+
while (depth > 0 && searchFrom < input.length()) {
311+
int nextStart = input.indexOf(startExpr, searchFrom);
312+
int nextEnd = input.indexOf(endExpr, searchFrom);
313+
314+
if (nextEnd < 0) {
315+
// No more end delimiters found
316+
return -1;
317+
}
318+
319+
if (nextStart >= 0 && nextStart < nextEnd) {
320+
// Found a nested start expression
321+
depth++;
322+
searchFrom = nextStart + startExpr.length();
323+
} else {
324+
// Found an end expression
325+
depth--;
326+
if (depth == 0) {
327+
return nextEnd;
328+
}
329+
searchFrom = nextEnd + endExpr.length();
330+
}
331+
}
332+
333+
return -1;
334+
}
335+
271336
public void clearAnswers() {
272337
existingAnswers.clear();
273338
}

src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public String interpolate(String input, InterpolationState interpolationState) t
181181
while ((startIdx = input.indexOf(startExpr, endIdx + 1)) > -1) {
182182
result.append(input, endIdx + 1, startIdx);
183183

184-
endIdx = input.indexOf(endExpr, startIdx + 1);
184+
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
185185
if (endIdx < 0) {
186186
break;
187187
}
@@ -212,6 +212,24 @@ public String interpolate(String input, InterpolationState interpolationState) t
212212
}
213213

214214
Object value = getValue(realExpr, interpolationState);
215+
216+
// If value is null, try to extract a default value (format: key:default)
217+
if (value == null) {
218+
int colonIndex = realExpr.indexOf(':');
219+
if (colonIndex > 0) {
220+
String key = realExpr.substring(0, colonIndex);
221+
String defaultValue = realExpr.substring(colonIndex + 1);
222+
223+
// Try to resolve the key part only
224+
value = getValue(key, interpolationState);
225+
226+
// If still null, use the default value
227+
if (value == null) {
228+
value = defaultValue;
229+
}
230+
}
231+
}
232+
215233
if (value != null) {
216234
value = interpolate(String.valueOf(value), interpolationState);
217235

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

247265
return result.toString();
248266
}
267+
268+
/**
269+
* Find the matching end expression, accounting for nested expressions.
270+
* @param input The input string
271+
* @param startIdx The index of the start expression
272+
* @param startExpr The start expression delimiter
273+
* @param endExpr The end expression delimiter
274+
* @return The index of the matching end expression, or -1 if not found
275+
*/
276+
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
277+
int depth = 1;
278+
int searchFrom = startIdx + startExpr.length();
279+
280+
while (depth > 0 && searchFrom < input.length()) {
281+
int nextStart = input.indexOf(startExpr, searchFrom);
282+
int nextEnd = input.indexOf(endExpr, searchFrom);
283+
284+
if (nextEnd < 0) {
285+
// No more end delimiters found
286+
return -1;
287+
}
288+
289+
if (nextStart >= 0 && nextStart < nextEnd) {
290+
// Found a nested start expression
291+
depth++;
292+
searchFrom = nextStart + startExpr.length();
293+
} else {
294+
// Found an end expression
295+
depth--;
296+
if (depth == 0) {
297+
return nextEnd;
298+
}
299+
searchFrom = nextEnd + endExpr.length();
300+
}
301+
}
302+
303+
return -1;
304+
}
249305
}

src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
161161
startIdx = selectedSpec.getNextStartIndex();
162162
result.append(input, endIdx + 1, startIdx);
163163

164-
endIdx = input.indexOf(endExpr, startIdx + 1);
164+
endIdx = findMatchingEndExpr(input, startIdx, startExpr, endExpr);
165165
if (endIdx < 0) {
166166
break;
167167
}
@@ -216,6 +216,32 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept
216216
throw new InterpolationCycleException(recursionInterceptor, realExpr, wholeExpr);
217217
}
218218

219+
// If value is null, try to extract a default value (format: key:default)
220+
if (value == null) {
221+
int colonIndex = realExpr.indexOf(':');
222+
if (colonIndex > 0) {
223+
String key = realExpr.substring(0, colonIndex);
224+
String defaultValue = realExpr.substring(colonIndex + 1);
225+
226+
// Try to resolve the key part only
227+
for (ValueSource vs : valueSources) {
228+
if (value != null) break;
229+
230+
value = vs.getValue(key, startExpr, endExpr);
231+
232+
if (value != null && value.toString().contains(wholeExpr)) {
233+
bestAnswer = value;
234+
value = null;
235+
}
236+
}
237+
238+
// If still null, use the default value
239+
if (value == null) {
240+
value = defaultValue;
241+
}
242+
}
243+
}
244+
219245
if (value != null) {
220246
value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable);
221247

@@ -303,6 +329,44 @@ public List getFeedback() {
303329
return messages;
304330
}
305331

332+
/**
333+
* Find the matching end expression, accounting for nested expressions.
334+
* @param input The input string
335+
* @param startIdx The index of the start expression
336+
* @param startExpr The start expression delimiter
337+
* @param endExpr The end expression delimiter
338+
* @return The index of the matching end expression, or -1 if not found
339+
*/
340+
private int findMatchingEndExpr(String input, int startIdx, String startExpr, String endExpr) {
341+
int depth = 1;
342+
int searchFrom = startIdx + startExpr.length();
343+
344+
while (depth > 0 && searchFrom < input.length()) {
345+
int nextStart = input.indexOf(startExpr, searchFrom);
346+
int nextEnd = input.indexOf(endExpr, searchFrom);
347+
348+
if (nextEnd < 0) {
349+
// No more end delimiters found
350+
return -1;
351+
}
352+
353+
if (nextStart >= 0 && nextStart < nextEnd) {
354+
// Found a nested start expression
355+
depth++;
356+
searchFrom = nextStart + startExpr.length();
357+
} else {
358+
// Found an end expression
359+
depth--;
360+
if (depth == 0) {
361+
return nextEnd;
362+
}
363+
searchFrom = nextEnd + endExpr.length();
364+
}
365+
}
366+
367+
return -1;
368+
}
369+
306370
/**
307371
* Clear the feedback messages from previous interpolate(..) calls.
308372
*/

src/test/java/org/codehaus/plexus/interpolation/StringSearchInterpolatorTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,4 +538,72 @@ public String getName() {
538538
return name;
539539
}
540540
}
541+
542+
@Test
543+
public void testDefaultValueWithExistingKey() throws InterpolationException {
544+
Properties p = new Properties();
545+
p.setProperty("key", "value");
546+
547+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
548+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
549+
550+
assertEquals("This is a test value.", interpolator.interpolate("This is a test ${key:default}."));
551+
}
552+
553+
@Test
554+
public void testDefaultValueWithMissingKey() throws InterpolationException {
555+
Properties p = new Properties();
556+
557+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
558+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
559+
560+
assertEquals("This is a test default.", interpolator.interpolate("This is a test ${missingkey:default}."));
561+
}
562+
563+
@Test
564+
public void testDefaultValueWithEmptyDefault() throws InterpolationException {
565+
Properties p = new Properties();
566+
567+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
568+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
569+
570+
assertEquals("This is a test .", interpolator.interpolate("This is a test ${missingkey:}."));
571+
}
572+
573+
@Test
574+
public void testDefaultValueWithColonInDefault() throws InterpolationException {
575+
Properties p = new Properties();
576+
577+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
578+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
579+
580+
assertEquals(
581+
"This is a test http://example.com.",
582+
interpolator.interpolate("This is a test ${missingkey:http://example.com}."));
583+
}
584+
585+
@Test
586+
public void testDefaultValueWithNestedExpression() throws InterpolationException {
587+
Properties p = new Properties();
588+
p.setProperty("fallback.key", "fallbackValue");
589+
590+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
591+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
592+
593+
assertEquals(
594+
"This is a test fallbackValue.",
595+
interpolator.interpolate("This is a test ${missingkey:${fallback.key}}."));
596+
}
597+
598+
@Test
599+
public void testNoDefaultValueSyntax() throws InterpolationException {
600+
Properties p = new Properties();
601+
p.setProperty("key:with:colons", "colonValue");
602+
603+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
604+
interpolator.addValueSource(new PropertiesBasedValueSource(p));
605+
606+
// When a key actually contains colons, it should still work if the key exists
607+
assertEquals("This is a test colonValue.", interpolator.interpolate("This is a test ${key:with:colons}."));
608+
}
541609
}

src/test/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolatorTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,74 @@ void fixedInjectedIntoRegular() throws InterpolationException {
493493
assertEquals("v1X", interpolator.interpolate("${key1}${key}"));
494494
}
495495

496+
@Test
497+
void testDefaultValueWithExistingKey() {
498+
Properties p = new Properties();
499+
p.setProperty("key", "value");
500+
501+
FixedStringSearchInterpolator interpolator =
502+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
503+
504+
assertEquals("This is a test value.", interpolator.interpolate("This is a test ${key:default}."));
505+
}
506+
507+
@Test
508+
void testDefaultValueWithMissingKey() {
509+
Properties p = new Properties();
510+
511+
FixedStringSearchInterpolator interpolator =
512+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
513+
514+
assertEquals("This is a test default.", interpolator.interpolate("This is a test ${missingkey:default}."));
515+
}
516+
517+
@Test
518+
void testDefaultValueWithEmptyDefault() {
519+
Properties p = new Properties();
520+
521+
FixedStringSearchInterpolator interpolator =
522+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
523+
524+
assertEquals("This is a test .", interpolator.interpolate("This is a test ${missingkey:}."));
525+
}
526+
527+
@Test
528+
void testDefaultValueWithColonInDefault() {
529+
Properties p = new Properties();
530+
531+
FixedStringSearchInterpolator interpolator =
532+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
533+
534+
assertEquals(
535+
"This is a test http://example.com.",
536+
interpolator.interpolate("This is a test ${missingkey:http://example.com}."));
537+
}
538+
539+
@Test
540+
void testDefaultValueWithNestedExpression() {
541+
Properties p = new Properties();
542+
p.setProperty("fallback.key", "fallbackValue");
543+
544+
FixedStringSearchInterpolator interpolator =
545+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
546+
547+
assertEquals(
548+
"This is a test fallbackValue.",
549+
interpolator.interpolate("This is a test ${missingkey:${fallback.key}}."));
550+
}
551+
552+
@Test
553+
void testNoDefaultValueSyntax() {
554+
Properties p = new Properties();
555+
p.setProperty("key:with:colons", "colonValue");
556+
557+
FixedStringSearchInterpolator interpolator =
558+
FixedStringSearchInterpolator.create(new PropertiesBasedValueSource(p));
559+
560+
// When a key actually contains colons, it should still work if the key exists
561+
assertEquals("This is a test colonValue.", interpolator.interpolate("This is a test ${key:with:colons}."));
562+
}
563+
496564
private PropertiesBasedValueSource properttyBasedValueSource(String... values) {
497565
Properties p = new Properties();
498566
for (int i = 0; i < values.length; i += 2) {

0 commit comments

Comments
 (0)