001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017-2018 microBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.cdi; 018 019import java.lang.annotation.Annotation; 020 021import java.lang.reflect.AnnotatedElement; 022 023import java.util.Arrays; 024import java.util.ArrayDeque; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Deque; 028import java.util.HashSet; 029import java.util.LinkedHashSet; 030import java.util.Objects; 031import java.util.Set; 032 033import javax.enterprise.inject.spi.Annotated; 034import javax.enterprise.inject.spi.BeanManager; 035 036/** 037 * A utility class housing methods that do helpful things with {@link 038 * Annotation}s, {@link Annotated}s and {@link AnnotatedElement}s. 039 * 040 * @author <a href="https://about.me/lairdnelson" 041 * target="_parent">Laird Nelson</a> 042 */ 043public final class Annotations { 044 045 046 /* 047 * Constructors. 048 */ 049 050 051 /** 052 * Creates a new {@link Annotations}. 053 */ 054 private Annotations() { 055 super(); 056 } 057 058 059 /* 060 * Static methods. 061 */ 062 063 064 /** 065 * Returns a {@link Set} of {@link Annotation}s that are 066 * <em>ultimately qualified</em> with an annotation whose 067 * {@linkplain Annotation#annotationType() annotation type} is equal 068 * to the supplied {@code metaAnnotationType}. 069 * 070 * <p>This method never returns {@code null}.</p> 071 * 072 * @param host the {@link Annotated} whose {@linkplain 073 * Annotated#getAnnotations() annotations} will be used as the 074 * initial set; must not be {@code null} 075 * 076 * @param metaAnnotationType the qualifier {@linkplain 077 * Annotation#annotationType() annotation type} to look for; must 078 * not be {@code null} 079 * 080 * @param beanManager a {@link BeanManager} for retrieving the 081 * appropriate {@linkplain BeanManager#createAnnotatedType(Class) 082 * annotated type} when appropriate; may be {@code null} 083 * 084 * @return a non-{@code null} subset of {@link Annotation}s 085 * 086 * @exception NullPointerException if {@code host} or {@code 087 * metaAnnotationType} is {@code null} 088 * 089 * @see #retainAnnotationsQualifiedWith(Collection, Class, 090 * BeanManager) 091 */ 092 public static final Set<Annotation> getAnnotationsQualifiedWith(final Annotated host, final Class<? extends Annotation> metaAnnotationType, final BeanManager beanManager) { 093 return retainAnnotationsQualifiedWith(host.getAnnotations(), Objects.requireNonNull(metaAnnotationType), beanManager); 094 } 095 096 /** 097 * Returns a {@link Set} of {@link Annotation}s that are 098 * <em>ultimately qualified</em> with an annotation whose 099 * {@linkplain Annotation#annotationType() annotation type} is equal 100 * to the supplied {@code metaAnnotationType}. 101 * 102 * <p>This method never returns {@code null}.</p> 103 * 104 * @param host the {@link AnnotatedElement} whose {@linkplain 105 * Annotated#getAnnotations() annotations} will be used as the 106 * initial set; must not be {@code null} 107 * 108 * @param metaAnnotationType the qualifier {@linkplain 109 * Annotation#annotationType() annotation type} to look for; must 110 * not be {@code null} 111 * 112 * @param beanManager a {@link BeanManager} for retrieving the 113 * appropriate {@linkplain BeanManager#createAnnotatedType(Class) 114 * annotated type} when appropriate; may be {@code null} 115 * 116 * @return a non-{@code null} subset of {@link Annotation}s 117 * 118 * @exception NullPointerException if {@code host} or {@code 119 * metaAnnotationType} is {@code null} 120 * 121 * @see #retainAnnotationsQualifiedWith(Collection, Class, 122 * BeanManager) 123 */ 124 public static final Set<Annotation> getAnnotationsQualifiedWith(final AnnotatedElement host, final Class<? extends Annotation> metaAnnotationType, final BeanManager beanManager) { 125 return retainAnnotationsQualifiedWith(Arrays.asList(host.getAnnotations()), Objects.requireNonNull(metaAnnotationType), beanManager); 126 } 127 128 /** 129 * Given a {@link Collection} of {@link Annotation}s, returns a 130 * subset of them that are found to be <em>ultimately qualified</em> 131 * with an annotation whose {@linkplain Annotation#annotationType() 132 * annotation type} is equal to the supplied {@code 133 * metaAnnotationType}. 134 * 135 * <p>This method never returns {@code null}.</p> 136 * 137 * @param suppliedAnnotations a {@link Collection} of {@link 138 * Annotation}s that will be used as the initial set; may be {@code 139 * null} 140 * 141 * @param metaAnnotationType the qualifier {@linkplain 142 * Annotation#annotationType() annotation type} to look for; must 143 * not be {@code null} 144 * 145 * @param beanManager a {@link BeanManager} for retrieving the 146 * appropriate {@linkplain BeanManager#createAnnotatedType(Class) 147 * annotated type} when appropriate; may be {@code null} 148 * 149 * @return a non-{@code null} subset of {@link Annotation}s 150 * 151 * @exception NullPointerException if {@code metaAnnotationType} is 152 * {@code null} 153 */ 154 public static final Set<Annotation> retainAnnotationsQualifiedWith(final Collection<? extends Annotation> suppliedAnnotations, final Class<? extends Annotation> metaAnnotationType, final BeanManager beanManager) { 155 Objects.requireNonNull(metaAnnotationType); 156 final Set<Annotation> results = new LinkedHashSet<>(); 157 if (suppliedAnnotations != null) { 158 for (final Annotation annotation : suppliedAnnotations) { 159 if (annotation != null && isAnnotationQualifiedWith(annotation, metaAnnotationType, beanManager)) { 160 results.add(annotation); 161 } 162 } 163 } 164 165 return results; 166 } 167 168 /** 169 * Returns a {@link Collection} of {@link Annotation}s that are 170 * <em>{@linkplain AnnotatedElement present}</em> on the supplied 171 * {@link Class}, using the {@link 172 * BeanManager#createAnnotatedType(Class)} method as appropriate. 173 * 174 * <p>This method never returns {@code null}.</p> 175 * 176 * @param c the {@link Class} whose {@link Annotation}s should be 177 * returned; must not be {@code null} 178 * 179 * @param beanManager a {@link BeanManager} whose {@link 180 * BeanManager#createAnnotatedType(Class)} method will be called; 181 * may be {@code null} in which case {@link 182 * AnnotatedElement#getAnnotations()} will be called instead 183 * 184 * @return a non-{@code null} {@link Collection} of {@link 185 * Annotation}s 186 * 187 * @see BeanManager#createAnnotatedType(Class) 188 * 189 * @see AnnotatedElement#getAnnotations() 190 */ 191 public static final Collection<? extends Annotation> getAnnotations(final Class<?> c, final BeanManager beanManager) { 192 Objects.requireNonNull(c); 193 final Collection<? extends Annotation> returnValue; 194 if (beanManager != null) { 195 final Annotated annotated = beanManager.createAnnotatedType(c); 196 assert annotated != null; 197 returnValue = annotated.getAnnotations(); 198 } else { 199 returnValue = Arrays.asList(c.getAnnotations()); 200 } 201 return returnValue; 202 } 203 204 /** 205 * Returns {@code true} if the supplied {@link Annotation} is 206 * <em>ultimately qualified</em> by an annotation {@linkplain 207 * Annotation#annotationType() with the supplied 208 * <code>qualifierType</code> as its <code>annotationType</code>}. 209 * 210 * <p><em>Ultimately qualified</em> means that either the qualifier 211 * annotation represented by the supplied {@code qualifierType} is 212 * {@linkplain AnnotatedElement <em>present</em>} on the supplied 213 * {@code annotation} or one of its {@link Annotation}s, or 214 * {@linkplain AnnotatedElement <em>present</em>} on one of 215 * <em>those</em> {@link Annotation}s, and so on.</p> 216 * 217 * @param annotation the {@link Annotation} to check; must not be 218 * {@code null} 219 * 220 * @param qualifierType the {@link Class} representing the {@link 221 * Annotation#annotationType() annotation type} to look for; must 222 * not be {@code null} 223 * 224 * @param beanManager a {@link BeanManager} whose {@link 225 * BeanManager#createAnnotatedType(Class)} method will be called; 226 * may be {@code null} 227 * 228 * @return {@code true} if the supplied {@link Annotation} is 229 * <em>ultimately qualified</em> by an annotation {@linkplain 230 * Annotation#annotationType() with the supplied 231 * <code>qualifierType</code> as its <code>annotationType</code>}; 232 * {@code false} otherwise 233 * 234 * @exception NullPointerException if {@code annotation} or {@code 235 * qualifierType} is {@code null} 236 */ 237 public static final boolean isAnnotationQualifiedWith(final Annotation annotation, final Class<? extends Annotation> qualifierType, final BeanManager beanManager) { 238 return isAnnotationQualifiedWith(annotation, qualifierType, beanManager, new HashSet<>()); 239 } 240 241 private static final boolean isAnnotationQualifiedWith(final Annotation annotation, final Class<? extends Annotation> qualifierType, final BeanManager beanManager, Set<Annotation> seen) { 242 Objects.requireNonNull(annotation); 243 Objects.requireNonNull(qualifierType); 244 if (seen == null) { 245 seen = new HashSet<>(); 246 } 247 boolean returnValue = false; 248 if (!isBlacklisted(annotation) && !seen.contains(annotation)) { 249 seen.add(annotation); 250 final Class<? extends Annotation> annotationType = annotation.annotationType(); 251 assert annotationType != null; 252 if (annotationType.equals(qualifierType)) { 253 returnValue = true; 254 } else { 255 final Collection<? extends Annotation> metaAnnotations = getAnnotations(annotationType, beanManager); 256 if (metaAnnotations != null && !metaAnnotations.isEmpty()) { 257 for (final Annotation metaAnnotation : metaAnnotations) { 258 if (metaAnnotation != null && isAnnotationQualifiedWith(metaAnnotation, qualifierType, beanManager, seen)) { // RECURSION 259 returnValue = true; 260 break; 261 } 262 } 263 } 264 } 265 } 266 return returnValue; 267 } 268 269 private static final boolean isBlacklisted(final Annotation annotation) { 270 return annotation == null; 271 } 272 273}