content: /*
   * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
   * (the "License"). You may not use this work except in compliance with the License, which is
   * available at www.apache.org/licenses/LICENSE-2.0
   *
   * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
   * either express or implied, as more fully set forth in the License.
   *
   * See the NOTICE file distributed with this work for information regarding copyright ownership.
   */
  
  package alluxio.conf;
  
  import java.lang.reflect.Field;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.regex.Matcher;
  import javax.annotation.Nullable;
  
  /**
   * This annotation checker should be used to determine whether a {@link PropertyKey} or
   * {@link alluxio.conf.PropertyKey.Template} has a given annotation.
   *
   * The class is mainly useful to check for {@link Deprecated} annotations on property keys and
   * return the associated message, if any.
   */
  public class DeprecatedKeyChecker {
    private static final Class<PropertyKey.Template> TEMPLATE_CLASS =
        PropertyKey.Template.class;
    private static final Class<PropertyKey> KEY_CLASS = PropertyKey.class;
  
    private Map<PropertyKey, Deprecated> mAnnotatedKeys;
    private Map<PropertyKey.Template, Deprecated> mAnnotatedTemplates;
    private AtomicBoolean mInitialized = new AtomicBoolean(false);
  
    /**
     * Create a new instance of {@link DeprecatedKeyChecker}.
     */
    public DeprecatedKeyChecker() { }
  
    /**
     * Given a class to search, a field type, and an annotation type will return a map of all
     * fields which are marked with the given annotation to the instance of the annotation.
     *
     * @param searchType the class to search through of the given type
     * @param <T> The type of the field to retrieve
     * @return a map of all fields within {@code searchType} class of the type T that are annotated
     *         with the {@link Deprecated} annotation
     */
    private static <T> Map<T, Deprecated> populateAnnotatedKeyMap(Class<T> searchType) {
      Map<T, Deprecated> annotations = new HashMap<>();
      // Iterate over all fields in the class
      for (Field field : searchType.getDeclaredFields()) {
        // If the field isn't equal to the class type, skip it
        if (!field.getType().equals(searchType)) {
          continue;
        }
  
        Deprecated keyAnnotation = field.getAnnotation(Deprecated.class);
  
        try {
          // Field#get parameter can be null if retrieving a static field (all PKs are static)
          // See https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html
          // This also works with Template enums
          T key = searchType.cast(field.get(null));
          if (keyAnnotation != null) {
            annotations.put(key, keyAnnotation);
          }
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }
      }
      return annotations;
    }
  
    private Deprecated getKeyAnnotation(PropertyKey key) {
      if (!mInitialized.get()) {
        initialize();
      }
      if (mAnnotatedKeys.containsKey(key)) {
        return mAnnotatedKeys.get(key);
      } else {
        for (Map.Entry<PropertyKey.Template, Deprecated> e : mAnnotatedTemplates.entrySet()) {
          Matcher match = e.getKey().match(key.getName());
          if (match.matches()) {
            return e.getValue();
          }
        }
      }
      return null;
    }
  
    private void initialize() {
      if (!mInitialized.getAndSet(true)) {
        mAnnotatedKeys =
            populateAnnotatedKeyMap(KEY_CLASS);
        mAnnotatedTemplates =
            populateAnnotatedKeyMap(TEMPLATE_CLASS);
      }
    }
  
    /**
     * Returns whether or not the given property key is marked by this class' annotation.
     *
     * It first checks if the specific key has the annotation, otherwise it will fall back to checking
     * if the key's name matches any of the PropertyKey templates. If no keys or templates match, it
     * will return false. This will only return true when the key is marked with a {@link Deprecated}
     * annotation.
     *
     * @param key the property key to check
     * @return if this property key is deprecated
     * @see Deprecated
     * @see PropertyKey#getDeprecationMessage(PropertyKey)
     */
    public boolean hasAnnotation(PropertyKey key) {
      return getKeyAnnotation(key) != null;
    }
  
    /**
     * Returns the annotation attached to the given property key.
     *
     * @param key the key to get the annotation for
     * @return the annotation if it exists, null otherwise
     */
    @Nullable
    public Deprecated getAnnotation(PropertyKey key) {
      return getKeyAnnotation(key);
    }
  }
  