content: package net.anotheria.moskito.core.util.annotation;
  
  import java.lang.annotation.Annotation;
  import java.lang.reflect.AnnotatedElement;
  import java.util.HashSet;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * Class contains utility methods with annotations
   *
   * @author bvanchuhov
   */
  public final class AnnotationUtils {
  
  	private AnnotationUtils() {
  		throw new IllegalAccessError("Can't be instantiated");
  	}
  
  	/**
  	 * Deep search of specified annotation considering "annotation as meta annotation" case (annotation annotated with specified annotation).
  	 *
  	 * @param source
  	 * 		specified annotated element
  	 * @param targetAnnotationClass
  	 * 		specified annotation class
  	 * @param <A>
  	 * 		the type of the annotation to query for and return if present
  	 * @return specified element's annotation for the specified annotation type if deeply present on this element, else null
  	 */
  	public static <A extends Annotation> A findAnnotation(final AnnotatedElement source, final Class<A> targetAnnotationClass) {
  		Objects.requireNonNull(source, "incoming 'source' is not valid");
  		Objects.requireNonNull(targetAnnotationClass, "incoming 'targetAnnotationClass' is not valid");
  
  		final A result = source.getAnnotation(targetAnnotationClass);
  		if (result != null)
  			return result;
  
  		return findAnnotationInAnnotations(source, targetAnnotationClass);
  	}
  
  	/**
  	 * Tries to find required annotation in scope of all annotations of incoming 'source'.
  	 *
  	 * @param source
  	 * 		{@link AnnotatedElement}
  	 * @param targetAnnotationClass
  	 * 		{@link Class} - actually required annotation class
  	 * @param <A>
  	 * 		- type param
  	 * @return {@link A} in case if found, {@code null} otherwise
  	 */
  	private static <A extends Annotation> A findAnnotationInAnnotations(final AnnotatedElement source, final Class<A> targetAnnotationClass) {
  		final Annotation[] allAnnotations = source.getAnnotations();
  		for (final Annotation annotation : allAnnotations) {
  			final A result = findAnnotation(annotation, targetAnnotationClass);
  			if (result != null)
  				return result;
  
  		}
  		return null;
  	}
  
  	/**
  	 * Deep search of specified source considering "annotation as meta annotation" case (source annotated with specified source).
  	 *
  	 * @param source
  	 * 		{@link Annotation} - source
  	 * @param targetAnnotationClass
  	 * 		represents class of the required annotaiton
  	 * @param <A>
  	 * 		type param
  	 * @return {@link A} in case if found, {@code null} otherwise
  	 */
  	public static <A extends Annotation> A findAnnotation(final Annotation source, final Class<A> targetAnnotationClass) {
  		Objects.requireNonNull(source, "incoming 'source' is not valid");
  		Objects.requireNonNull(targetAnnotationClass, "incoming 'targetAnnotationClass' is not valid");
  		return findAnnotation(source, targetAnnotationClass, new HashSet<Class<? extends Annotation>>());
  	}
  
  	/**
  	 * Recursive annotation search, with check in all assigned {@link ElementType#ANNOTATION_TYPE} - annotations.
  	 *
  	 * @param sourceAnnotation
  	 * 		incoming / source {@link Annotation}
  	 * @param targetAnnotationClass
  	 * 		target / required annotation class
  	 * @param alreadyCheckedAnnotations
  	 * 		annotations which were already checked
  	 * @param <A>
  	 * 		type param
  	 * @return * @return {@link A} in case if found, {@code null} otherwise
  	 */
  	private static <A extends Annotation> A findAnnotation(final Annotation sourceAnnotation,
  														   final Class<A> targetAnnotationClass, final Set<Class<? extends Annotation>> alreadyCheckedAnnotations) {
  
  		final Class<? extends Annotation> currentAnnotationType = sourceAnnotation.annotationType();
  		alreadyCheckedAnnotations.add(currentAnnotationType);
  
  		final A result = currentAnnotationType.getAnnotation(targetAnnotationClass);
  		if (result != null)
  			return result;
  
  		final Annotation[] allAnnotations = currentAnnotationType.getAnnotations();
  		for (final Annotation annotation : allAnnotations) {
  			final Class<? extends Annotation> annotationType = annotation.annotationType();
  			if (alreadyCheckedAnnotations.contains(annotationType))
  				continue;
  			final A found = findAnnotation(annotation, targetAnnotationClass, alreadyCheckedAnnotations);
  			if (found != null)
  				return found;
  		}
  
  		return null;
  	}
  
  	/**
  	 * Recursive annotation search on type, it annotations and parents hierarchy. Interfaces ignored.
  	 *
  	 * @param type
  	 * 		{@link Class}
  	 * @param targetAnnotationClass
  	 * 		required {@link Annotation} class
  	 * @param <A>
  	 * 		type param
  	 * @return {@link A} in case if found, {@code null} otherwise
  	 */
  	public static <A extends Annotation> A findTypeAnnotation(final Class<?> type, final Class<A> targetAnnotationClass) {
  		Objects.requireNonNull(type, "incoming 'type' is not valid");
  		Objects.requireNonNull(targetAnnotationClass, "incoming 'targetAnnotationClass' is not valid");
  
  		if (type.equals(Object.class))
  			return null;
  		//check annotation presence on class itself and its annotations if any
  		final A annotation = findAnnotation(type, targetAnnotationClass);
  		if (annotation != null)
  			return annotation;
  		//recursive call to check superclass if present
  		final Class<?> superClass = type.getSuperclass();
  		if (superClass != null)
  			return findTypeAnnotation(superClass, targetAnnotationClass);
  		return null;
  	}
  }
  
  