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

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

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%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 - 2024 Weber Informatics LLC | Privacy Policy