org.scijava.util.ClassUtils Maven / Gradle / Ivy
Show all versions of scijava-common Show documentation
/*
* #%L
* SciJava Common shared library for SciJava software.
* %%
* Copyright (C) 2009 - 2014 Board of Regents of the University of
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
* Institute of Molecular Cell Biology and Genetics.
* %%
* 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 com.googlecode.gentyref.GenericTypeReflector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* 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
}
// -- Type conversion and casting --
/**
* @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 ConversionUtils#cast(Object, Class)}
*/
@Deprecated
public static T cast(final Object obj, final Class type) {
return ConversionUtils.cast(obj, type);
}
/**
* @deprecated use {@link ConversionUtils#canCast(Class, Class)}
*/
@Deprecated
public static boolean canCast(final Class> c, final Class> type) {
return ConversionUtils.canCast(c, type);
}
/**
* @deprecated use {@link ConversionUtils#canCast(Object, Class)}
*/
@Deprecated
public static boolean canCast(final Object obj, final Class> type) {
return ConversionUtils.canCast(obj, type);
}
/**
* @deprecated use {@link ConversionUtils#getNonprimitiveType(Class)}
*/
@Deprecated
public static Class getNonprimitiveType(final Class type) {
return ConversionUtils.getNonprimitiveType(type);
}
/**
* @deprecated use {@link ConversionUtils#getNullValue(Class)}
*/
@Deprecated
public static T getNullValue(final Class type) {
return ConversionUtils.getNullValue(type);
}
// -- Class loading, querying and reflection --
/**
* Loads the class with the given name, using the current thread's context
* class loader, or null if it cannot be loaded.
*
* @see #loadClass(String, ClassLoader)
*/
public static Class> loadClass(final String className) {
return loadClass(className, null);
}
/**
* Loads the class with the given name, using the specified
* {@link ClassLoader}, or null if it cannot be loaded.
*
* This method is capable of parsing several different class name syntaxes.
* In particular, array classes (including primitives) represented using
* either square brackets or internal Java array name syntax are supported.
* Examples:
*
*
* - {@code boolean} is loaded as {@code boolean.class}
* - {@code Z} is loaded as {@code boolean.class}
* - {@code double[]} is loaded as {@code double[].class}
* - {@code string[]} is loaded as {@code java.lang.String.class}
* - {@code [F} is loaded as {@code float[].class}
*
*
* @param name The name of the class to load.
* @param classLoader The class loader with which to load the class; if null,
* the current thread's context class loader will be used.
*/
public static Class> loadClass(final String name,
final ClassLoader classLoader)
{
// handle primitive types
if (name.equals("Z") || name.equals("boolean")) return boolean.class;
if (name.equals("B") || name.equals("byte")) return byte.class;
if (name.equals("C") || name.equals("char")) return char.class;
if (name.equals("D") || name.equals("double")) return double.class;
if (name.equals("F") || name.equals("float")) return float.class;
if (name.equals("I") || name.equals("int")) return int.class;
if (name.equals("J") || name.equals("long")) return long.class;
if (name.equals("S") || name.equals("short")) return short.class;
if (name.equals("V") || name.equals("void")) return void.class;
// handle built-in class shortcuts
final String className;
if (name.equals("string")) className = "java.lang.String";
else className = name;
// handle source style arrays (e.g.: "java.lang.String[]")
if (name.endsWith("[]")) {
final String elementClassName = name.substring(0, name.length() - 2);
return getArrayClass(loadClass(elementClassName, classLoader));
}
// handle non-primitive internal arrays (e.g.: "[Ljava.lang.String;")
if (name.startsWith("[L") && name.endsWith(";")) {
final String elementClassName = name.substring(2, name.length() - 1);
return getArrayClass(loadClass(elementClassName, classLoader));
}
// handle other internal arrays (e.g.: "[I", "[[I", "[[Ljava.lang.String;")
if (name.startsWith("[")) {
final String elementClassName = name.substring(1);
return getArrayClass(loadClass(elementClassName, classLoader));
}
// load the class!
try {
final ClassLoader cl =
classLoader == null ? Thread.currentThread().getContextClassLoader()
: classLoader;
return cl.loadClass(className);
}
catch (final ClassNotFoundException e) {
return null;
}
}
/**
* Gets the array class corresponding to the given element type.
*
* For example, {@code getArrayClass(double.class)} returns
* {@code double[].class}.
*
*/
public static Class> getArrayClass(final Class> elementClass) {
if (elementClass == null) return null;
// NB: It appears the reflection API has no built-in way to do this.
// So unfortunately, we must allocate a new object and then inspect it.
try {
return Array.newInstance(elementClass, 0).getClass();
}
catch (final IllegalArgumentException exc) {
return null;
}
}
/** Checks whether a class with the given name exists. */
public static boolean hasClass(final String className) {
return hasClass(className, null);
}
/** Checks whether a class with the given name exists. */
public static boolean hasClass(final String className,
final ClassLoader classLoader)
{
return loadClass(className, classLoader) != null;
}
/**
* Gets the base location of the given class.
*
* If the class is directly on the file system (e.g.,
* "/path/to/my/package/MyClass.class") then it will return the base directory
* (e.g., "/path/to").
*
*
* If the class is within a JAR file (e.g.,
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
* path to the JAR (e.g., "/path/to/my-jar.jar").
*
*
* @param className The name of the class whose location is desired.
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
*/
public static URL getLocation(final String className) {
return getLocation(className, null);
}
/**
* Gets the base location of the given class.
*
* If the class is directly on the file system (e.g.,
* "/path/to/my/package/MyClass.class") then it will return the base directory
* (e.g., "/path/to").
*
*
* If the class is within a JAR file (e.g.,
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
* path to the JAR (e.g., "/path/to/my-jar.jar").
*
*
* @param className The name of the class whose location is desired.
* @param classLoader The class loader to use when loading the class.
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
*/
public static URL getLocation(final String className,
final ClassLoader classLoader)
{
final Class> c = loadClass(className, classLoader);
return getLocation(c);
}
/**
* Gets the base location of the given class.
*
* If the class is directly on the file system (e.g.,
* "/path/to/my/package/MyClass.class") then it will return the base directory
* (e.g., "file:/path/to").
*
*
* If the class is within a JAR file (e.g.,
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
* path to the JAR (e.g., "file:/path/to/my-jar.jar").
*
*
* @param c The class whose location is desired.
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
*/
public static URL getLocation(final Class> c) {
if (c == null) return null; // could not load the class
// try the easy way first
try {
final URL codeSourceLocation =
c.getProtectionDomain().getCodeSource().getLocation();
if (codeSourceLocation != null) return codeSourceLocation;
}
catch (final SecurityException e) {
// NB: Cannot access protection domain.
}
catch (final NullPointerException e) {
// NB: Protection domain or code source is null.
}
// NB: The easy way failed, so we try the hard way. We ask for the class
// itself as a resource, then strip the class's path from the URL string,
// leaving the base path.
// get the class's raw resource path
final URL classResource = c.getResource(c.getSimpleName() + ".class");
if (classResource == null) return null; // cannot find class resource
final String url = classResource.toString();
final String suffix = c.getCanonicalName().replace('.', '/') + ".class";
if (!url.endsWith(suffix)) return null; // weird URL
// strip the class's path from the URL string
final String base = url.substring(0, url.length() - suffix.length());
String path = base;
// remove the "jar:" prefix and "!/" suffix, if present
if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);
try {
return new URL(path);
}
catch (final MalformedURLException e) {
e.printStackTrace();
return null;
}
}
/**
* 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 new list containing all fields with the requested annotation.
*/
public static List getAnnotatedFields(
final Class> c, final Class annotationClass)
{
final ArrayList 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)
{
if (c == null) return;
// check supertypes for annotated fields first
getAnnotatedFields(c.getSuperclass(), annotationClass, fields);
for (final Class> iface : c.getInterfaces()) {
getAnnotatedFields(iface, annotationClass, fields);
}
for (final Field f : c.getDeclaredFields()) {
final A ann = f.getAnnotation(annotationClass);
if (ann != null) fields.add(f);
}
}
/**
* Gets the specified field of the given class, or null if it does not exist.
*/
public static Field getField(final String className, final String fieldName) {
return getField(loadClass(className), fieldName);
}
/**
* Gets the specified field of the given class, or null if it does not exist.
*/
public static Field getField(final Class> c, final String fieldName) {
if (c == null) return null;
try {
return c.getDeclaredField(fieldName);
}
catch (final NoSuchFieldException e) {
return null;
}
}
/**
* Returns the "safe" type(s) of the given field, as viewed from the specified
* type. This may be narrower than what {@link Field#getType()} returns, if
* the field is declared in a superclass, or {@code type} has a type parameter
* that is used in the type of the field.
*
* For example, suppose we have the following three classes:
*
*
*
* public class Thing<T> {
* public T thing;
* }
*
* public class NumberThing<N extends Number> extends Thing<N> { }
*
* public class IntegerThing extends NumberThing<Integer> { }
*
*
* Then this method operates as follows:
*
*
* field = ClassUtils.getField(Thing.class, "thing");
*
* field.getType(); // Object
*
* ClassUtils.getTypes(field, Thing.class).get(0); // Object
* ClassUtils.getTypes(field, NumberThing.class).get(0); // Number
* ClassUtils.getTypes(field, IntegerThing.class).get(0); // Integer
*
*
*
* In cases of complex generics which take the intersection of
* multiple types using the {@code &} operator, there may be multiple types
* returned by this method. For example:
*
*
*
* public class ComplexThing<T extends Serializable & Cloneable> extends Thing<T> { }
*
* ClassUtils.getTypes(field, ComplexThing.class); // Serializable, Cloneable
*
*
* @see #getGenericType(Field, Class)
*/
public static List> getTypes(final Field field, final Class> type)
{
final Type genericType = getGenericType(field, type);
return GenericTypeReflector.getUpperBoundClassAndInterfaces(genericType);
}
/**
* Returns the "safe" generic type of the given field, as viewed from the
* given type. This may be narrower than what {@link Field#getGenericType()}
* returns, if the field is declared in a superclass, or {@code type} has a
* type parameter that is used in the type of the field.
*
* For example, suppose we have the following three classes:
*
*
*
* public class Thing<T> {
* public T thing;
* }
*
* public class NumberThing<N extends Number> extends Thing<N> { }
*
* public class IntegerThing extends NumberThing<Integer> { }
*
*
* Then this method operates as follows:
*
*
* field = ClassUtils.getField(Thing.class, "thing");
*
* field.getType(); // Object
* field.getGenericType(); // T
*
* ClassUtils.getGenericType(field, Thing.class); // T
* ClassUtils.getGenericType(field, NumberThing.class); // N extends Number
* ClassUtils.getGenericType(field, IntegerThing.class); // Integer
*
*
* @see #getTypes(Field, Class)
*/
public static Type getGenericType(final Field field, final Class> type) {
final Type wildType = GenericTypeReflector.addWildcardParameters(type);
return GenericTypeReflector.getExactFieldType(field, wildType);
}
/**
* 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.
*/
public static void setValue(final Field field, final Object instance,
final Object value)
{
try {
field.setAccessible(true);
field.set(instance, ConversionUtils
.convert(value, field.getGenericType()));
}
catch (final IllegalAccessException e) {
throw new IllegalArgumentException("No access to field: " +
field.getName(), e);
}
}
// -- Type querying --
public static boolean isBoolean(final Class> type) {
return type == boolean.class || Boolean.class.isAssignableFrom(type);
}
public static boolean isByte(final Class> type) {
return type == byte.class || Byte.class.isAssignableFrom(type);
}
public static boolean isCharacter(final Class> type) {
return type == char.class || Character.class.isAssignableFrom(type);
}
public static boolean isDouble(final Class> type) {
return type == double.class || Double.class.isAssignableFrom(type);
}
public static boolean isFloat(final Class> type) {
return type == float.class || Float.class.isAssignableFrom(type);
}
public static boolean isInteger(final Class> type) {
return type == int.class || Integer.class.isAssignableFrom(type);
}
public static boolean isLong(final Class> type) {
return type == long.class || Long.class.isAssignableFrom(type);
}
public static boolean isShort(final Class> type) {
return type == short.class || Short.class.isAssignableFrom(type);
}
public static boolean isNumber(final Class> type) {
return Number.class.isAssignableFrom(type) || type == byte.class ||
type == double.class || type == float.class || type == int.class ||
type == long.class || type == short.class;
}
public static boolean isText(final Class> type) {
return String.class.isAssignableFrom(type) || isCharacter(type);
}
// -- 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);
}
}