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