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

org.scijava.util.ClassUtils Maven / Gradle / Ivy

/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
 * Institute of Molecular Cell Biology and Genetics, University of
 * Konstanz, and KNIME GmbH.
 * %%
 * 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.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
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 {@link Class} objects and primitive types.
 *
 * @author Curtis Rueden
 */
public final class ClassUtils {

	private ClassUtils() {
		// 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 FieldCache fieldCache = new FieldCache(); /** * 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 MethodCache methodCache = new MethodCache(); // -- 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 getAnnotatedMethods( final Class c, final Class annotationClass) { List methods = methodCache.getList(c, annotationClass); if (methods == null) { methods = new ArrayList<>(); getAnnotatedMethods(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 getAnnotatedMethods(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 getAnnotatedFields( final Class c, final Class annotationClass) { List fields = fieldCache.getList(c, annotationClass); if (fields == null) { fields = new ArrayList<>(); getAnnotatedFields(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 getAnnotatedFields( 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); } fields.addAll(cachedFields); } /** * 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. */ public 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 getAnnotatedxxx 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 == null || 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) { // NB: No action needed? } } } } /** * Gets the given field's value of the specified object instance, or null if * the value cannot be obtained. */ public static Object getValue(final Field field, final Object instance) { try { field.setAccessible(true); return field.get(instance); } catch (final IllegalAccessException e) { return null; } } /** * Sets the given field's value of the specified object instance. * * @throws IllegalArgumentException if the value cannot be set. */ // FIXME: Move to ConvertService and deprecate this signature. public static void setValue(final Field field, final Object instance, final Object value) { try { field.setAccessible(true); final Object compatibleValue; if (value == null || field.getType().isInstance(value)) { // the given value is compatible with the field compatibleValue = value; } else { // the given value needs to be converted to a compatible type final Type fieldType = Types.fieldType(field, instance.getClass()); @SuppressWarnings("deprecation") final Object convertedValue = ConversionUtils.convert(value, fieldType); compatibleValue = convertedValue; } field.set(instance, compatibleValue); } catch (final IllegalAccessException e) { throw new IllegalArgumentException("No access to field: " + field.getName(), e); } } // -- Type querying -- // -- Comparison -- /** * Compares two {@link Class} objects using their fully qualified names. *

* Note: this method provides a natural ordering that may be inconsistent with * equals. Specifically, two unequal classes may return 0 when compared in * this fashion if they represent the same class loaded using two different * {@link ClassLoader}s. Hence, if this method is used as a basis for * implementing {@link Comparable#compareTo} or * {@link java.util.Comparator#compare}, that implementation may want to * impose logic beyond that of this method, for breaking ties, if a total * ordering consistent with equals is always required. *

* * @see org.scijava.Priority#compare(org.scijava.Prioritized, * org.scijava.Prioritized) */ public static int compare(final Class c1, final Class c2) { if (c1 == c2) return 0; final String name1 = c1 == null ? null : c1.getName(); final String name2 = c2 == null ? null : c2.getName(); return MiscUtils.compare(name1, name2); } // -- Helper methods -- private static Class arrayOrNull(final Class componentType) { try { return Types.array(componentType); } catch (final IllegalArgumentException exc) { return null; } } /** * 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()); } } // -- Deprecated methods -- /** @deprecated Use {@link Types#load(String)} instead. */ @Deprecated public static Class loadClass(final String name) { return Types.load(name); } /** @deprecated Use {@link Types#load(String, ClassLoader)} instead. */ @Deprecated public static Class loadClass(final String name, final ClassLoader classLoader) { return Types.load(name, classLoader); } /** @deprecated Use {@link Types#load(String, boolean)} instead. */ @Deprecated public static Class loadClass(final String className, final boolean quietly) { return Types.load(className, quietly); } /** * @deprecated Use {@link Types#load(String, ClassLoader, boolean)} instead. */ @Deprecated public static Class loadClass(final String name, final ClassLoader classLoader, final boolean quietly) { return Types.load(name, classLoader, quietly); } /** @deprecated Use {@link Types#load(String)} instead. */ @Deprecated public static boolean hasClass(final String className) { return Types.load(className) != null; } /** @deprecated Use {@link Types#load(String, ClassLoader)} instead. */ @Deprecated public static boolean hasClass(final String className, final ClassLoader classLoader) { return Types.load(className, classLoader) != null; } /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ @Deprecated public static URL getLocation(final String className) { return Types.location(Types.load(className)); } /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ @Deprecated public static URL getLocation(final String className, final ClassLoader classLoader) { return Types.location(Types.load(className, classLoader)); } /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ @Deprecated public static URL getLocation(final Class c) { return Types.location(c); } /** @deprecated Use {@link Types#isBoolean} instead. */ @Deprecated public static boolean isBoolean(final Class type) { return Types.isBoolean(type); } /** @deprecated Use {@link Types#isByte} instead. */ @Deprecated public static boolean isByte(final Class type) { return Types.isByte(type); } /** @deprecated Use {@link Types#isCharacter} instead. */ @Deprecated public static boolean isCharacter(final Class type) { return Types.isCharacter(type); } /** @deprecated Use {@link Types#isDouble} instead. */ @Deprecated public static boolean isDouble(final Class type) { return Types.isDouble(type); } /** @deprecated Use {@link Types#isFloat} instead. */ @Deprecated public static boolean isFloat(final Class type) { return Types.isFloat(type); } /** @deprecated Use {@link Types#isInteger} instead. */ @Deprecated public static boolean isInteger(final Class type) { return Types.isInteger(type); } /** @deprecated Use {@link Types#isLong} instead. */ @Deprecated public static boolean isLong(final Class type) { return Types.isLong(type); } /** @deprecated Use {@link Types#isShort} instead. */ @Deprecated public static boolean isShort(final Class type) { return Types.isShort(type); } /** @deprecated Use {@link Types#isNumber} instead. */ @Deprecated public static boolean isNumber(final Class type) { return Types.isNumber(type); } /** @deprecated Use {@link Types#isText} instead. */ @Deprecated public static boolean isText(final Class type) { return Types.isText(type); } /** @deprecated use {@link ConversionUtils#convert(Object, Class)} */ @Deprecated public static T convert(final Object value, final Class type) { return ConversionUtils.convert(value, type); } /** @deprecated use {@link ConversionUtils#canConvert(Class, Class)} */ @Deprecated public static boolean canConvert(final Class c, final Class type) { return ConversionUtils.canConvert(c, type); } /** @deprecated use {@link ConversionUtils#canConvert(Object, Class)} */ @Deprecated public static boolean canConvert(final Object value, final Class type) { return ConversionUtils.canConvert(value, type); } /** @deprecated use {@link Types#cast(Object, Class)} */ @Deprecated public static T cast(final Object obj, final Class type) { return Types.cast(obj, type); } /** @deprecated use {@link Types#isAssignable(Type, Type)} */ @Deprecated public static boolean canCast(final Class c, final Class type) { return Types.isAssignable(c, type); } /** @deprecated use {@link Types#isInstance(Object, Class)} */ @Deprecated public static boolean canCast(final Object obj, final Class type) { return Types.isInstance(obj, type); } /** @deprecated use {@link Types#box(Class)} */ @Deprecated public static Class getNonprimitiveType(final Class type) { return Types.box(type); } /** @deprecated use {@link Types#nullValue(Class)} */ @Deprecated public static T getNullValue(final Class type) { return Types.nullValue(type); } /** * @deprecated Use {@link Types#fieldType(Field, Class)} and {@link Types#raws} * instead. */ @Deprecated public static List> getTypes(final Field field, final Class type) { return Types.raws(Types.fieldType(field, type)); } /** @deprecated Use {@link Types#fieldType(Field, Class)} instead. */ @Deprecated public static Type getGenericType(final Field field, final Class type) { return Types.fieldType(field, type); } /** @deprecated Use {@link Types#field} instead. */ @Deprecated public static Field getField(final String className, final String fieldName) { try { return Types.field(Types.load(className), fieldName); } catch (final IllegalArgumentException e) { return null; } } /** @deprecated Use {@link Types#field} instead. */ @Deprecated public static Field getField(final Class c, final String fieldName) { try { return Types.field(c, fieldName); } catch (final IllegalArgumentException e) { return null; } } /** @deprecated Use {@link Types#array(Class)} instead. */ @Deprecated public static Class getArrayClass(final Class elementClass) { return Types.raw(arrayOrNull(elementClass)); } // -- Helper classes -- /** * Convenience class for a {@link CacheMap} that stores annotated * {@link Field}s. */ private static class FieldCache extends CacheMap { // Trivial subclass to narrow generic params } /** * Convenience class for a {@link CacheMap} that stores annotated * {@link Method}s. */ private static class MethodCache extends CacheMap { // Trivial subclass to narrow generic params } /** * 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) { List annotatedFields = null; final Map, List> annotationTypes = get(c); if (annotationTypes != null) { annotatedFields = annotationTypes.get(annotationClass); } return annotatedFields; } /** * 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; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy