code: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  package org.apache.skywalking.oap.server.library.util;
  
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Set;
  
  /**
   * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form {@code
   * ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for user-supplied values. <p>
   * Values for substitution can be supplied using a {@link Properties} instance or using a {@link PlaceholderResolver}.
   */
  public enum PropertyPlaceholderHelper {
  
      INSTANCE(
          PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX,
          PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX, PlaceholderConfigurerSupport.DEFAULT_VALUE_SEPARATOR,
          true
      );
  
      private final String placeholderPrefix;
  
      private final String placeholderSuffix;
  
      private final String simplePrefix;
  
      private final String valueSeparator;
  
      private final boolean ignoreUnresolvablePlaceholders;
  
      /**
       * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
       *
       * @param placeholderPrefix              the prefix that denotes the start of a placeholder
       * @param placeholderSuffix              the suffix that denotes the end of a placeholder
       * @param valueSeparator                 the separating character between the placeholder variable and the
       *                                       associated default value, if any
       * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored ({@code
       *                                       true}) or cause an exception ({@code false})
       */
      PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator,
                                boolean ignoreUnresolvablePlaceholders) {
          if (StringUtil.isEmpty(placeholderPrefix) || StringUtil.isEmpty(placeholderSuffix)) {
              throw new UnsupportedOperationException("'placeholderPrefix or placeholderSuffix' must not be null");
          }
  
          final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
  
          wellKnownSimplePrefixes.put("}", "{");
          wellKnownSimplePrefixes.put("]", "[");
          wellKnownSimplePrefixes.put(")", "(");
  
          this.placeholderPrefix = placeholderPrefix;
          this.placeholderSuffix = placeholderSuffix;
          String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
          if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
              this.simplePrefix = simplePrefixForSuffix;
          } else {
              this.simplePrefix = this.placeholderPrefix;
          }
          this.valueSeparator = valueSeparator;
          this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
      }
  
      /**
       * Replaces all placeholders of format {@code ${name}} with the corresponding property from the supplied {@link
       * Properties}.
       *
       * @param value      the value containing the placeholders to be replaced
       * @param properties the {@code Properties} to use for replacement
       * @return the supplied value with placeholders replaced inline
       */
      public String replacePlaceholders(String value, final Properties properties) {
          return replacePlaceholders(value,
                                     placeholderName -> getConfigValue(placeholderName, properties)
          );
      }
  
      private String getConfigValue(String key, final Properties properties) {
          String value = System.getProperty(key);
          if (value == null) {
              value = System.getenv(key);
          }
          if (value == null) {
              value = properties.getProperty(key);
          }
          return value;
      }
  
      /**
       * Replaces all placeholders of format {@code ${name}} with the value returned from the supplied {@link
       * PlaceholderResolver}.
       *
       * @param value               the value containing the placeholders to be replaced
       * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
       * @return the supplied value with placeholders replaced inline
       */
      public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
          return parseStringValue(value, placeholderResolver, new HashSet<String>());
      }
  
      protected String parseStringValue(String value, PlaceholderResolver placeholderResolver,
                                        Set<String> visitedPlaceholders) {
  
          StringBuilder result = new StringBuilder(value);
  
          int startIndex = value.indexOf(this.placeholderPrefix);
          while (startIndex != -1) {
              int endIndex = findPlaceholderEndIndex(result, startIndex);
              if (endIndex != -1) {
                  String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                  String originalPlaceholder = placeholder;
                  if (!visitedPlaceholders.add(originalPlaceholder)) {
                      throw new IllegalArgumentException(
                          "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                  }
                  // Recursive invocation, parsing placeholders contained in the placeholder key.
                  placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                  // Now obtain the value for the fully resolved key...
                  String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                  if (propVal == null && this.valueSeparator != null) {
                      int separatorIndex = placeholder.indexOf(this.valueSeparator);
                      if (separatorIndex != -1) {
                          String actualPlaceholder = placeholder.substring(0, separatorIndex);
                          String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                          propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                          if (propVal == null) {
                              propVal = defaultValue;
                          }
                      }
                  }
                  if (propVal != null) {
                      // Recursive invocation, parsing placeholders contained in the
                      // previously resolved placeholder value.
                      propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                      result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                      startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                  } else if (this.ignoreUnresolvablePlaceholders) {
                      // Proceed with unprocessed value.
                      startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                  } else {
                      throw new IllegalArgumentException(
                          "Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\"");
                  }
                  visitedPlaceholders.remove(originalPlaceholder);
              } else {
                  startIndex = -1;
              }
          }
          return result.toString();
      }
  
      private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
          int index = startIndex + this.placeholderPrefix.length();
          int withinNestedPlaceholder = 0;
          while (index < buf.length()) {
              if (StringUtil.substringMatch(buf, index, this.placeholderSuffix)) {
                  if (withinNestedPlaceholder > 0) {
                      withinNestedPlaceholder--;
                      index = index + this.placeholderSuffix.length();
                  } else {
                      return index;
                  }
              } else if (StringUtil.substringMatch(buf, index, this.simplePrefix)) {
                  withinNestedPlaceholder++;
                  index = index + this.simplePrefix.length();
              } else {
                  index++;
              }
          }
          return -1;
      }
  
      /**
       * Strategy interface used to resolve replacement values for placeholders contained in Strings.
       */
      public interface PlaceholderResolver {
  
          /**
           * Resolve the supplied placeholder name to the replacement value.
           *
           * @param placeholderName the name of the placeholder to resolve
           * @return the replacement value, or {@code null} if no replacement is to be made
           */
          String resolvePlaceholder(String placeholderName);
      }
  }
  