apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java/PlaceholderHelper/extractPlaceholderKeys
class PlaceholderHelper:  public Set<String> extractPlaceholderKeys(String propertyString) {
    Set<String> placeholderKeys = Sets.newHashSet();

    if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) {
      return placeholderKeys;
    }

    Stack<String> stack = new Stack<>();
    stack.push(propertyString);

    while (!stack.isEmpty()) {
      String strVal = stack.pop();
      int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
      if (startIndex == -1) {
        placeholderKeys.add(strVal);
        continue;
      }
      int endIndex = findPlaceholderEndIndex(strVal, startIndex);
      if (endIndex == -1) {
        // invalid placeholder?
        continue;
      }

      String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);

      // ${some.key:other.key}
      if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
        stack.push(placeholderCandidate);
      } else {
        // some.key:${some.other.key:100}
        int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);

        if (separatorIndex == -1) {
          stack.push(placeholderCandidate);
        } else {
          stack.push(placeholderCandidate.substring(0, separatorIndex));
          String defaultValuePart =
              normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
          if (!Strings.isNullOrEmpty(defaultValuePart)) {
            stack.push(defaultValuePart);
          }
        }
      }

      // has remaining part, e.g. ${a}.${b}
      if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
        String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
        if (!Strings.isNullOrEmpty(remainingPart)) {
          stack.push(remainingPart);
        }
      }
    }

    return placeholderKeys;
  }