content: /*
   *  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.causeway.commons.internal.reflection;
  
  import java.lang.annotation.Annotation;
  import java.lang.annotation.ElementType;
  import java.lang.annotation.Inherited;
  import java.lang.annotation.Target;
  import java.lang.reflect.AnnotatedElement;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.Optional;
  
  import org.springframework.core.annotation.MergedAnnotations;
  import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
  
  import lombok.AccessLevel;
  import lombok.NoArgsConstructor;
  
  /**
   * <h1>- internal use only -</h1>
   * <p>
   * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
   * These may be changed or removed without notice!
   * </p>
   * @since 2.0
   */
  @NoArgsConstructor(access = AccessLevel.PRIVATE)
  public final class _Annotations {
  
      /**
       * Determine if the specified annotation is either directly present or meta-present.
       * <p>
       * Also includes annotated fields, getter methods might be associated with.
       * If annotations from a getter method are competing with annotations from its corresponding field,
       * let the one win, that is 'nearer' to the <i>Class</i> that is subject to introspection.
       * <p>
       * Perform a full search of the entire type hierarchy,
       * including super-classes and implemented interfaces.
       * Super-class annotations do not need to be meta-annotated with {@link Inherited}.
       *
       * @param <A>
       * @param annotatedElement
       * @param annotationType
       * @return non-null
       */
      public static <A extends Annotation> boolean isPresent(
              final AnnotatedElement annotatedElement,
              final Class<A> annotationType) {
  
          var collected = collect(annotatedElement, SearchStrategy.TYPE_HIERARCHY);
  
          if(collected.isPresent(annotationType)) {
              return true;
          }
  
          // also handle annotated fields, getter methods might be associated with
          return annotatedFieldForAnnotatedElement(annotatedElement, annotationType)
          .map(fieldForGetter->isPresent(fieldForGetter, annotationType))
          .orElse(false);
      }
  
      /**
       * Optionally create a type-safe synthesized version of this annotation based on presence.
       * <p>
       * Also includes annotated fields, getter methods might be associated with.
       * If annotations from a getter method are competing with annotations from its corresponding field,
       * let the one win, that is 'nearer' to the <i>Class</i> that is subject to introspection.
       * <p>
       * Perform a full search of the entire type hierarchy,
       * including super-classes and implemented interfaces.
       * Super-class annotations do not need to be meta-annotated with {@link Inherited}.
       *
       * @param <A>
       * @param annotatedElement
       * @param annotationType
       * @return non-null
       */
      public static <A extends Annotation> Optional<A> synthesize(
              final AnnotatedElement annotatedElement,
              final Class<A> annotationType) {
  
          var collected = collect(annotatedElement, SearchStrategy.TYPE_HIERARCHY);
  
          // also handle annotated fields, getter methods might be associated with
          var associated =
                  annotatedFieldForAnnotatedElement(annotatedElement, annotationType)
                          .map(fieldForGetter->collect(fieldForGetter, SearchStrategy.TYPE_HIERARCHY));
  
          return _Annotations_SynthesizedMergedAnnotationInvocationHandler
                  .createProxy(collected, associated, annotationType);
      }
  
      /**
       * Optionally create a type-safe synthesized version of this annotation based on presence.
       * <p>
       * Perform a full search of the entire type hierarchy,
       * including super-classes and implemented interfaces.p
       * Super-class annotations do not need to be meta-annotated with {@link Inherited}.
       *
       * @param <A>
       * @param annotatedMethod
       * @param annotationType
       * @return non-null
       */
      public static <A extends Annotation> Optional<A> synthesizeConsideringClass(
              final Method annotatedMethod,
              final Class<A> annotationType) {
  
          var collected = collect(annotatedMethod, SearchStrategy.TYPE_HIERARCHY);
  
          // also handle containing class
          var associatedClass = collect(annotatedMethod.getDeclaringClass(), SearchStrategy.TYPE_HIERARCHY);
  
          return _Annotations_SynthesizedMergedAnnotationInvocationHandler
                  .createProxy(collected, Optional.of(associatedClass), annotationType);
      }
  
      /**
       * Optionally create a type-safe synthesized version of this annotation based on presence.
       * <p>
       * Also includes annotated fields, getter methods might be associated with.
       * If annotations from a getter method are competing with annotations from its corresponding field,
       * let the one win, that is 'nearer' to the <i>Class</i> that is subject to introspection.
       * <p>
       * Find only directly declared annotations,
       * without considering {@link Inherited} annotations and
       * without searching super-classes or implemented interfaces.
       *
       * @param <A>
       * @param annotatedElement
       * @param annotationType
       * @return non-null
       */
      public static <A extends Annotation> Optional<A> synthesizeDirect(
              final AnnotatedElement annotatedElement,
              final Class<A> annotationType) {
  
          var collected = collect(annotatedElement, SearchStrategy.DIRECT);
  
          // also handle annotated fields, getter methods might be associated with
          var associated =
                  annotatedFieldForAnnotatedElement(annotatedElement, annotationType)
                          .map(fieldForGetter->collect(fieldForGetter, SearchStrategy.DIRECT));
  
          return _Annotations_SynthesizedMergedAnnotationInvocationHandler
                  .createProxy(collected, associated, annotationType);
      }
  
      // -- HELPER
  
      /* XXX no longer used
      / **
       * Optionally create a type-safe synthesized version of this annotation based on presence.
       * <p>
       * Also includes annotated fields, getter methods might be associated with.
       * If annotations from a getter method are competing with annotations from its corresponding field,
       * let the one win, that is 'nearer' to the <i>Class</i> that is subject to introspection.
       * /
      private static <A extends Annotation> Optional<A> synthesize(
              final AnnotatedElement annotatedElement,
              final Class<A> annotationType,
              final SearchStrategy searchStrategy) {
  
          var collected = collect(annotatedElement, searchStrategy);
  
          // also handle annotated fields, getter methods might be associated with
          var associated =
                  annotatedFieldForAnnotatedElement(annotatedElement, annotationType)
                          .map(fieldForGetter->collect(fieldForGetter, searchStrategy));
  
          return _Annotations_SynthesizedMergedAnnotationInvocationHandler
                  .createProxy(collected, associated, annotationType);
      }*/
  
      /**
       * @apiNote don't publicly expose Spring's {@link MergedAnnotations}
       */
      private static MergedAnnotations collect(
              final AnnotatedElement annotatedElement,
              final SearchStrategy searchStrategy) {
          var collected = MergedAnnotations.from(annotatedElement, searchStrategy);
          return collected;
      }
  
      private static boolean isAnnotationAllowedOnField(final Class<? extends Annotation> annotationType) {
          var target = annotationType.getAnnotation(Target.class);
          if(target==null) {
              return false;
          }
          for(var elementType : target.value()) {
              if(elementType == ElementType.FIELD) {
                  return true;
              }
          }
          return false;
      }
  
      private static <A extends Annotation> Optional<Field> annotatedFieldForAnnotatedElement(
              final AnnotatedElement annotatedElement,
              final Class<A> annotationType){
  
          return annotatedElement instanceof Method
                  ? annotatedFieldForGetter((Method)annotatedElement, annotationType)
                  : Optional.empty();
      }
  
      private static <A extends Annotation> Optional<Field> annotatedFieldForGetter(
              final Method method,
              final Class<A> annotationType){
  
          return (method.getName().startsWith("get")
                      || method.getName().startsWith("is"))
                  && isAnnotationAllowedOnField(annotationType)
                  ? _Reflect.fieldForGetter(method)
                  : Optional.empty();
      }
  
  }
  