package org.apache.commons.lang3.text; import java.util.ArrayList; import java.util.List; import java.util.Map; public class StrSubstitutor { public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); private char escapeChar; private StrMatcher prefixMatcher; private StrMatcher suffixMatcher; private StrLookup<?> variableResolver; private boolean enableSubstitutionInVariables; public StrSubstitutor() { this((StrLookup)null, DEFAULT_PREFIX, DEFAULT_SUFFIX, '$'); } public <V> StrSubstitutor(Map<String, V> valueMap) { this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, '$'); } public StrSubstitutor(StrLookup<?> variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher, char escape) { setVariableResolver(variableResolver); setVariablePrefixMatcher(prefixMatcher); setVariableSuffixMatcher(suffixMatcher); setEscapeChar(escape); } public String replace(String source) { if (source == null) { return null; } StrBuilder buf = new StrBuilder(source); if (!substitute(buf, 0, source.length())) { return source; } return buf.toString(); } protected boolean substitute(StrBuilder buf, int offset, int length) { return substitute(buf, offset, length, null) > 0; } private int substitute(StrBuilder buf, int offset, int length, List<String> priorVariables) { StrMatcher prefixMatcher = getVariablePrefixMatcher(); StrMatcher suffixMatcher = getVariableSuffixMatcher(); char escape = getEscapeChar(); boolean top = priorVariables == null; boolean altered = false; int lengthChange = 0; char[] chars = buf.buffer; int bufEnd = offset + length; int pos = offset; while (pos < bufEnd) { int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); if (startMatchLen == 0) { pos++; } else if ((pos > offset) && (chars[(pos - 1)] == escape)) { buf.deleteCharAt(pos - 1); chars = buf.buffer; lengthChange--; altered = true; bufEnd--; } else { int startPos = pos; pos += startMatchLen; int endMatchLen = 0; int nestedVarCount = 0; while (pos < bufEnd) { if ((isEnableSubstitutionInVariables()) && ((endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0)) { nestedVarCount++; pos += endMatchLen; } else { endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); if (endMatchLen == 0) { pos++; } else { if (nestedVarCount == 0) { String varName = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); if (isEnableSubstitutionInVariables()) { StrBuilder bufName = new StrBuilder(varName); substitute(bufName, 0, bufName.length()); varName = bufName.toString(); } pos += endMatchLen; int endPos = pos; if (priorVariables == null) { priorVariables = new ArrayList(); priorVariables.add(new String(chars, offset, length)); } checkCyclicSubstitution(varName, priorVariables); priorVariables.add(varName); String varValue = resolveVariable(varName, buf, startPos, endPos); if (varValue != null) { int varLen = varValue.length(); buf.replace(startPos, endPos, varValue); altered = true; int change = substitute(buf, startPos, varLen, priorVariables); change = change + varLen - (endPos - startPos); pos += change; bufEnd += change; lengthChange += change; chars = buf.buffer; } priorVariables.remove(priorVariables.size() - 1); break; } nestedVarCount--; pos += endMatchLen; } } } } } if (top) { return altered ? 1 : 0; } return lengthChange; } private void checkCyclicSubstitution(String varName, List<String> priorVariables) { if (!priorVariables.contains(varName)) { return; } StrBuilder buf = new StrBuilder(256); buf.append("Infinite loop in property interpolation of "); buf.append((String)priorVariables.remove(0)); buf.append(": "); buf.appendWithSeparators(priorVariables, "->"); throw new IllegalStateException(buf.toString()); } protected String resolveVariable(String variableName, StrBuilder buf, int startPos, int endPos) { StrLookup resolver = getVariableResolver(); if (resolver == null) { return null; } return resolver.lookup(variableName); } public char getEscapeChar() { return this.escapeChar; } public void setEscapeChar(char escapeCharacter) { this.escapeChar = escapeCharacter; } public StrMatcher getVariablePrefixMatcher() { return this.prefixMatcher; } public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) { if (prefixMatcher == null) { throw new IllegalArgumentException("Variable prefix matcher must not be null!"); } this.prefixMatcher = prefixMatcher; return this; } public StrMatcher getVariableSuffixMatcher() { return this.suffixMatcher; } public StrSubstitutor setVariableSuffixMatcher(StrMatcher suffixMatcher) { if (suffixMatcher == null) { throw new IllegalArgumentException("Variable suffix matcher must not be null!"); } this.suffixMatcher = suffixMatcher; return this; } public StrLookup<?> getVariableResolver() { return this.variableResolver; } public void setVariableResolver(StrLookup<?> variableResolver) { this.variableResolver = variableResolver; } public boolean isEnableSubstitutionInVariables() { return this.enableSubstitutionInVariables; } }