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

org.scijava.common3.Annotations Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * Common functionality widely used across SciJava modules.
 * %%
 * Copyright (C) 2021 - 2024 SciJava developers.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava.common3;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Useful methods for working with Java annotations.
 *
 * @author Mark Hiner
 * @author Curtis Rueden
 */
public final class Annotations {

	private Annotations() {
		// prevent instantiation of utility class
	}

	/**
	 * This maps a base class (key1) to a map of annotation classes (key2), which
	 * then maps to a list of {@link Field} instances, being the set of fields in
	 * the base class with the specified annotation.
	 * 

* This map serves as a cache, as these annotations should not change at * runtime and thus can alleviate the frequency field querying. *

* * @see issue * #142 */ private static final CacheMap fieldCache = new CacheMap<>(); /** * This maps a base class (key1) to a map of annotation classes (key2), which * then maps to a list of {@link Method} instances, being the set of methods * in the base class with the specified annotation. *

* This map serves as a cache, as these annotations should not change at * runtime and thus can alleviate the frequency of method querying. *

* * @see issue * #142 */ private static final CacheMap methodCache = new CacheMap<>(); // -- Class loading, querying and reflection -- /** * Gets the given class's {@link Method}s marked with the annotation of the * specified class. *

* Unlike {@link Class#getMethods()}, the result will include any non-public * methods, including methods defined in supertypes of the given class. *

* * @param c The class to scan for annotated methods. * @param annotationClass The type of annotation for which to scan. * @return A list containing all methods with the requested annotation. Note * that for performance reasons, lists may be cached and reused, so it * is best to make a copy of the result if you need to modify it. */ public static List annotatedMethods( final Class c, final Class annotationClass) { List methods = methodCache.getList(c, annotationClass); if (methods == null) { methods = new ArrayList<>(); annotatedMethods(c, annotationClass, methods); } return methods; } /** * Gets the given class's {@link Method}s marked with the annotation of the * specified class. *

* Unlike {@link Class#getMethods()}, the result will include any non-public * methods, including methods defined in supertypes of the given class. *

* * @param c The class to scan for annotated methods. * @param annotationClass The type of annotation for which to scan. * @param methods The list to which matching methods will be added. */ public static
void annotatedMethods(final Class c, final Class annotationClass, final List methods) { List cachedMethods = methodCache.getList(c, annotationClass); if (cachedMethods == null) { final Query query = new Query(); query.put(annotationClass, Method.class); cacheAnnotatedObjects(c, query); cachedMethods = methodCache.getList(c, annotationClass); } if (cachedMethods != null) methods.addAll(cachedMethods); } /** * Gets the given class's {@link Field}s marked with the annotation of the * specified class. *

* Unlike {@link Class#getFields()}, the result will include any non-public * fields, including fields defined in supertypes of the given class. *

* * @param c The class to scan for annotated fields. * @param annotationClass The type of annotation for which to scan. * @return A list containing all fields with the requested annotation. Note * that for performance reasons, lists may be cached and reused, so it * is best to make a copy of the result if you need to modify it. */ public static
List annotatedFields( final Class c, final Class annotationClass) { List fields = fieldCache.getList(c, annotationClass); if (fields == null) { fields = new ArrayList<>(); annotatedFields(c, annotationClass, fields); } return fields; } /** * Gets the given class's {@link Field}s marked with the annotation of the * specified class. *

* Unlike {@link Class#getFields()}, the result will include any non-public * fields, including fields defined in supertypes of the given class. *

* * @param c The class to scan for annotated fields. * @param annotationClass The type of annotation for which to scan. * @param fields The list to which matching fields will be added. */ public static
void annotatedFields(final Class c, final Class annotationClass, final List fields) { List cachedFields = fieldCache.getList(c, annotationClass); if (cachedFields == null) { final Query query = new Query(); query.put(annotationClass, Field.class); cacheAnnotatedObjects(c, query); cachedFields = fieldCache.getList(c, annotationClass); } if (cachedFields != null) fields.addAll(cachedFields); } // -- Helper methods -- /** * This method scans the provided class, its superclasses and interfaces for * all supported {@link Annotation} : {@link AnnotatedElement} pairs. These * are then cached to remove the need for future queries. *

* By combining multiple {@code Annotation : AnnotatedElement} pairs in one * query, we can limit the number of times a class's superclass and interface * hierarchy are traversed. *

* * @param scannedClass Class to scan * @param query Pairs of {@link Annotation} and {@link AnnotatedElement}s to * discover. */ private static void cacheAnnotatedObjects(final Class scannedClass, final Query query) { // Only allow one thread at a time to populate cached objects for a given // class. This lock is technically overkill - the minimal lock would be // on the provided Query contents + class. Devising a way to obtain this // lock could be useful - as if Query A and Query B were executed on the // same class by different threads, there are three scenarios: // 1) intersection of A + B is empty - then they can run on separate threads // 2) A == B - whichever was received second must wait for the first to // finish. // 3) A != B and intersection of A + B is not empty - the intersection // subset // can be safely performed on a separate thread, but the later query must // still wait for the earlier query to complete. // // NB: an alternative would be to update the annotatedxxx methods to // return Sets instead of Lists. Then threads can pretty much go nuts // as long as you double lock the Set creation in a synchronized block. // // NB: another possibility would be to keep this synchronized entry point // but divide the work for each Query into asynchronous blocks. However, it // has not been investigated how much of a performance boost that would // provide as it would then cause multiple traversals of the class hierarchy // - which is exactly what the Query notation was created to avoid. synchronized (scannedClass) { // NB: The java.lang.Object class does not have any annotated methods. // And even if it did, it definitely does not have any methods annotated // with SciJava annotations such as org.scijava.event.EventHandler, which // are the main sorts of methods we are interested in. if (scannedClass == Object.class) return; // Initialize step - determine which queries are solved final Set> keysToDrop = new HashSet<>(); for (final Class annotationClass : query.keySet()) { // Fields if (fieldCache.getList(scannedClass, annotationClass) != null) { keysToDrop.add(annotationClass); } else if (methodCache.getList(scannedClass, annotationClass) != null) { keysToDrop.add(annotationClass); } } // Clean up resolved keys for (final Class key : keysToDrop) { query.remove(key); } // Stop now if we know all requested information is cached if (query.isEmpty()) return; final List> inherited = new ArrayList<>(); // cache all parents recursively final Class superClass = scannedClass.getSuperclass(); if (superClass != null) { // Recursive step cacheAnnotatedObjects(superClass, new Query(query)); inherited.add(superClass); } // cache all interfaces recursively for (final Class ifaceClass : scannedClass.getInterfaces()) { // Recursive step cacheAnnotatedObjects(ifaceClass, new Query(query)); inherited.add(ifaceClass); } // Populate supported objects for scanned class for (final Class annotationClass : query.keySet()) { final Class objectClass = query.get( annotationClass); try { // Methods if (Method.class.isAssignableFrom(objectClass)) { populateCache(scannedClass, inherited, annotationClass, methodCache, scannedClass.getDeclaredMethods()); } // Fields else if (Field.class.isAssignableFrom(objectClass)) { populateCache(scannedClass, inherited, annotationClass, fieldCache, scannedClass.getDeclaredFields()); } } catch (final Throwable t) { // FIXME: empty catch block } } } } /** * Populates the cache of annotated elements for a particular class by looking * for all inherited and declared instances annotated with the specified * annotationClass. If no matches are found, an empty mapping is created to * mark this class complete. */ private static void populateCache( final Class scannedClass, final List> inherited, final Class annotationClass, final CacheMap cacheMap, final T[] declaredElements) { // Add inherited elements for (final Class inheritedClass : inherited) { final List annotatedElements = cacheMap.getList(inheritedClass, annotationClass); if (annotatedElements != null && !annotatedElements.isEmpty()) { final List scannedElements = cacheMap.makeList(scannedClass, annotationClass); scannedElements.addAll(annotatedElements); } } // Add declared elements if (declaredElements != null && declaredElements.length > 0) { List scannedElements = null; for (final T t : declaredElements) { if (t.getAnnotation(annotationClass) != null) { if (scannedElements == null) { scannedElements = cacheMap.makeList(scannedClass, annotationClass); } scannedElements.add(t); } } } // If there were no elements for this query, map an empty // list to mark the query complete if (cacheMap.getList(scannedClass, annotationClass) == null) { cacheMap.putList(scannedClass, annotationClass, Collections . emptyList()); } } // -- Helper classes -- /** * Convenience class for {@code Map > Map > List} hierarchy. Cleans up * generics and contains helper methods for traversing the two map levels. *

* The intent for this class is to allow subclasses to specify the generic * parameter ultimately referenced by the at the end of these maps. *

*

* The first map key is a base class, presumably with various types of * annotations. The second map key is the annotation class, for example * {@link Method} or {@link Field}. The list then contains all instances of * the annotated type within the original base class. *

* * @param - The type of {@link AnnotatedElement} contained by the * {@link List} ultimately referenced by these {@link Map}s */ private static class CacheMap extends HashMap, Map, List>> { /** * @param c Base class of interest * @param annotationClass {@link Annotation} type within the base class * @return A {@link List} of instances in the base class with the specified * {@link Annotation}, or null if a cached list does not exist. */ public List getList(final Class c, final Class annotationClass) { final Map, List> annotationTypes = get(c); return annotationTypes == null ? null : annotationTypes.get( annotationClass); } /** * Creates a {@code base class > annotation > list of elements} mapping to * the provided list, creating the intermediate map if needed. * * @param c Base class of interest * @param annotationClass {@link Annotation} type of interest * @param annotatedElements List of {@link AnnotatedElement}s to map */ public void putList(final Class c, final Class annotationClass, final List annotatedElements) { Map, List> map = get(c); if (map == null) { map = new HashMap<>(); put(c, map); } map.put(annotationClass, annotatedElements); } /** * Generates mappings as in {@link #putList(Class, Class, List)}, but also * creates the {@link List} if it doesn't already exist. Returns the final * list at this mapping, for external population. * * @param c Base class of interest * @param annotationClass {@link Annotation} type of interest * @return Cached list of {@link AnnotatedElement}s in the base class with * the specified {@link Annotation}. */ public List makeList(final Class c, final Class annotationClass) { List elements = getList(c, annotationClass); if (elements == null) { elements = new ArrayList<>(); putList(c, annotationClass, elements); } return elements; } } /** A map of annotations to annotated elements. */ private static class Query extends HashMap, Class> { public Query() { super(); } public Query(final Query query) { super(query); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy