001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.runtime.util;
017
018import java.lang.annotation.Annotation;
019import java.lang.annotation.Inherited;
020import java.util.function.BiFunction;
021import java.util.function.Function;
022import java.util.stream.Collectors;
023import java.util.stream.Stream;
024
025import org.apache.commons.lang3.tuple.Pair;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Helper for {@link Annotation}s.
031 */
032public final class Annotations
033{
034    private static final Logger __LOGGER = LoggerFactory.getLogger(Annotations.class);
035    
036    private Annotations()
037    {
038        // empty constructor
039    }
040    
041    /**
042     * Tests if given {@link Annotation} is present on any of the superclasses or on any of the implemented interfaces.
043     * <br>Unlike {@link Class#isAnnotationPresent}, it also checks into all the implemented interfaces.
044     * <br> Thus, the meta-annotation {@link Inherited} has no effect because whether or not it is present, the whole hierarchy is traversed here anyway.
045     * @param classToTest The class to test
046     * @param annotationClass The {@link Annotation} class
047     * @return <code>true</code> if annotation is present on any of the superclasses or on any of the implemented interfaces.
048     */
049    public static boolean isAnnotationPresent(Class<?> classToTest, Class<? extends Annotation> annotationClass)
050    {
051        if (__LOGGER.isDebugEnabled())
052        {
053            __LOGGER.debug("[BEGIN] Test if annotation {} is present for {}", annotationClass.getName(), classToTest.getName());
054        }
055        try
056        {
057            return _isAnnotationPresent(classToTest, annotationClass) || _isAnnotationPresentOnInterfacesOfSuperclasses(classToTest, annotationClass);
058        }
059        finally
060        {
061            if (__LOGGER.isDebugEnabled())
062            {
063                __LOGGER.debug("[END] Test if annotation {} is present for {} done", annotationClass.getName(), classToTest.getName());
064            }
065        }
066    }
067    
068    private static boolean _isAnnotationPresent(Class<?> classToTest, Class<? extends Annotation> annotationClass)
069    {
070        boolean result = classToTest.isAnnotationPresent(annotationClass);
071        if (result && __LOGGER.isDebugEnabled())
072        {
073            String additionalText = classToTest.isInterface() ? "" : "(or on one of its ancestors)";
074            __LOGGER.debug("[FOUND] annotation {} is present on {} {}", annotationClass.getName(), classToTest.getName(), additionalText);
075        }
076        return result;
077    }
078    
079    private static boolean _isAnnotationPresentOnInterfacesOfSuperclasses(Class<?> classToTest, Class<? extends Annotation> annotationClass)
080    {
081        return _getSuperclassesAndSelf(classToTest)
082                .anyMatch(superclass -> _isAnnotationPresentOnInterfaces(superclass, annotationClass));
083    }
084    
085    private static Stream<Class<?>> _getSuperclassesAndSelf(Class<?> classToTest)
086    {
087        if (classToTest == null 
088            || classToTest == Object.class /* optimization */)
089        {
090            return Stream.empty();
091        }
092        else
093        {
094            __LOGGER.debug("found class {}", classToTest.getName());
095            return Stream.concat(
096                    Stream.of(classToTest), 
097                    _getSuperclassesAndSelf(classToTest.getSuperclass()));
098        }
099    }
100    
101    private static boolean _isAnnotationPresentOnInterfaces(Class<?> classToTest, Class<? extends Annotation> annotationClass)
102    {
103        return _getAncestorInterfacesAndSelf(classToTest)
104                .anyMatch(theInterface -> _isAnnotationPresent(theInterface, annotationClass));
105    }
106    
107    private static Stream<Class<?>> _getAncestorInterfacesAndSelf(Class<?> classToTest)
108    {
109        return Stream.concat(
110                Stream.of(classToTest), 
111                _getAncestorInterfaces(classToTest));
112    }
113    
114    private static Stream<Class<?>> _getAncestorInterfaces(Class<?> classToTest)
115    {
116        return _getInterfaces(classToTest)
117                .map(Annotations::_getAncestorInterfacesAndSelf)
118                .flatMap(Function.identity());
119    }
120    
121    private static Stream<Class<?>> _getInterfaces(Class<?> classToTest)
122    {
123        Class< ? >[] interfaces = classToTest.getInterfaces();
124        if (__LOGGER.isDebugEnabled())
125        {
126            __LOGGER.debug("found interfaces {}", Stream.of(interfaces)
127                    .map(Class::getName)
128                    .collect(Collectors.toList()));
129        }
130        return Stream.of(interfaces);
131    }
132    
133    /**
134     * Gets the {@link Annotation}s on any of the superclasses or on any of the implemented interfaces from the given class to test, and then apply
135     * a {@link BiFunction bifunction} on it (generally to obtain attributes from the {@link Annotation} instance).
136     * <br>Unlike {@link Class#getAnnotation}, it also checks into all the implemented interfaces.
137     * <br> Whereas the whole hierarchy is traversed, the meta-annotation {@link Inherited} can have some effects depending on
138     * the operations you process on the result {@link Stream}, as the same annotation instance will be provided to the {@link BiFunction}
139     * with different classes (which are related, one being the superclass of all the others).
140     * @param <A> The runtime type of the {@link Annotation}
141     * @param <T> The type of results to get
142     * @param classToTest The class to test
143     * @param annotationClass The {@link Annotation} class
144     * @param annotationAttributeGetter The {@link BiFunction two-args function} to apply to get the final results. The two arguments
145     * represent the {@link Annotation} instance and the class which is annotated (be careful if the {@link Annotation} class has the meta-annotation {@link Inherited}).
146     * @return a {@link Stream} of T elements returned by the {@link BiFunction bifunction} applied on {@link Annotation}s instances 
147     * on any of the superclasses or on any of the implemented interfaces or on the provided class itself.
148     */
149    public static <A extends Annotation, T> Stream<T> getAnnotationAttributeValues(
150            Class<?> classToTest, 
151            Class<A> annotationClass, 
152            BiFunction<A, Class<?>, T> annotationAttributeGetter)
153    {
154        return _getSuperclassesAndSelf(classToTest)
155                .flatMap(Annotations::_getAncestorInterfacesAndSelf)
156                .map(classOrInterface -> Pair.of(classOrInterface.getAnnotation(annotationClass), classOrInterface))
157                .filter(annotationAndClass -> annotationAndClass.getLeft() != null) // annotation must be present
158                .map(annotationAndClass -> _logAndGet(annotationAttributeGetter, annotationAndClass.getRight(), annotationAndClass.getLeft()));
159    }
160    
161    private static <A extends Annotation, T> T _logAndGet(BiFunction<A, Class<?>, T> annotationAttributeGetter, Class<?> annotated, A annotation)
162    {
163        T result = annotationAttributeGetter.apply(annotation, annotated);
164        if (__LOGGER.isDebugEnabled())
165        {
166            __LOGGER.debug("value '{}' retrieved from {}", result, annotated.getName());
167        }
168        return result;
169    }
170}
171