private String interpolateBundleMessage(
     String message, ResourceBundle bundle, Locale locale, boolean recursive)
     throws MessageDescriptorFormatException {
   TokenCollector tokenCollector = new TokenCollector(message, InterpolationTermType.PARAMETER);
   TokenIterator tokenIterator = new TokenIterator(tokenCollector.getTokenList());
   while (tokenIterator.hasMoreInterpolationTerms()) {
     String term = tokenIterator.nextInterpolationTerm();
     String resolvedParameterValue = resolveParameter(term, bundle, locale, recursive);
     tokenIterator.replaceCurrentInterpolationTerm(resolvedParameterValue);
   }
   return tokenIterator.getInterpolatedMessage();
 }
  /**
   * Runs the message interpolation according to algorithm specified in the Bean Validation
   * specification. <br>
   * Note: <br>
   * Look-ups in user bundles is recursive whereas look-ups in default bundle are not!
   *
   * @param message the message to interpolate
   * @param context the context for this interpolation
   * @param locale the {@code Locale} to use for the resource bundle.
   * @return the interpolated message.
   */
  private String interpolateMessage(String message, Context context, Locale locale)
      throws MessageDescriptorFormatException {
    LocalizedMessage localisedMessage = new LocalizedMessage(message, locale);
    String resolvedMessage = null;

    if (cachingEnabled) {
      resolvedMessage = resolvedMessages.get(localisedMessage);
    }

    // if the message is not already in the cache we have to run step 1-3 of the message resolution
    if (resolvedMessage == null) {
      ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle(locale);
      ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle(locale);

      String userBundleResolvedMessage;
      resolvedMessage = message;
      boolean evaluatedDefaultBundleOnce = false;
      do {
        // search the user bundle recursive (step1)
        userBundleResolvedMessage =
            interpolateBundleMessage(resolvedMessage, userResourceBundle, locale, true);

        // exit condition - we have at least tried to validate against the default bundle and there
        // was no
        // further replacements
        if (evaluatedDefaultBundleOnce
            && !hasReplacementTakenPlace(userBundleResolvedMessage, resolvedMessage)) {
          break;
        }

        // search the default bundle non recursive (step2)
        resolvedMessage =
            interpolateBundleMessage(
                userBundleResolvedMessage, defaultResourceBundle, locale, false);
        evaluatedDefaultBundleOnce = true;
      } while (true);
    }

    // cache resolved message
    if (cachingEnabled) {
      String cachedResolvedMessage =
          resolvedMessages.putIfAbsent(localisedMessage, resolvedMessage);
      if (cachedResolvedMessage != null) {
        resolvedMessage = cachedResolvedMessage;
      }
    }

    // resolve parameter expressions (step 4)
    List<Token> tokens = null;
    if (cachingEnabled) {
      tokens = tokenizedParameterMessages.get(resolvedMessage);
    }
    if (tokens == null) {
      TokenCollector tokenCollector =
          new TokenCollector(resolvedMessage, InterpolationTermType.PARAMETER);
      tokens = tokenCollector.getTokenList();

      if (cachingEnabled) {
        tokenizedParameterMessages.putIfAbsent(resolvedMessage, tokens);
      }
    }
    resolvedMessage = interpolateExpression(new TokenIterator(tokens), context, locale);

    // resolve EL expressions (step 5)
    tokens = null;
    if (cachingEnabled) {
      tokens = tokenizedELMessages.get(resolvedMessage);
    }
    if (tokens == null) {
      TokenCollector tokenCollector = new TokenCollector(resolvedMessage, InterpolationTermType.EL);
      tokens = tokenCollector.getTokenList();

      if (cachingEnabled) {
        tokenizedELMessages.putIfAbsent(resolvedMessage, tokens);
      }
    }
    resolvedMessage = interpolateExpression(new TokenIterator(tokens), context, locale);

    // last but not least we have to take care of escaped literals
    resolvedMessage = replaceEscapedLiterals(resolvedMessage);

    return resolvedMessage;
  }