/** * Replaces all the occurrences of variables with their matching values from the resolver using * the given source builder as a template. The builder is not altered by this method. * * @param source the builder to use as a template, not changed, null returns null * @return the result of the replace operation */ public String replace(final StrBuilder source) { if (source == null) { return null; } final StrBuilder buf = new StrBuilder(source.length()).append(source); substitute(buf, 0, buf.length()); return buf.toString(); }
/** * Recursive handler for multiple levels of interpolation. This is the main interpolation method, * which resolves the values of all variable references contained in the passed in text. * * @param buf the string builder to substitute into, not null * @param offset the start offset within the builder, must be valid * @param length the length within the builder to be processed, must be valid * @param priorVariables the stack keeping track of the replaced variables, may be null * @return the length change that occurs, unless priorVariables is null when the int represents a * boolean flag as to whether any change occurred. */ private int substitute( final StrBuilder buf, final int offset, final int length, List<String> priorVariables) { final StrMatcher pfxMatcher = getVariablePrefixMatcher(); final StrMatcher suffMatcher = getVariableSuffixMatcher(); final char escape = getEscapeChar(); final StrMatcher valueDelimMatcher = getValueDelimiterMatcher(); final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); final boolean top = priorVariables == null; boolean altered = false; int lengthChange = 0; char[] chars = buf.buffer; int bufEnd = offset + length; int pos = offset; while (pos < bufEnd) { final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd); if (startMatchLen == 0) { pos++; } else { // found variable start marker if (pos > offset && chars[pos - 1] == escape) { // escaped buf.deleteCharAt(pos - 1); chars = buf.buffer; // in case buffer was altered lengthChange--; altered = true; bufEnd--; } else { // find suffix final int startPos = pos; pos += startMatchLen; int endMatchLen = 0; int nestedVarCount = 0; while (pos < bufEnd) { if (substitutionInVariablesEnabled && (endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) { // found a nested variable start nestedVarCount++; pos += endMatchLen; continue; } endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd); if (endMatchLen == 0) { pos++; } else { // found variable end marker if (nestedVarCount == 0) { String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); if (substitutionInVariablesEnabled) { final StrBuilder bufName = new StrBuilder(varNameExpr); substitute(bufName, 0, bufName.length()); varNameExpr = bufName.toString(); } pos += endMatchLen; final int endPos = pos; String varName = varNameExpr; String varDefaultValue = null; if (valueDelimMatcher != null) { final char[] varNameExprChars = varNameExpr.toCharArray(); int valueDelimiterMatchLen = 0; for (int i = 0; i < varNameExprChars.length; i++) { // if there's any nested variable when nested variable substitution disabled, // then stop resolving name and default value. if (!substitutionInVariablesEnabled && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { break; } if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) { varName = varNameExpr.substring(0, i); varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); break; } } } // on the first call initialize priorVariables if (priorVariables == null) { priorVariables = new ArrayList<String>(); priorVariables.add(new String(chars, offset, length)); } // handle cyclic substitution checkCyclicSubstitution(varName, priorVariables); priorVariables.add(varName); // resolve the variable String varValue = resolveVariable(varName, buf, startPos, endPos); if (varValue == null) { varValue = varDefaultValue; } if (varValue != null) { // recursive replace final 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; // in case buffer was // altered } // remove variable from the cyclic stack priorVariables.remove(priorVariables.size() - 1); break; } nestedVarCount--; pos += endMatchLen; } } } } } if (top) { return altered ? 1 : 0; } return lengthChange; }
/** * Replaces all the occurrences of variables within the given source builder with their matching * values from the resolver. * * @param source the builder to replace in, updated, null returns zero * @return true if altered */ public boolean replaceIn(final StrBuilder source) { if (source == null) { return false; } return substitute(source, 0, source.length()); }