/**
  * 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());
 }