diff --git a/src/main/java/org/codehaus/plexus/interpolation/Interpolator.java b/src/main/java/org/codehaus/plexus/interpolation/Interpolator.java index 7ed2c88..9581c7a 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/Interpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/Interpolator.java @@ -88,6 +88,62 @@ public interface Interpolator extends BasicInterpolator { String interpolate(String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor) throws InterpolationException; + /** + * Attempt to resolve all expressions in the given input string, using the + * provided lists of value sources and post processors. This method allows + * for efficient interpolation without the need to repeatedly add and remove + * value sources and post processors from the interpolator instance. + *

+ * This method triggers the use of a {@link SimpleRecursionInterceptor} + * instance for protection against expression cycles.

+ *

+ * return an empty String if input is null + * + * @param input The input string to interpolate + * @param valueSources The list of value sources to use for resolving expressions. + * If null or empty, no value sources will be used. + * @param postProcessors The list of post processors to apply after interpolation. + * If null or empty, no post processors will be used. + * @return interpolated string. + * @throws InterpolationException in case of an error. + * @since 1.29 + */ + default String interpolate( + String input, List valueSources, List postProcessors) + throws InterpolationException { + return interpolate(input, valueSources, postProcessors, new SimpleRecursionInterceptor()); + } + + /** + * Attempt to resolve all expressions in the given input string, using the + * provided lists of value sources and post processors. This method allows + * for efficient interpolation without the need to repeatedly add and remove + * value sources and post processors from the interpolator instance. + *

+ * The supplied recursion interceptor will provide protection from expression + * cycles, ensuring that the input can be resolved or an exception is thrown. + *

+ * return an empty String if input is null + * + * @param input The input string to interpolate + * @param valueSources The list of value sources to use for resolving expressions. + * If null or empty, no value sources will be used. + * @param postProcessors The list of post processors to apply after interpolation. + * If null or empty, no post processors will be used. + * @param recursionInterceptor Used to protect the interpolation process + * from expression cycles, and throw an + * exception if one is detected. + * @return interpolated string. + * @throws InterpolationException in case of an error. + * @since 1.29 + */ + String interpolate( + String input, + List valueSources, + List postProcessors, + RecursionInterceptor recursionInterceptor) + throws InterpolationException; + /** * Return any feedback messages and errors that were generated - but * suppressed - during the interpolation process. Since unresolvable diff --git a/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java index 0880987..f3d27d2 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/RegexBasedInterpolator.java @@ -248,10 +248,39 @@ private String interpolate( String expressionDelimiterEnd, int realExprGroup) throws InterpolationException { + return interpolate( + input, + recursionInterceptor, + expressionPattern, + expressionDelimiterStart, + expressionDelimiterEnd, + realExprGroup, + this.valueSources, + this.postProcessors); + } + + private String interpolate( + String input, + RecursionInterceptor recursionInterceptor, + Pattern expressionPattern, + String expressionDelimiterStart, + String expressionDelimiterEnd, + int realExprGroup, + List valueSources, + List postProcessors) + throws InterpolationException { if (input == null) { // return empty String to prevent NPE too return ""; } + + // Use instance value sources if provided list is null or empty + List effectiveValueSources = + (valueSources != null && !valueSources.isEmpty()) ? valueSources : this.valueSources; + // Use instance post processors if provided list is null or empty + List effectivePostProcessors = + (postProcessors != null && !postProcessors.isEmpty()) ? postProcessors : this.postProcessors; + String result = input; Matcher matcher = expressionPattern.matcher(result); @@ -271,7 +300,7 @@ private String interpolate( recursionInterceptor.expressionResolutionStarted(realExpr); try { Object value = existingAnswers.get(realExpr); - for (ValueSource vs : valueSources) { + for (ValueSource vs : effectiveValueSources) { if (value != null) break; value = vs.getValue(realExpr, expressionDelimiterStart, expressionDelimiterEnd); @@ -284,10 +313,12 @@ private String interpolate( expressionPattern, expressionDelimiterStart, expressionDelimiterEnd, - realExprGroup); + realExprGroup, + effectiveValueSources, + effectivePostProcessors); - if (postProcessors != null && !postProcessors.isEmpty()) { - for (InterpolationPostProcessor postProcessor : postProcessors) { + if (effectivePostProcessors != null && !effectivePostProcessors.isEmpty()) { + for (InterpolationPostProcessor postProcessor : effectivePostProcessors) { Object newVal = postProcessor.execute(realExpr, value); if (newVal != null) { value = newVal; @@ -373,7 +404,7 @@ public String interpolate(String input, String thisPrefixPattern) throws Interpo * @param input The input string to interpolate */ public String interpolate(String input) throws InterpolationException { - return interpolate(input, null, null); + return interpolate(input, (String) null, (RecursionInterceptor) null); } /** @@ -393,6 +424,65 @@ public String interpolate(String input, RecursionInterceptor recursionIntercepto return interpolate(input, null, recursionInterceptor); } + /** + * {@inheritDoc} + */ + public String interpolate( + String input, + List valueSources, + List postProcessors, + RecursionInterceptor recursionInterceptor) + throws InterpolationException { + if (input == null) { + // return empty String to prevent NPE too + return ""; + } + if (recursionInterceptor == null) { + recursionInterceptor = new SimpleRecursionInterceptor(); + } + + String thisPrefixPattern = null; + int realExprGroup = 2; + Pattern expressionPattern; + final String expressionDelimiterStart; + final String expressionDelimiterEnd; + if (startRegex != null || endRegex != null) { + if (thisPrefixPattern == null) { + expressionPattern = getPattern(startRegex + endRegex); + realExprGroup = 1; + } else { + expressionPattern = getPattern(startRegex + thisPrefixPattern + endRegex); + } + expressionDelimiterStart = startRegex; + expressionDelimiterEnd = endRegex; + + } else { + expressionDelimiterStart = "${"; + expressionDelimiterEnd = "}"; + if (thisPrefixPattern != null) { + expressionPattern = getPattern("\\$\\{(" + thisPrefixPattern + ")?(.+?)\\}"); + } else { + expressionPattern = getPattern(DEFAULT_REGEXP); + realExprGroup = 1; + } + } + try { + return interpolate( + input, + recursionInterceptor, + expressionPattern, + expressionDelimiterStart, + expressionDelimiterEnd, + realExprGroup, + valueSources, + postProcessors); + } finally { + if (!cacheAnswers) { + clearAnswers(); + } + } + } + public boolean isReusePatterns() { return reusePatterns; } diff --git a/src/main/java/org/codehaus/plexus/interpolation/StringSearchInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/StringSearchInterpolator.java index 28e2189..b099716 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/StringSearchInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/StringSearchInterpolator.java @@ -110,13 +110,48 @@ public String interpolate(String input, RecursionInterceptor recursionIntercepto } } + /** + * {@inheritDoc} + */ + public String interpolate( + String input, + List valueSources, + List postProcessors, + RecursionInterceptor recursionInterceptor) + throws InterpolationException { + try { + return interpolate(input, recursionInterceptor, new HashSet(), valueSources, postProcessors); + } finally { + if (!cacheAnswers) { + existingAnswers.clear(); + } + } + } + private String interpolate(String input, RecursionInterceptor recursionInterceptor, Set unresolvable) throws InterpolationException { + return interpolate(input, recursionInterceptor, unresolvable, this.valueSources, this.postProcessors); + } + + private String interpolate( + String input, + RecursionInterceptor recursionInterceptor, + Set unresolvable, + List valueSources, + List postProcessors) + throws InterpolationException { if (input == null) { // return empty String to prevent NPE too return ""; } + // Use instance value sources if provided list is null or empty + List effectiveValueSources = + (valueSources != null && !valueSources.isEmpty()) ? valueSources : this.valueSources; + // Use instance post processors if provided list is null or empty + List effectivePostProcessors = + (postProcessors != null && !postProcessors.isEmpty()) ? postProcessors : this.postProcessors; + int startIdx; int endIdx = -1; if ((startIdx = input.indexOf(startExpr, endIdx + 1)) > -1) { @@ -159,7 +194,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept Object value = getExistingAnswer(realExpr); Object bestAnswer = null; - for (ValueSource valueSource : valueSources) { + for (ValueSource valueSource : effectiveValueSources) { if (value != null) { break; } @@ -179,10 +214,15 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept } if (value != null) { - value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable); - - if (postProcessors != null && !postProcessors.isEmpty()) { - for (InterpolationPostProcessor postProcessor : postProcessors) { + value = interpolate( + String.valueOf(value), + recursionInterceptor, + unresolvable, + effectiveValueSources, + effectivePostProcessors); + + if (effectivePostProcessors != null && !effectivePostProcessors.isEmpty()) { + for (InterpolationPostProcessor postProcessor : effectivePostProcessors) { Object newVal = postProcessor.execute(realExpr, value); if (newVal != null) { value = newVal; 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 c1a8541..9a3332b 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator.java @@ -133,12 +133,48 @@ public String interpolate(String input, RecursionInterceptor recursionIntercepto } } + /** + * {@inheritDoc} + */ + public String interpolate( + String input, + List valueSources, + List postProcessors, + RecursionInterceptor recursionInterceptor) + throws InterpolationException { + try { + return interpolate(input, recursionInterceptor, new HashSet(), valueSources, postProcessors); + } finally { + if (!cacheAnswers) { + existingAnswers.clear(); + } + } + } + private String interpolate(String input, RecursionInterceptor recursionInterceptor, Set unresolvable) throws InterpolationException { + return interpolate(input, recursionInterceptor, unresolvable, this.valueSources, this.postProcessors); + } + + private String interpolate( + String input, + RecursionInterceptor recursionInterceptor, + Set unresolvable, + List valueSources, + List postProcessors) + throws InterpolationException { if (input == null) { // return empty String to prevent NPE too return ""; } + + // Use instance value sources if provided list is null or empty + List effectiveValueSources = + (valueSources != null && !valueSources.isEmpty()) ? valueSources : this.valueSources; + // Use instance post processors if provided list is null or empty + List effectivePostProcessors = + (postProcessors != null && !postProcessors.isEmpty()) ? postProcessors : this.postProcessors; + StringBuilder result = new StringBuilder(input.length() * 2); String lastResult = input; @@ -198,7 +234,7 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept Object value = existingAnswers.get(realExpr); Object bestAnswer = null; - for (ValueSource vs : valueSources) { + for (ValueSource vs : effectiveValueSources) { if (value != null) break; value = vs.getValue(realExpr, startExpr, endExpr); @@ -217,10 +253,15 @@ private String interpolate(String input, RecursionInterceptor recursionIntercept } if (value != null) { - value = interpolate(String.valueOf(value), recursionInterceptor, unresolvable); - - if (postProcessors != null && !postProcessors.isEmpty()) { - for (Object postProcessor1 : postProcessors) { + value = interpolate( + String.valueOf(value), + recursionInterceptor, + unresolvable, + effectiveValueSources, + effectivePostProcessors); + + if (effectivePostProcessors != null && !effectivePostProcessors.isEmpty()) { + for (Object postProcessor1 : effectivePostProcessors) { InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) postProcessor1; Object newVal = postProcessor.execute(realExpr, value); if (newVal != null) { diff --git a/src/test/java/org/codehaus/plexus/interpolation/InterpolateWithListsTest.java b/src/test/java/org/codehaus/plexus/interpolation/InterpolateWithListsTest.java new file mode 100644 index 0000000..bf118d5 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/interpolation/InterpolateWithListsTest.java @@ -0,0 +1,113 @@ +package org.codehaus.plexus.interpolation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InterpolateWithListsTest { + + @Test + public void testStringSearchInterpolatorWithLists() throws InterpolationException { + String src = "This is a ${test.label}."; + String result = "This is a test value."; + + Properties p = new Properties(); + p.setProperty("test.label", "test value"); + + List valueSources = new ArrayList<>(); + valueSources.add(new PropertiesBasedValueSource(p)); + + List postProcessors = new ArrayList<>(); + + StringSearchInterpolator interpolator = new StringSearchInterpolator(); + assertEquals(result, interpolator.interpolate(src, valueSources, postProcessors)); + } + + @Test + public void testRegexBasedInterpolatorWithLists() throws InterpolationException { + String src = "This is a ${test.label}."; + String result = "This is a test value."; + + Properties p = new Properties(); + p.setProperty("test.label", "test value"); + + List valueSources = new ArrayList<>(); + valueSources.add(new PropertiesBasedValueSource(p)); + + List postProcessors = new ArrayList<>(); + + RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); + assertEquals(result, interpolator.interpolate(src, valueSources, postProcessors)); + } + + @Test + public void testMultiDelimiterInterpolatorWithLists() throws InterpolationException { + String src = "This is a ${test.label}."; + String result = "This is a test value."; + + Properties p = new Properties(); + p.setProperty("test.label", "test value"); + + List valueSources = new ArrayList<>(); + valueSources.add(new PropertiesBasedValueSource(p)); + + List postProcessors = new ArrayList<>(); + + org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator interpolator = + new org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator(); + assertEquals(result, interpolator.interpolate(src, valueSources, postProcessors)); + } + + @Test + public void testInterpolatorWithPostProcessor() throws InterpolationException { + String src = "This is a ${test.label}."; + String result = "This is a PROCESSED."; + + Properties p = new Properties(); + p.setProperty("test.label", "test value"); + + List valueSources = new ArrayList<>(); + valueSources.add(new PropertiesBasedValueSource(p)); + + List postProcessors = new ArrayList<>(); + postProcessors.add(new InterpolationPostProcessor() { + @Override + public Object execute(String expression, Object value) { + if ("test.label".equals(expression)) { + return "PROCESSED"; + } + return null; + } + }); + + StringSearchInterpolator interpolator = new StringSearchInterpolator(); + assertEquals(result, interpolator.interpolate(src, valueSources, postProcessors)); + } + + @Test + public void testInstanceValueSourcesNotAffected() throws InterpolationException { + String src = "This is a ${test.label}."; + + Properties p1 = new Properties(); + p1.setProperty("test.label", "instance value"); + + Properties p2 = new Properties(); + p2.setProperty("test.label", "list value"); + + StringSearchInterpolator interpolator = new StringSearchInterpolator(); + interpolator.addValueSource(new PropertiesBasedValueSource(p1)); + + List valueSources = new ArrayList<>(); + valueSources.add(new PropertiesBasedValueSource(p2)); + + // Should use the list value sources, not instance value sources + assertEquals("This is a list value.", interpolator.interpolate(src, valueSources, null)); + + // Should still use instance value sources with regular interpolate + assertEquals("This is a instance value.", interpolator.interpolate(src)); + } +}