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

org.scijava.common3.Classes 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.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;

/**
 * Useful methods for working with {@link Class} objects and primitive types.
 *
 * @author Curtis Rueden
 */
public final class Classes {

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

	/**
	 * Gets the class loader to use. This will be the current thread's context
	 * class loader if non-null; otherwise it will be the system class loader.
	 *
	 * @see Thread#getContextClassLoader()
	 * @see ClassLoader#getSystemClassLoader()
	 */
	public static ClassLoader classLoader() {
		final ClassLoader contextCL = Thread.currentThread()
			.getContextClassLoader();
		return contextCL != null ? contextCL : ClassLoader.getSystemClassLoader();
	}

	/**
	 * Loads the class with the given name, using the current thread's context
	 * class loader, or null if it cannot be loaded.
	 *
	 * @param name The name of the class to load.
	 * @return The loaded class, or null if the class could not be loaded.
	 * @see #load(String, ClassLoader, boolean)
	 */
	public static Class load(final String name) {
		return load(name, null, true);
	}

	/**
	 * Loads the class with the given name, using the specified
	 * {@link ClassLoader}, or null if it cannot be loaded.
	 *
	 * @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.
	 * @return The loaded class, or null if the class could not be loaded.
	 * @see #load(String, ClassLoader, boolean)
	 */
	public static Class load(final String name,
		final ClassLoader classLoader)
	{
		return load(name, classLoader, true);
	}

	/**
	 * Loads the class with the given name, using the current thread's context
	 * class loader.
	 *
	 * @param className the name of the class to load.
	 * @param quietly Whether to return {@code null} (rather than throwing
	 *          {@link IllegalArgumentException}) if something goes wrong loading
	 *          the class.
	 * @return The loaded class, or {@code null} if the class could not be loaded
	 *         and the {@code quietly} flag is set.
	 * @see #load(String, ClassLoader, boolean)
	 * @throws IllegalArgumentException If the class cannot be loaded and the
	 *           {@code quietly} flag is not set.
	 */
	public static Class load(final String className, final boolean quietly) {
		return load(className, null, quietly);
	}

	/**
	 * 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. * @param quietly Whether to return {@code null} (rather than throwing * {@link IllegalArgumentException}) if something goes wrong loading * the class * @return The loaded class, or {@code null} if the class could not be loaded * and the {@code quietly} flag is set. * @throws IllegalArgumentException If the class cannot be loaded and the * {@code quietly} flag is not set. */ public static Class load(final String name, final ClassLoader classLoader, final boolean quietly) { // 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 arrayOrNull(load(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 arrayOrNull(load(elementClassName, classLoader)); } // handle other internal arrays (e.g.: "[I", "[[I", "[[Ljava.lang.String;") if (name.startsWith("[")) { final String elementClassName = name.substring(1); return arrayOrNull(load(elementClassName, classLoader)); } // load the class! try { final ClassLoader cl = classLoader == null ? classLoader() : classLoader; return cl.loadClass(className); } catch (final Throwable t) { // NB: Do not allow any failure to load the class to crash us. // Not ClassNotFoundException. // Not NoClassDefFoundError. // Not UnsupportedClassVersionError! if (quietly) return null; throw iae(t, "Cannot load class: %s", className); } } /** * Gets the base location of the given class. * * @param c The class whose location is desired. * @return URL pointing to the class, or null if the location could not be * determined. * @see #location(Class, boolean) */ public static URL location(final Class c) { return location(c, true); } /** * 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. * @param quietly Whether to return {@code null} (rather than throwing * {@link IllegalArgumentException}) if something goes wrong * determining the location. * @return URL pointing to the class, or null if the location could not be * determined and the {@code quietly} flag is set. * @throws IllegalArgumentException If the location cannot be determined and * the {@code quietly} flag is not set. */ public static URL location(final Class c, final boolean quietly) { Exception cause = null; String why = null; // try the easy way first try { final CodeSource codeSource = c.getProtectionDomain().getCodeSource(); if (codeSource != null) { final URL location = codeSource.getLocation(); if (location != null) return location; why = "null code source location"; } else why = "null code source"; } catch (final SecurityException exc) { // NB: Cannot access protection domain. cause = exc; why = "cannot access protection domain"; } // 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) { // cannot find class resource if (quietly) return null; throw iae(cause, "No class resource for class: %s (%s)", c.getName(), why); } final String url = classResource.toString(); final String suffix = c.getCanonicalName().replace('.', '/') + ".class"; if (!url.endsWith(suffix)) { // weird URL if (quietly) return null; throw iae(cause, "Unsupported URL format: %s (%s)", url, why); } // 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) { if (quietly) return null; throw iae(e, "Malformed URL: %s (%s)", path, why); } } // -- Primitives -- 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); } /** * Returns the non-primitive {@link Class} closest to the given type. *

* Specifically, the following type conversions are done: *

    *
  • boolean.class becomes Boolean.class
  • *
  • byte.class becomes Byte.class
  • *
  • char.class becomes Character.class
  • *
  • double.class becomes Double.class
  • *
  • float.class becomes Float.class
  • *
  • int.class becomes Integer.class
  • *
  • long.class becomes Long.class
  • *
  • short.class becomes Short.class
  • *
  • void.class becomes Void.class
  • *
* All other types are unchanged. */ public static Class box(final Class type) { final Class destType; if (type == boolean.class) destType = Boolean.class; else if (type == byte.class) destType = Byte.class; else if (type == char.class) destType = Character.class; else if (type == double.class) destType = Double.class; else if (type == float.class) destType = Float.class; else if (type == int.class) destType = Integer.class; else if (type == long.class) destType = Long.class; else if (type == short.class) destType = Short.class; else if (type == void.class) destType = Void.class; else destType = type; @SuppressWarnings("unchecked") final Class result = (Class) destType; return result; } /** * Returns the primitive {@link Class} closest to the given type. *

* Specifically, the following type conversions are done: *

    *
  • Boolean.class becomes boolean.class
  • *
  • Byte.class becomes byte.class
  • *
  • Character.class becomes char.class
  • *
  • Double.class becomes double.class
  • *
  • Float.class becomes float.class
  • *
  • Integer.class becomes int.class
  • *
  • Long.class becomes long.class
  • *
  • Short.class becomes short.class
  • *
  • Void.class becomes void.class
  • *
* All other types are unchanged. */ public static Class unbox(final Class type) { final Class destType; if (type == Boolean.class) destType = boolean.class; else if (type == Byte.class) destType = byte.class; else if (type == Character.class) destType = char.class; else if (type == Double.class) destType = double.class; else if (type == Float.class) destType = float.class; else if (type == Integer.class) destType = int.class; else if (type == Long.class) destType = long.class; else if (type == Short.class) destType = short.class; else if (type == Void.class) destType = void.class; else destType = type; @SuppressWarnings("unchecked") final Class result = (Class) destType; return result; } /** * Gets the "null" value for the given type. For non-primitives, this will * actually be null. For primitives, it will be zero for numeric types, false * for boolean, and the null character for char. */ public static T nullValue(final Class type) { final Object defaultValue; if (type == boolean.class) defaultValue = false; else if (type == byte.class) defaultValue = (byte) 0; else if (type == char.class) defaultValue = '\0'; else if (type == double.class) defaultValue = 0d; else if (type == float.class) defaultValue = 0f; else if (type == int.class) defaultValue = 0; else if (type == long.class) defaultValue = 0L; else if (type == short.class) defaultValue = (short) 0; else defaultValue = null; @SuppressWarnings("unchecked") final T result = (T) defaultValue; return result; } /** * Gets the field with the specified name, of the given class, or superclass * thereof. *

* Unlike {@link Class#getField(String)}, this method will return fields of * any visibility, not just {@code public}. And unlike * {@link Class#getDeclaredField(String)}, it will do so recursively, * returning the first field of the given name from the class's superclass * hierarchy. *

*

* Note that this method does not guarantee that the returned field is * accessible; if the field is not {@code public}, calling code will need to * use {@link Field#setAccessible(boolean)} in order to manipulate the field's * contents. *

* * @param c The class (or subclass thereof) containing the desired field. * @param name * @return The first field with the given name in the class's superclass * hierarchy. * @throws IllegalArgumentException if the specified class does not contain a * field with the given name */ public static Field field(final Class c, final String name) { if (c == null) throw iae(null, "No such field: %s", name); try { return c.getDeclaredField(name); } catch (final NoSuchFieldException e) { // Try the next class level up return field(c.getSuperclass(), name); } } /** * Gets the method with the specified name and argument types, of the given * class, or superclass thereof. *

* Unlike {@link Class#getMethod(String, Class[])}, this method will return * methods of any visibility, not just {@code public}. And unlike * {@link Class#getDeclaredMethod(String, Class[])}, it will do so * recursively, returning the first method of the given name and argument * types from the class's superclass hierarchy. *

*

* Note that this method does not guarantee that the returned method is * accessible; if the method is not {@code public}, calling code will need to * use {@link Method#setAccessible(boolean)} in order to invoke the method. *

* * @param c The class (or subclass thereof) containing the desired method. * @param name Name of the method. * @param parameterTypes Types of the method parameters. * @return The first method with the given name and argument types in the * class's superclass hierarchy. * @throws IllegalArgumentException If the specified class does not contain a * method with the given name and argument types. */ public static Method method(final Class c, final String name, final Class... parameterTypes) { if (c == null) throw iae(null, "No such field: %s", name); try { return c.getDeclaredMethod(name, parameterTypes); } catch (final NoSuchMethodException exc) { // NB: empty catch block } // Try the next class level up return method(c.getSuperclass(), name, parameterTypes); } /** * Gets the array class corresponding to the given element type. *

* For example, {@code arrayType(double.class)} returns {@code double[].class} * . *

* * @param componentType The type of elements which the array possesses * @throws IllegalArgumentException if the type cannot be the component type * of an array (this is the case e.g. for {@code void.class}). */ public static Class array(final Class componentType) { if (componentType == 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. return Array.newInstance(componentType, 0).getClass(); } /** * Gets the array class corresponding to the given element type and * dimensionality. *

* For example, {@code arrayType(double.class, 2)} returns * {@code double[][].class}. *

* * @param componentType The type of elements which the array possesses * @param dim The dimensionality of the array */ public static Class array(final Class componentType, final int dim) { if (dim < 0) throw iae(null, "Negative dimension"); if (dim == 0) return componentType; return array(array(componentType), dim - 1); } // -- 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. *

*/ 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 Comparisons.compare(name1, name2); } // -- Helper methods -- private static Class arrayOrNull(final Class componentType) { try { return Classes.array(componentType); } catch (final IllegalArgumentException exc) { return null; } } /** * Creates a new {@link IllegalArgumentException} with the given cause and * formatted message string. */ private static IllegalArgumentException iae(final Throwable cause, final String formattedMessage, final String... values) { final String s = String.format(formattedMessage, (Object[]) values); final IllegalArgumentException exc = new IllegalArgumentException(s); if (cause != null) exc.initCause(cause); return exc; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy