All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.orne.beans.BeanAnnotationFinder Maven / Gradle / Ivy

package dev.orne.beans;

/*-
 * #%L
 * Orne Beans
 * %%
 * Copyright (C) 2020 Orne Developments
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

Type level annotation finder. Detects annotations in classes, * directly implemented interfaces, super classes and inherited * interfaces. Supports detection of annotation list annotations.

* *

Examples:

* *

To search for type level annotations:

*
 * Set<?> annotations = new BeanAnnotationFinder<>(annotationType)
 *     .find(beanType);
 * // Real case
 * Set<NotNull> annotations = new BeanAnnotationFinder<>(NotNull.class)
 *     .find(beanType);
 * 
* *

To search for type level annotations with annotation list support:

*
 * Set<?> annotations = new BeanAnnotationFinder<>(
 *         annotationType,
 *         annotationListType,
 *         annotationListExtractor)
 *     .find(beanType);
 * // Real case
 * Set<NotNull> annotations = new BeanAnnotationFinder<>(
 *         NotNull.class,
 *         NotNull.List.class,
 *         NotNull.List::values)
 *     .find(beanType);
 * 
* *

Instances are reusable and thread-safe.

* * @author (w) Iker Hernaez * @version 1.0, 2020-05 * @param The supported annotation type * @param The supported annotation list type * @since 0.1 */ public class BeanAnnotationFinder< T extends Annotation, L extends Annotation> { /** The class logger. */ private static final Logger LOG = LoggerFactory.getLogger(BeanAnnotationFinder.class); /** The type level annotations shared cache. */ private static final Cache SHARED_CACHE = new WeakHashMapCache(); /** The searched annotation type. */ private final @NotNull Class annotationType; /** The searched annotation list type. */ private final Class annotationListType; /** The searched annotation list extractor. */ private final AnnotationListExtractor extractor; /** * The type level annotations cache for this instance. By default shared * between all instances. */ private @NotNull Cache cache = SHARED_CACHE; /** * Creates a new instance. * * @param annotationType The searched annotation type */ public BeanAnnotationFinder( final @NotNull Class annotationType) { this(annotationType, null, null); } /** * Creates a new instance. To support annotation list both * {@code annotationListType} and {@code extractor} must be supplied. * * @param annotationType The searched annotation type * @param annotationListType The searched annotation list type * @param extractor The searched annotation list extractor */ public BeanAnnotationFinder( final @NotNull Class annotationType, final Class annotationListType, final AnnotationListExtractor extractor) { super(); Validate.notNull(annotationType, "Annotation type is required."); this.annotationType = annotationType; if ((annotationListType == null) != (extractor == null)) { LOG.warn( "To support annotation lists both 'annotationListType'" + " and 'extractor' are required." + " Annotation list support is disabled." + " Passed values are: '{}' and '{}'", annotationListType, extractor); } this.annotationListType = annotationListType; this.extractor = extractor; } /** * Returns the searched annotation type. * * @return The searched annotation type */ public @NotNull Class getAnnotationType() { return this.annotationType; } /** * Returns the searched annotation list type. * * @return The searched annotation list type */ public Class getAnnotationListType() { return this.annotationListType; } /** * Returns the searched annotation list extractor. * * @return The searched annotation list extractor */ public AnnotationListExtractor getExtractor() { return this.extractor; } /** * Returns the cache to be used by this instance. * * @return The cache to be used by this instance */ protected @NotNull Cache getCache() { return this.cache; } /** *

Sets the type level annotations cache for this instance. * If {@code cache} is {@code null} shared cache will be used.

* * @param cache The cache to be used by this instance * @return This instance for method chaining */ protected BeanAnnotationFinder setCache( final Cache cache) { if (cache == null) { this.cache = SHARED_CACHE; } else { this.cache = cache; } return this; } /** * Finds the type level annotations this instance supports in the specified * type. * * @param type The type to search for supported annotations * @return The type level annotations of the type */ public @NotNull Set find( final @NotNull Class type) { Validate.notNull(type, "Type is required."); return findAnnotations(type, new HashSet<>()); } /** * Finds type level annotations of the supported annotation type in the * specified type. If the type has been already processed returns an * empty set. Found annotations are cached for future uses. * * @param type The type to found annotations on * @param visitedTypes The visited types record to prevent loops * and repeated searches * @return The found annotations */ protected @NotNull Set findAnnotations( final @NotNull Class type, final @NotNull Set> visitedTypes) { final Set annotations = new HashSet<>(0); if (!visitedTypes.contains(type)) { synchronized (this.cache) { final CacheEntryKey cacheKey = new CacheEntryKey<>( type, this.annotationType); final Set cachedDefinitions = this.cache.get(cacheKey); if (cachedDefinitions == null) { final Set foundAnnotatios = findAllAnnotations(type); annotations.addAll(foundAnnotatios); LOG.debug( "Caching annotations for type {}: {}", type, annotations); this.cache.put(cacheKey, foundAnnotatios); } else { annotations.addAll(cachedDefinitions); } } visitedTypes.add(type); } return annotations; } /** * Finds type level annotations of the supported annotation type in the * specified type. Finds direct annotations (detecting annotation lists if * configured) and annotations in super class and implemented interfaces. * * @param type The type to found annotations on * @return The found annotations */ protected @NotNull Set findAllAnnotations( final @NotNull Class type) { final Set annotations = new HashSet<>(0); final Set> visitedTypes = new HashSet<>(); visitedTypes.add(type); addDirectAnnotation(type, annotations); addDirectAnnotationsList(type, annotations); addInterfacesAnnotations(type, annotations, visitedTypes); addSuperclassAnnotations(type, annotations, visitedTypes); return annotations; } /** * Finds direct type level annotations of the supported annotation type in * the specified type. * * @param type The type to found annotations on * @param annotations The set to add the found annotations on */ protected void addDirectAnnotation( final @NotNull Class type, final @NotNull Set annotations) { final T annotation = type.getAnnotation(this.annotationType); if (annotation != null) { annotations.add(annotation); } } /** * Finds direct type level annotations of the supported annotation list * type in the specified type, if configured. If {@code annotationListType} * or {@code extractor} are {@code null} no searching is done. * * @param type The type to found annotations on * @param annotations The set to add the found annotations on */ protected void addDirectAnnotationsList( final @NotNull Class type, final @NotNull Set annotations) { if (this.annotationListType != null && this.extractor != null) { final L annotationsList = type.getAnnotation( this.annotationListType); if (annotationsList != null) { annotations.addAll(Arrays.asList( this.extractor.extract(annotationsList))); } } } /** * Finds type level annotations of the supported annotation type in * the super class of the specified type, if any. * * @param type The type to found annotations on * @param annotations The set to add the found annotations on * @param visitedTypes The visited types record to prevent loops * and repeated searches */ protected void addSuperclassAnnotations( final @NotNull Class type, final @NotNull Set annotations, final @NotNull Set> visitedTypes) { if (type.getSuperclass() != null) { annotations.addAll(findAnnotations( type.getSuperclass(), visitedTypes)); } } /** * Finds type level annotations of the supported annotation type in * the interfaces implemented by the specified type, if any. * * @param type The type to found annotations on * @param annotations The set to add the found annotations on * @param visitedTypes The visited types record to prevent loops * and repeated searches */ protected void addInterfacesAnnotations( final @NotNull Class type, final @NotNull Set annotations, final @NotNull Set> visitedTypes) { for (final Class iface : type.getInterfaces()) { annotations.addAll(findAnnotations(iface, visitedTypes)); } } /** *

Functional interface for extractor of individual annotations from * annotation list annotations.

* * @param The annotation type * @param The annotation list type */ @FunctionalInterface public static interface AnnotationListExtractor< L extends Annotation, T extends Annotation> { /** * Extracts the nested annotations from the list annotation * passed as argument. * * @param list The annotation list annotation * @return The nested annotations */ @NotNull T[] extract(L list); } /** * Class level annotations cache entry. This class is immutable. * * @param The annotation type */ protected static final class CacheEntryKey { /** The analyzed class. */ private final @NotNull Class type; /** The annotation type searched. */ private final @NotNull Class annotationType; /** * Creates a new instance. * * @param type The analyzed class * @param annotationType The annotation type searched */ public CacheEntryKey( final @NotNull Class type, final @NotNull Class annotationType) { super(); this.type = type; this.annotationType = annotationType; } /** * Returns the analyzed class. * * @return The analyzed class */ public @NotNull Class getType() { return this.type; } /** * Returns the annotation type searched. * * @return The annotation type searched */ public @NotNull Class getAnnotationType() { return this.annotationType; } /** * {@inheritDoc} */ @Override public int hashCode() { return new HashCodeBuilder() .append(getClass()) .append(this.type) .append(this.annotationType) .toHashCode(); } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } final CacheEntryKey other = (CacheEntryKey) obj; return new EqualsBuilder() .append(this.type, other.type) .append(this.annotationType, other.annotationType) .isEquals(); } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this) .append("type", this.type) .append("annotationType", this.annotationType) .toString(); } } /** * Interface for type level annotations cache. */ protected static interface Cache { /** * Returns {@code true} if this instance contains an entry for * the specified key. * * @param key The cache entry key * @return If this instance contains an entry for the key */ boolean contains( @NotNull CacheEntryKey key); /** * Returns the cached found annotations for the specified key, if any. * * @param The type of annotations * @param key The cache entry key * @return The annotations found, or {@code null} if not cached o cache expired */ Set get( @NotNull CacheEntryKey key); /** * Puts the specified found annotations for the specified key. * * @param The type of annotations * @param key The cache entry key * @param value The annotations found */ void put( @NotNull CacheEntryKey key, @NotNull Set value); } /** * Implementation of {@code Cache} based on {@code WeakHashMap}. * * @see Cache * @see WeakHashMap */ protected static class WeakHashMapCache implements Cache { /** The cache entries. */ private final WeakHashMap, Set> entries = new WeakHashMap<>(); /** * {@inheritDoc} */ @Override public synchronized boolean contains( final @NotNull CacheEntryKey key) { return this.entries.containsKey(key); } /** * {@inheritDoc} */ @Override public synchronized @NotNull void put( final @NotNull CacheEntryKey key, final @NotNull Set value) { this.entries.put(key, value); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public synchronized Set get( final @NotNull CacheEntryKey key) { return (Set) this.entries.get(key); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy