org.codehaus.plexus.interpolation.RegexBasedInterpolator Maven / Gradle / Ivy
package org.codehaus.plexus.interpolation;
/*
* Copyright 2001-2008 Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.plexus.interpolation.util.StringUtils;
/**
* Expansion of the original RegexBasedInterpolator, found in plexus-utils, this
* interpolator provides options for setting custom prefix/suffix regex parts,
* and includes a {@link RecursionInterceptor} parameter in its interpolate(..)
* call, to allow the detection of cyclical expression references.
*
*/
public class RegexBasedInterpolator implements Interpolator {
private String startRegex;
private String endRegex;
private Map existingAnswers = new HashMap();
private List valueSources = new ArrayList();
private List postProcessors = new ArrayList();
private boolean reusePatterns = false;
private boolean cacheAnswers = false;
public static final String DEFAULT_REGEXP = "\\$\\{(.+?)\\}";
/**
* the key is the regex the value is the Pattern
* At the class construction time the Map will contains the default Pattern
*/
private Map compiledPatterns = new WeakHashMap();
/**
* Setup a basic interpolator.
* NOTE: You will have to call
* {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
* if you use this constructor!
*/
public RegexBasedInterpolator() {
compiledPatterns.put(DEFAULT_REGEXP, Pattern.compile(DEFAULT_REGEXP));
}
/**
*
* @param reusePatterns already compiled patterns will be reused
*/
public RegexBasedInterpolator(boolean reusePatterns) {
this();
this.reusePatterns = reusePatterns;
}
/**
* Setup an interpolator with no value sources, and the specified regex pattern
* prefix and suffix in place of the default one.
* NOTE: You will have to call
* {@link RegexBasedInterpolator#addValueSource(ValueSource)} at least once
* if you use this constructor!
*
* @param startRegex start of the regular expression to use
* @param endRegex end of the regular expression to use
*/
public RegexBasedInterpolator(String startRegex, String endRegex) {
this();
this.startRegex = startRegex;
this.endRegex = endRegex;
}
/**
* Setup a basic interpolator with the specified list of value sources.
*
* @param valueSources The list of value sources to use
*/
public RegexBasedInterpolator(List valueSources) {
this();
this.valueSources.addAll(valueSources);
}
/**
* Setup an interpolator with the specified value sources, and the specified
* regex pattern prefix and suffix in place of the default one.
*
* @param startRegex start of the regular expression to use
* @param endRegex end of the regular expression to use
* @param valueSources The list of value sources to use
*/
public RegexBasedInterpolator(String startRegex, String endRegex, List valueSources) {
this();
this.startRegex = startRegex;
this.endRegex = endRegex;
this.valueSources.addAll(valueSources);
}
/**
* {@inheritDoc}
*/
public void addValueSource(ValueSource valueSource) {
valueSources.add(valueSource);
}
/**
* {@inheritDoc}
*/
public void removeValuesSource(ValueSource valueSource) {
valueSources.remove(valueSource);
}
/**
* {@inheritDoc}
*/
public void addPostProcessor(InterpolationPostProcessor postProcessor) {
postProcessors.add(postProcessor);
}
/**
* {@inheritDoc}
*/
public void removePostProcessor(InterpolationPostProcessor postProcessor) {
postProcessors.remove(postProcessor);
}
/**
* Attempt to resolve all expressions in the given input string, using the
* given pattern to first trim an optional prefix from each expression. The
* supplied recursion interceptor will provide protection from expression
* cycles, ensuring that the input can be resolved or an exception is
* thrown.
*
* @param input The input string to interpolate
*
* @param thisPrefixPattern An optional pattern that should be trimmed from
* the start of any expressions found in the input.
*
* @param recursionInterceptor Used to protect the interpolation process
* from expression cycles, and throw an
* exception if one is detected.
*/
public String interpolate(String input, String thisPrefixPattern, RecursionInterceptor recursionInterceptor)
throws InterpolationException {
if (input == null) {
// return empty String to prevent NPE too
return "";
}
if (recursionInterceptor == null) {
recursionInterceptor = new SimpleRecursionInterceptor();
}
if (thisPrefixPattern != null && thisPrefixPattern.length() == 0) {
thisPrefixPattern = null;
}
int realExprGroup = 2;
Pattern expressionPattern;
if (startRegex != null || endRegex != null) {
if (thisPrefixPattern == null) {
expressionPattern = getPattern(startRegex + endRegex);
realExprGroup = 1;
} else {
expressionPattern = getPattern(startRegex + thisPrefixPattern + endRegex);
}
} else if (thisPrefixPattern != null) {
expressionPattern = getPattern("\\$\\{(" + thisPrefixPattern + ")?(.+?)\\}");
} else {
expressionPattern = getPattern(DEFAULT_REGEXP);
realExprGroup = 1;
}
try {
return interpolate(input, recursionInterceptor, expressionPattern, realExprGroup);
} finally {
if (!cacheAnswers) {
clearAnswers();
}
}
}
private Pattern getPattern(String regExp) {
if (!reusePatterns) {
return Pattern.compile(regExp);
}
Pattern pattern;
synchronized (this) {
pattern = compiledPatterns.get(regExp);
if (pattern != null) {
return pattern;
}
pattern = Pattern.compile(regExp);
compiledPatterns.put(regExp, pattern);
}
return pattern;
}
/**
* Entry point for recursive resolution of an expression and all of its
* nested expressions.
*
* @todo Ensure unresolvable expressions don't trigger infinite recursion.
*/
private String interpolate(
String input, RecursionInterceptor recursionInterceptor, Pattern expressionPattern, int realExprGroup)
throws InterpolationException {
if (input == null) {
// return empty String to prevent NPE too
return "";
}
String result = input;
Matcher matcher = expressionPattern.matcher(result);
while (matcher.find()) {
String wholeExpr = matcher.group(0);
String realExpr = matcher.group(realExprGroup);
if (realExpr.startsWith(".")) {
realExpr = realExpr.substring(1);
}
if (recursionInterceptor.hasRecursiveExpression(realExpr)) {
throw new InterpolationCycleException(recursionInterceptor, realExpr, wholeExpr);
}
recursionInterceptor.expressionResolutionStarted(realExpr);
try {
Object value = existingAnswers.get(realExpr);
for (ValueSource vs : valueSources) {
if (value != null) break;
value = vs.getValue(realExpr);
}
if (value != null) {
value = interpolate(String.valueOf(value), recursionInterceptor, expressionPattern, realExprGroup);
if (postProcessors != null && !postProcessors.isEmpty()) {
for (InterpolationPostProcessor postProcessor : postProcessors) {
Object newVal = postProcessor.execute(realExpr, value);
if (newVal != null) {
value = newVal;
break;
}
}
}
// could use:
// result = matcher.replaceFirst( stringValue );
// but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour
result = StringUtils.replace(result, wholeExpr, String.valueOf(value));
matcher.reset(result);
}
} finally {
recursionInterceptor.expressionResolutionFinished(realExpr);
}
}
return result;
}
/**
* Return any feedback messages and errors that were generated - but
* suppressed - during the interpolation process. Since unresolvable
* expressions will be left in the source string as-is, this feedback is
* optional, and will only be useful for debugging interpolation problems.
*
* @return a {@link List} that may be interspersed with {@link String} and
* {@link Throwable} instances.
*/
public List getFeedback() {
List messages = new ArrayList();
for (Object valueSource : valueSources) {
ValueSource vs = (ValueSource) valueSource;
List feedback = vs.getFeedback();
if (feedback != null && !feedback.isEmpty()) {
messages.addAll(feedback);
}
}
return messages;
}
/**
* Clear the feedback messages from previous interpolate(..) calls.
*/
public void clearFeedback() {
for (Object valueSource : valueSources) {
ValueSource vs = (ValueSource) valueSource;
vs.clearFeedback();
}
}
/**
* See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
*
* This method triggers the use of a {@link SimpleRecursionInterceptor}
* instance for protection against expression cycles.
*
* @param input The input string to interpolate
*
* @param thisPrefixPattern An optional pattern that should be trimmed from
* the start of any expressions found in the input.
*/
public String interpolate(String input, String thisPrefixPattern) throws InterpolationException {
return interpolate(input, thisPrefixPattern, null);
}
/**
* See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
*
* This method triggers the use of a {@link SimpleRecursionInterceptor}
* instance for protection against expression cycles. It also leaves empty the
* expression prefix which would otherwise be trimmed from expressions. The
* result is that any detected expression will be resolved as-is.
*
* @param input The input string to interpolate
*/
public String interpolate(String input) throws InterpolationException {
return interpolate(input, null, null);
}
/**
* See {@link RegexBasedInterpolator#interpolate(String, String, RecursionInterceptor)}.
*
* This method leaves empty the expression prefix which would otherwise be
* trimmed from expressions. The result is that any detected expression will
* be resolved as-is.
*
* @param input The input string to interpolate
*
* @param recursionInterceptor Used to protect the interpolation process
* from expression cycles, and throw an
* exception if one is detected.
*/
public String interpolate(String input, RecursionInterceptor recursionInterceptor) throws InterpolationException {
return interpolate(input, null, recursionInterceptor);
}
public boolean isReusePatterns() {
return reusePatterns;
}
public void setReusePatterns(boolean reusePatterns) {
this.reusePatterns = reusePatterns;
}
public boolean isCacheAnswers() {
return cacheAnswers;
}
public void setCacheAnswers(boolean cacheAnswers) {
this.cacheAnswers = cacheAnswers;
}
public void clearAnswers() {
existingAnswers.clear();
}
}