
scriptella.expression.PropertiesSubstitutor Maven / Gradle / Ivy
/*
* Copyright 2006-2012 The Scriptella Project Team.
*
* 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.
*/
package scriptella.expression;
import scriptella.spi.ParametersCallback;
import scriptella.spi.support.MapParametersCallback;
import scriptella.util.IOUtils;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Substitutes properties(or expressions) in strings.
* $ symbol indicate property or expression to evaluate and substitute.
*
The following properties/expression syntax is used:
*
Property reference
* References named property.
*
Examples:
*
* $foo
*
* Expression
.
* Expression is wrapped by braces and evaluated by {@link Expression} engine.
* Examples:
*
* ${name+' '+surname} etc.
*
*
* This class is not thread safe
*
* @author Fyodor Kupolov
* @version 1.0
*/
public class PropertiesSubstitutor {
/**
* Simple property patterns, e.g. $property
*/
public static final Pattern PROP_PTR = Pattern.compile("([a-zA-Z_0-9\\.]+)");
/**
* Expression pattern, e.g. ${property} etc.
*/
public static final Pattern EXPR_PTR = Pattern.compile("\\{([^\\}]+)\\}");
final Matcher m1 = PROP_PTR.matcher("");
final Matcher m2 = EXPR_PTR.matcher("");
private ParametersCallback parameters;
private String nullString;
/**
* Creates a properties substitutor.
*
This constructor is used for performance critical places where multiple instantiation
* via {@link #PropertiesSubstitutor(scriptella.spi.ParametersCallback)} is expensive.
*
Note: {@link #setParameters(scriptella.spi.ParametersCallback)} must be called before
* {@link #substitute(String)}.
*/
public PropertiesSubstitutor() {
}
/**
* Creates a properties substitutor.
*
* @param parameters parameters callback to use for substitution.
*/
public PropertiesSubstitutor(ParametersCallback parameters) {
this.parameters = parameters;
}
/**
* Creates a properties substitutor based on specified properties map.
*
* @param map parameters to substitute.
*/
public PropertiesSubstitutor(Map map) {
this(new MapParametersCallback(map));
}
/**
* Substitutes properties/expressions in s and returns the result string.
* If result of evaluation is null or the property being substitued doesn't have value in callback - the whole
* expressions is copied into result string as is.
*
* @param s string to substitute. Null strings allowed.
* @return substituted string.
*/
public String substitute(final String s) {
if (parameters == null) {
throw new IllegalStateException("setParameters must be called before calling substitute");
}
int i = firstCandidate(s); //Remember the first index of $
if (i < 0) { //skip strings without $ char, or when the $ is the last char
return s;
}
final int len = s.length() - 1; //Last character is not checked - optimization
StringBuilder res = null;
int lastPos = 0;
m1.reset(s);
m2.reset(s);
for (; i >= 0 && i < len; i = s.indexOf('$', i + 1)) {
//Start of expression
Matcher m;
if (m1.find(i + 1) && m1.start() == i + 1) {
m = m1;
} else if (m2.find(i + 1) && m2.start() == i + 1) {
m = m2;
} else { //not an expression
m = null;
}
if (m != null) {
final String name = m.group(1);
String v;
if (m == m1) {
v = toString(parameters.getParameter(name));
} else {
v = toString(Expression.compile(name).evaluate(parameters));
}
if (v != null) {
if (res == null) {
res = new StringBuilder(s.length());
}
if (i > lastPos) { //if we have unflushed character
res.append(s.substring(lastPos, i));
}
lastPos = m.end();
res.append(v);
}
}
}
if (res == null) {
return s;
}
if (lastPos <= len) {
res.append(s.substring(lastPos, s.length()));
}
return res.toString();
}
/**
* Copies content from reader to writer and expands properties.
*
* @param reader reader to process.
* @param writer writer to output substituted content to.
* @throws IOException if I/O error occurs.
*/
public void substitute(final Reader reader, final Writer writer) throws IOException {
//Current implementation is too simple,
// we need to provide a better implementation for stream based content.
writer.write(substitute(IOUtils.toString(reader)));
}
/**
* Reads content from reader and expands properties.
*
Note: For performance reasons use
* {@link #substitute(java.io.Reader,java.io.Writer)} if possible.
*
* @param reader reader to process.
* @return reader's content with properties expanded.
* @throws IOException if I/O error occurs.
* @see #substitute(java.io.Reader,java.io.Writer)
*/
public String substitute(final Reader reader) throws IOException {
//Current implementation is too simple,
// we need to provide a better implementation for stream based content.
return substitute(IOUtils.toString(reader));
}
/**
* @return parameter callback used for substitution.
*/
public ParametersCallback getParameters() {
return parameters;
}
/**
* Sets parameters callback used for substitution.
*
* @param parameters not null parameters callback.
*/
public void setParameters(ParametersCallback parameters) {
this.parameters = parameters;
}
/**
* Returns string literal representing null value.
*
Used when converting objects {@link #toString(Object)}.
*
* @return string representing null value.
*/
public String getNullString() {
return nullString;
}
/**
* Sets string literal representing null.
*
Used when converting objects {@link #toString(Object)}.
*
* @param nullString string literal representing null
*/
public void setNullString(String nullString) {
this.nullString = nullString;
}
/**
* Converts specified object to string.
*
{@link #getNullString()} represents null values.
*
Subclasses may provide custom conversion strategy here.
*
* @param o object to convert to String.
* @return string representation of object.
*/
protected String toString(final Object o) {
return o == null ? nullString : o.toString();
}
/**
* Tests if the given string contains properties/expressions.
*
* @param string string to check.
* @return true if a given string contains properties/expressions.
*/
public static boolean hasProperties(String string) {
return firstCandidate(string) >= 0;
}
static int firstCandidate(String string) {
if (string == null) {
return -1;
}
int n = string.length();
if (n < 2) {
return -1;
}
return string.indexOf('$');
}
}