From 34439f7890131c0b57c546b259c1e7c16e2b640d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:12:57 +0000 Subject: [PATCH 1/3] Initial plan From dabd0dbf9865b99b820ddd9b4979fcf169c87cdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:20:08 +0000 Subject: [PATCH 2/3] Fix MultiDelimiterStringSearchInterpolator cache - populate existingAnswers map when cacheAnswers is true Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- ...ultiDelimiterStringSearchInterpolator.java | 4 ++ ...DelimiterStringSearchInterpolatorTest.java | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java index ba658e5..c1a8541 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java @@ -236,6 +236,10 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept // behaviour result.append(String.valueOf(value)); resolved = true; + + if (cacheAnswers) { + existingAnswers.put(realExpr, value); + } } else { unresolvable.add(wholeExpr); } diff --git a/src/test/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolatorTest.java b/src/test/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolatorTest.java index 8b3b724..7b38a16 100644 --- a/src/test/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolatorTest.java +++ b/src/test/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolatorTest.java @@ -146,4 +146,62 @@ public Object getValue(String expression) { assertEquals("test", interpolator.interpolate("#(test)")); } + + @Test + public void testCacheAnswersTrue() throws InterpolationException { + Map ctx = new HashMap(); + ctx.put("key", "value"); + + final int[] valueSourceCallCount = {0}; + + ValueSource vs = new AbstractValueSource(false) { + @Override + public Object getValue(String expression) { + valueSourceCallCount[0]++; + return ctx.get(expression); + } + }; + + MultiDelimiterStringSearchInterpolator interpolator = new MultiDelimiterStringSearchInterpolator(); + interpolator.setCacheAnswers(true); + interpolator.addValueSource(vs); + + String result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + + assertEquals("value-value-value-value", result); + // first value is interpolated and saved, then the 3 next answers came from existing answer Map + assertEquals(1, valueSourceCallCount[0]); + + // answers are preserved between calls: + result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + assertEquals("value-value-value-value", result); + // still 1 from first call as cache is preserved + assertEquals(1, valueSourceCallCount[0]); + } + + @Test + public void testCacheAnswersFalse() throws InterpolationException { + Map ctx = new HashMap(); + ctx.put("key", "value"); + + final int[] valueSourceCallCount = {0}; + + ValueSource vs = new AbstractValueSource(false) { + @Override + public Object getValue(String expression) { + valueSourceCallCount[0]++; + return ctx.get(expression); + } + }; + + MultiDelimiterStringSearchInterpolator interpolator = new MultiDelimiterStringSearchInterpolator(); + interpolator.addValueSource(vs); + + String result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + + assertEquals("value-value-value-value", result); + // Without caching, expressions are evaluated multiple times due to multi-pass resolution + // In this case: 4 expressions evaluated in 2 passes = 8 calls + assertEquals(8, valueSourceCallCount[0]); + } } From cbe8ccfcf384d7058066da8f96fc6d60c2f48669 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:46:29 +0000 Subject: [PATCH 3/3] Fix RegexBasedInterpolator cache - populate existingAnswers map when cacheAnswers is true Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- .../interpolation/RegexBasedInterpolator.java | 4 ++ .../RegexBasedInterpolatorTest.java | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java index d940aaa..0880987 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java @@ -301,6 +301,10 @@ private String interpolate( // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour result = StringUtils.replace(result, wholeExpr, String.valueOf(value)); + if (cacheAnswers) { + existingAnswers.put(realExpr, value); + } + matcher.reset(result); } } finally { diff --git a/src/test/java/org/codehaus/plexus/interpolation/RegexBasedInterpolatorTest.java b/src/test/java/org/codehaus/plexus/interpolation/RegexBasedInterpolatorTest.java index 61a25c2..f2debe4 100644 --- a/src/test/java/org/codehaus/plexus/interpolation/RegexBasedInterpolatorTest.java +++ b/src/test/java/org/codehaus/plexus/interpolation/RegexBasedInterpolatorTest.java @@ -223,4 +223,73 @@ public Object execute(String expression, Object value) { System.out.println("time with pattern reuse and RegexBasedInterpolator instance reuse " + (end - start)); } + + @Test + public void testCacheAnswersTrue() throws InterpolationException { + Map ctx = new HashMap(); + ctx.put("key", "value"); + + final int[] valueSourceCallCount = {0}; + + ValueSource vs = new AbstractValueSource(false) { + @Override + public Object getValue(String expression) { + valueSourceCallCount[0]++; + return ctx.get(expression); + } + }; + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + interpolator.setCacheAnswers(true); + interpolator.addValueSource(vs); + + // First interpolation + String result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + assertEquals("value-value-value-value", result); + assertEquals(1, valueSourceCallCount[0]); + + // Second interpolation - cache should be used, no new ValueSource calls + result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + assertEquals("value-value-value-value", result); + assertEquals(1, valueSourceCallCount[0]); // still 1, cache was used + + // Third interpolation with different expression that also uses cached value + result = interpolator.interpolate("The value is ${key}"); + assertEquals("The value is value", result); + assertEquals(1, valueSourceCallCount[0]); // still 1, cache was used + } + + @Test + public void testCacheAnswersFalse() throws InterpolationException { + Map ctx = new HashMap(); + ctx.put("key", "value"); + + final int[] valueSourceCallCount = {0}; + + ValueSource vs = new AbstractValueSource(false) { + @Override + public Object getValue(String expression) { + valueSourceCallCount[0]++; + return ctx.get(expression); + } + }; + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + interpolator.addValueSource(vs); + + // First interpolation + String result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + assertEquals("value-value-value-value", result); + assertEquals(1, valueSourceCallCount[0]); + + // Second interpolation - without caching, ValueSource is called again + result = interpolator.interpolate("${key}-${key}-${key}-${key}"); + assertEquals("value-value-value-value", result); + assertEquals(2, valueSourceCallCount[0]); // incremented to 2 + + // Third interpolation + result = interpolator.interpolate("The value is ${key}"); + assertEquals("The value is value", result); + assertEquals(3, valueSourceCallCount[0]); // incremented to 3 + } }