io.microsphere.util.ClassUtils Maven / Gradle / Ivy
/**
*
*/
package io.microsphere.util;
import io.microsphere.collection.CollectionUtils;
import io.microsphere.constants.FileConstants;
import io.microsphere.filter.ClassFileJarEntryFilter;
import io.microsphere.io.filter.FileExtensionFilter;
import io.microsphere.io.scanner.SimpleFileScanner;
import io.microsphere.io.scanner.SimpleJarEntryScanner;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static io.microsphere.collection.MapUtils.ofMap;
import static io.microsphere.collection.SetUtils.of;
import static io.microsphere.collection.SetUtils.ofSet;
import static io.microsphere.constants.FileConstants.CLASS;
import static io.microsphere.constants.FileConstants.CLASS_EXTENSION;
import static io.microsphere.constants.FileConstants.JAR;
import static io.microsphere.constants.PathConstants.SLASH;
import static io.microsphere.constants.SymbolConstants.DOT;
import static io.microsphere.io.FileUtils.resolveRelativePath;
import static io.microsphere.lang.function.Streams.filterAll;
import static io.microsphere.lang.function.ThrowableSupplier.execute;
import static io.microsphere.reflect.ConstructorUtils.findDeclaredConstructors;
import static io.microsphere.text.FormatUtils.format;
import static io.microsphere.util.ArrayUtils.EMPTY_CLASS_ARRAY;
import static io.microsphere.util.ArrayUtils.isEmpty;
import static io.microsphere.util.ArrayUtils.isNotEmpty;
import static io.microsphere.util.ArrayUtils.length;
import static io.microsphere.util.ClassPathUtils.getBootstrapClassPaths;
import static io.microsphere.util.ClassPathUtils.getClassPaths;
import static io.microsphere.util.StringUtils.isNotBlank;
import static io.microsphere.util.StringUtils.replace;
import static io.microsphere.util.StringUtils.startsWith;
import static io.microsphere.util.StringUtils.substringAfter;
import static io.microsphere.util.StringUtils.substringBefore;
import static io.microsphere.util.StringUtils.substringBeforeLast;
import static java.lang.reflect.Modifier.isAbstract;
import static java.lang.reflect.Modifier.isInterface;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.reverse;
import static java.util.Collections.synchronizedMap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
/**
* {@link Class} utility class
*
* @author Mercy
* @version 1.0.0
* @see ClassUtils
* @since 1.0.0
*/
public abstract class ClassUtils extends BaseUtils {
/**
* Suffix for array class names: "[]"
*/
public static final String ARRAY_SUFFIX = "[]";
/**
* @see {@link Class#ANNOTATION}
*/
private static final int ANNOTATION = 0x00002000;
/**
* @see {@link Class#ENUM}
*/
private static final int ENUM = 0x00004000;
/**
* @see {@link Class#SYNTHETIC}
*/
private static final int SYNTHETIC = 0x00001000;
/**
* Simple Types including:
*
* - {@link Void}
* - {@link Boolean}
* - {@link Character}
* - {@link Byte}
* - {@link Integer}
* - {@link Float}
* - {@link Double}
* - {@link String}
* - {@link BigDecimal}
* - {@link BigInteger}
* - {@link Date}
* - {@link Object}
*
*
* @see javax.management.openmbean.SimpleType
*/
public static final Set> SIMPLE_TYPES = of(
Void.class,
Boolean.class,
Character.class,
Byte.class,
Short.class,
Integer.class,
Long.class,
Float.class,
Double.class,
String.class,
BigDecimal.class,
BigInteger.class,
Date.class,
Object.class);
public static final Set> PRIMITIVE_TYPES = of(
Void.TYPE,
Boolean.TYPE,
Character.TYPE,
Byte.TYPE,
Short.TYPE,
Integer.TYPE,
Long.TYPE,
Float.TYPE,
Double.TYPE
);
/**
* Prefix for internal array class names: "[L"
*/
private static final String INTERNAL_ARRAY_PREFIX = "[L";
/**
* A map with primitive type name as key and corresponding primitive type as
* value, for example: "int" -> "int.class".
*/
private static final Map> PRIMITIVE_TYPE_NAME_MAP;
/**
* A map with primitive wrapper type as key and corresponding primitive type
* as value, for example: Integer.class -> int.class.
*/
private static final Map, Class>> PRIMITIVE_WRAPPER_TYPE_MAP;
/**
* A map with primitive type as key and its wrapper type
* as value, for example: int.class -> Integer.class.
*/
private static final Map, Class>> WRAPPER_PRIMITIVE_TYPE_MAP;
static final Map, Boolean> concreteClassCache = synchronizedMap(new WeakHashMap<>());
static {
PRIMITIVE_WRAPPER_TYPE_MAP = ofMap(
Void.class, Void.TYPE,
Boolean.class, Boolean.TYPE,
Byte.class, Byte.TYPE,
Character.class, Character.TYPE,
Short.class, Short.TYPE,
Integer.class, Integer.TYPE,
Long.class, Long.TYPE,
Float.class, Float.TYPE,
Double.class, Double.TYPE
);
}
static {
WRAPPER_PRIMITIVE_TYPE_MAP = ofMap(
Void.TYPE, Void.class,
Boolean.TYPE, Boolean.class,
Byte.TYPE, Byte.class,
Character.TYPE, Character.class,
Short.TYPE, Short.class,
Boolean.TYPE, Boolean.class,
Integer.TYPE, Integer.class,
Long.TYPE, Long.class,
Float.TYPE, Float.class,
Double.TYPE, Double.class
);
}
static {
Map> typeNamesMap = new HashMap<>(16);
List> primitiveTypeNames = new ArrayList<>(16);
primitiveTypeNames.addAll(asList(boolean.class, byte.class, char.class, double.class,
float.class, int.class, long.class, short.class));
primitiveTypeNames.addAll(asList(boolean[].class, byte[].class, char[].class, double[].class,
float[].class, int[].class, long[].class, short[].class));
for (Class> primitiveTypeName : primitiveTypeNames) {
typeNamesMap.put(primitiveTypeName.getName(), primitiveTypeName);
}
PRIMITIVE_TYPE_NAME_MAP = unmodifiableMap(typeNamesMap);
}
/**
* The specified type is array or not?
*
* It's an optimized alternative for {@link Class#isArray()}).
*
* @param type the type to test
* @return true
if the specified type is an array class,
* false
otherwise
* @see Class#isArray()
*/
public static boolean isArray(Class> type) {
return type != null && type.getName().startsWith("[");
}
/**
* Is the specified type a concrete class or not?
*
* @param type type to check
* @return true
if concrete class, false
otherwise.
*/
public static boolean isConcreteClass(Class> type) {
if (type == null) {
return false;
}
if (concreteClassCache.containsKey(type)) {
return true;
}
if (isGeneralClass(type, Boolean.FALSE)) {
concreteClassCache.put(type, Boolean.TRUE);
return true;
}
return false;
}
/**
* Is the specified type a abstract class or not?
*
*
* @param type the type
* @return true if type is a abstract class, false otherwise.
*/
public static boolean isAbstractClass(Class> type) {
return isGeneralClass(type, Boolean.TRUE);
}
/**
* Is the specified type a general class or not?
*
*
* @param type the type
* @return true if type is a general class, false otherwise.
*/
public static boolean isGeneralClass(Class> type) {
return isGeneralClass(type, null);
}
/**
* Is the specified type a general class or not?
*
* If isAbstract
== null
, it will not check type
is abstract or not.
*
* @param type the type
* @param isAbstract optional abstract flag
* @return true if type is a general (abstract) class, false otherwise.
*/
protected static boolean isGeneralClass(Class> type, Boolean isAbstract) {
if (type == null) {
return false;
}
int mod = type.getModifiers();
if (isInterface(mod)
|| isAnnotation(mod)
|| isEnum(mod)
|| isSynthetic(mod)
|| type.isPrimitive()
|| type.isArray()) {
return false;
}
if (isAbstract != null) {
return isAbstract(mod) == isAbstract.booleanValue();
}
return true;
}
public static boolean isTopLevelClass(Class> type) {
if (type == null) {
return false;
}
return !type.isLocalClass() && !type.isMemberClass();
}
/**
* The specified type is primitive type or simple type
*
* It's an optimized implementation for {@link Class#isPrimitive()}.
*
* @param type the type to test
* @return
* @see Class#isPrimitive()
*/
public static boolean isPrimitive(Class> type) {
return PRIMITIVE_TYPES.contains(type);
}
public static boolean isFinal(Class> type) {
return type != null && Modifier.isFinal(type.getModifiers());
}
/**
* The specified type is simple type or not
*
* @param type the type to test
* @return if type
is one element of {@link #SIMPLE_TYPES}, return true
, or false
* @see #SIMPLE_TYPES
*/
public static boolean isSimpleType(Class> type) {
return SIMPLE_TYPES.contains(type);
}
public static Object convertPrimitive(Class> type, String value) {
if (value == null) {
return null;
} else if (type == char.class || type == Character.class) {
return value.length() > 0 ? value.charAt(0) : '\0';
} else if (type == boolean.class || type == Boolean.class) {
return Boolean.valueOf(value);
}
try {
if (type == byte.class || type == Byte.class) {
return Byte.valueOf(value);
} else if (type == short.class || type == Short.class) {
return Short.valueOf(value);
} else if (type == int.class || type == Integer.class) {
return Integer.valueOf(value);
} else if (type == long.class || type == Long.class) {
return Long.valueOf(value);
} else if (type == float.class || type == Float.class) {
return Float.valueOf(value);
} else if (type == double.class || type == Double.class) {
return Double.valueOf(value);
}
} catch (NumberFormatException e) {
return null;
}
return value;
}
/**
* Resolve the primitive class from the specified type
*
* @param type the specified type
* @return null
if not found
*/
public static Class> resolvePrimitiveType(Class> type) {
if (isPrimitive(type)) {
return type;
}
return PRIMITIVE_WRAPPER_TYPE_MAP.get(type);
}
/**
* Resolve the wrapper class from the primitive type
*
* @param primitiveType the primitive type
* @return null
if not found
*/
public static Class> resolveWrapperType(Class> primitiveType) {
if (PRIMITIVE_WRAPPER_TYPE_MAP.containsKey(primitiveType)) {
return primitiveType;
}
return WRAPPER_PRIMITIVE_TYPE_MAP.get(primitiveType);
}
public static boolean isWrapperType(Class> type) {
return WRAPPER_PRIMITIVE_TYPE_MAP.containsKey(type);
}
public static boolean arrayTypeEquals(Class> oneArrayType, Class> anotherArrayType) {
if (!isArray(oneArrayType) || !isArray(anotherArrayType)) {
return false;
}
Class> oneComponentType = oneArrayType.getComponentType();
Class> anotherComponentType = anotherArrayType.getComponentType();
if (isArray(oneComponentType) && isArray(anotherComponentType)) {
return arrayTypeEquals(oneComponentType, anotherComponentType);
} else {
return Objects.equals(oneComponentType, anotherComponentType);
}
}
/**
* Resolve the given class name as primitive class, if appropriate,
* according to the JVM's naming rules for primitive classes.
*
* Also supports the JVM's internal class names for primitive arrays. Does
* not support the "[]" suffix notation for primitive arrays; this is
* only supported by {@link #forName}.
*
* @param name the name of the potentially primitive class
* @return the primitive class, or null
if the name does not
* denote a primitive class or primitive array class
*/
public static Class> resolvePrimitiveClassName(String name) {
Class> result = null;
// Most class names will be quite long, considering that they
// SHOULD sit in a package, so a length check is worthwhile.
if (name != null && name.length() <= 8) {
// Could be a primitive - likely.
result = PRIMITIVE_TYPE_NAME_MAP.get(name);
}
return result;
}
/**
* @param modifiers {@link Class#getModifiers()}
* @return true if this class's modifiers represents an annotation type; false otherwise
* @see Class#isAnnotation()
*/
public static boolean isAnnotation(int modifiers) {
return (modifiers & ANNOTATION) != 0;
}
/**
* @param modifiers {@link Class#getModifiers()}
* @return true if this class's modifiers represents an enumeration type; false otherwise
* @see Class#isEnum()
*/
public static boolean isEnum(int modifiers) {
return (modifiers & ENUM) != 0;
}
/**
* @param modifiers {@link Class#getModifiers()}
* @return true if this class's modifiers represents a synthetic type; false otherwise
* @see Class#isSynthetic()
*/
public static boolean isSynthetic(int modifiers) {
return (modifiers & SYNTHETIC) != 0;
}
/**
* Resolve package name under specified class name
*
* @param className class name
* @return package name
*/
@Nullable
public static String resolvePackageName(String className) {
return substringBeforeLast(className, ".");
}
/**
* Find all class names in class path
*
* @param classPath class path
* @param recursive is recursive on sub directories
* @return all class names in class path
*/
@Nonnull
public static Set findClassNamesInClassPath(String classPath, boolean recursive) {
File classesFileHolder = new File(classPath); // JarFile or Directory
if (classesFileHolder.isDirectory()) { //Directory
return findClassNamesInDirectory(classesFileHolder, recursive);
} else if (classesFileHolder.isFile() && classPath.endsWith(FileConstants.JAR_EXTENSION)) { //JarFile
return findClassNamesInJarFile(classesFileHolder, recursive);
}
return emptySet();
}
/**
* Find all class names in class path
*
* @param archiveFile JarFile or class patch directory
* @param recursive is recursive on sub directories
* @return all class names in class path
*/
public static Set findClassNamesInClassPath(File archiveFile, boolean recursive) {
if (archiveFile == null || !archiveFile.exists()) {
return emptySet();
}
if (archiveFile.isDirectory()) { // Directory
return findClassNamesInArchiveDirectory(archiveFile, recursive);
} else if (archiveFile.isFile() && archiveFile.getName().endsWith(JAR)) { //JarFile
return findClassNamesInArchiveFile(archiveFile, recursive);
}
return emptySet();
}
protected static Set findClassNamesInArchiveDirectory(File classesDirectory, boolean recursive) {
Set classNames = new LinkedHashSet<>();
SimpleFileScanner simpleFileScanner = SimpleFileScanner.INSTANCE;
Set classFiles = simpleFileScanner.scan(classesDirectory, recursive, FileExtensionFilter.of(CLASS_EXTENSION));
for (File classFile : classFiles) {
String className = resolveClassName(classesDirectory, classFile);
classNames.add(className);
}
return classNames;
}
protected static Set findClassNamesInArchiveFile(File jarFile, boolean recursive) {
Set classNames = new LinkedHashSet<>();
SimpleJarEntryScanner simpleJarEntryScanner = SimpleJarEntryScanner.INSTANCE;
try {
JarFile jarFile_ = new JarFile(jarFile);
Set jarEntries = simpleJarEntryScanner.scan(jarFile_, recursive, ClassFileJarEntryFilter.INSTANCE);
for (JarEntry jarEntry : jarEntries) {
String jarEntryName = jarEntry.getName();
String className = resolveClassName(jarEntryName);
if (isNotBlank(className)) {
classNames.add(className);
}
}
} catch (Exception ignored) {
}
return classNames;
}
protected static Set findClassNamesInDirectory(File classesDirectory, boolean recursive) {
Set classNames = new LinkedHashSet();
SimpleFileScanner simpleFileScanner = SimpleFileScanner.INSTANCE;
Set classFiles = simpleFileScanner.scan(classesDirectory, recursive, FileExtensionFilter.of(CLASS));
for (File classFile : classFiles) {
String className = resolveClassName(classesDirectory, classFile);
classNames.add(className);
}
return classNames;
}
protected static Set findClassNamesInJarFile(File jarFile, boolean recursive) {
if (!jarFile.exists()) {
return emptySet();
}
Set classNames = new LinkedHashSet();
SimpleJarEntryScanner simpleJarEntryScanner = SimpleJarEntryScanner.INSTANCE;
try {
JarFile jarFile_ = new JarFile(jarFile);
Set jarEntries = simpleJarEntryScanner.scan(jarFile_, recursive, ClassFileJarEntryFilter.INSTANCE);
for (JarEntry jarEntry : jarEntries) {
String jarEntryName = jarEntry.getName();
String className = resolveClassName(jarEntryName);
if (isNotBlank(className)) {
classNames.add(className);
}
}
} catch (Exception e) {
}
return classNames;
}
protected static String resolveClassName(File classesDirectory, File classFile) {
String classFileRelativePath = resolveRelativePath(classesDirectory, classFile);
return resolveClassName(classFileRelativePath);
}
/**
* Resolve resource name to class name
*
* @param resourceName resource name
* @return class name
*/
public static String resolveClassName(String resourceName) {
String className = replace(resourceName, SLASH, DOT);
className = substringBefore(className, CLASS_EXTENSION);
while (startsWith(className, DOT)) {
className = substringAfter(className, DOT);
}
return className;
}
/**
* Get all super classes from the specified type
*
* @param type the specified type
* @param classFilters the filters for classes
* @return non-null read-only {@link Set}
*/
public static Set> getAllSuperClasses(Class> type, Predicate>... classFilters) {
Set> allSuperClasses = new LinkedHashSet<>();
Class> superClass = type.getSuperclass();
while (superClass != null) {
// add current super class
allSuperClasses.add(superClass);
superClass = superClass.getSuperclass();
}
return unmodifiableSet(filterAll(allSuperClasses, classFilters));
}
/**
* Get all interfaces from the specified type
*
* @param type the specified type
* @param interfaceFilters the filters for interfaces
* @return non-null read-only {@link Set}
*/
public static Set> getAllInterfaces(Class> type, Predicate>... interfaceFilters) {
if (type == null || type.isPrimitive()) {
return emptySet();
}
Set> allInterfaces = new LinkedHashSet<>();
Set> resolved = new LinkedHashSet<>();
Queue> waitResolve = new LinkedList<>();
resolved.add(type);
Class> clazz = type;
while (clazz != null) {
Class>[] interfaces = clazz.getInterfaces();
if (isNotEmpty(interfaces)) {
// add current interfaces
Arrays.stream(interfaces).filter(resolved::add).forEach(cls -> {
allInterfaces.add(cls);
waitResolve.add(cls);
});
}
// add all super classes to waitResolve
getAllSuperClasses(clazz).stream().filter(resolved::add).forEach(waitResolve::add);
clazz = waitResolve.poll();
}
return filterAll(allInterfaces, interfaceFilters);
}
/**
* Get all inherited types from the specified type
*
* @param type the specified type
* @param typeFilters the filters for types
* @return non-null read-only {@link Set}
*/
public static Set> getAllInheritedTypes(Class> type, Predicate>... typeFilters) {
// Add all super classes
Set> types = new LinkedHashSet<>(getAllSuperClasses(type, typeFilters));
// Add all interface classes
types.addAll(getAllInterfaces(type, typeFilters));
return unmodifiableSet(types);
}
/**
* the semantics is same as {@link Class#isAssignableFrom(Class)}
*
* @param superType the super type
* @param targetType the target type
* @return see {@link Class#isAssignableFrom(Class)}
*/
public static boolean isAssignableFrom(Class> superType, Class> targetType) {
// any argument is null
if (superType == null || targetType == null) {
return false;
}
// equals
if (Objects.equals(superType, targetType)) {
return true;
}
// isAssignableFrom
return superType.isAssignableFrom(targetType);
}
/**
* Is generic class or not?
*
* @param type the target type
* @return if the target type is not null or void
or Void.class, return true
, or false
*/
public static boolean isGenericClass(Class> type) {
return type != null && !void.class.equals(type) && !Void.class.equals(type);
}
/**
* Resolve the types of the specified values
*
* @param values the values
* @return If can't be resolved, return {@link ArrayUtils#EMPTY_CLASS_ARRAY empty class array}
*/
public static Class[] getTypes(Object... values) {
if (isEmpty(values)) {
return EMPTY_CLASS_ARRAY;
}
int size = values.length;
Class[] types = new Class[size];
for (int i = 0; i < size; i++) {
Object value = values[i];
types[i] = value == null ? null : value.getClass();
}
return types;
}
/**
* Get the name of the specified type
*
* @param type the specified type
* @return non-null
*/
public static String getTypeName(Class> type) {
if (type.isArray()) {
try {
Class> cl = type;
int dimensions = 0;
while (cl.isArray()) {
dimensions++;
cl = cl.getComponentType();
}
String name = getTypeName(cl);
StringBuilder sb = new StringBuilder(name.length() + dimensions * 2);
sb.append(name);
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
return sb.toString();
} catch (Throwable e) {
}
}
return type.getName();
}
/**
* Get the simple name of the specified type
*
* @param type the specified type
* @return non-null
*/
public static String getSimpleName(Class> type) {
boolean array = type.isArray();
return getSimpleName(type, array);
}
private static String getSimpleName(Class> type, boolean array) {
if (array) {
return getSimpleName(type.getComponentType()) + "[]";
}
String simpleName = type.getName();
Class> enclosingClass = type.getEnclosingClass();
if (enclosingClass == null) { // top level class
simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
} else {
String ecName = enclosingClass.getName();
simpleName = simpleName.substring(ecName.length());
// Remove leading "\$[0-9]*" from the name
int length = simpleName.length();
if (length < 1 || simpleName.charAt(0) != '$') throw new InternalError("Malformed class name");
int index = 1;
while (index < length && isAsciiDigit(simpleName.charAt(index))) index++;
// Eventually, this is the empty string iff this is an anonymous class
return simpleName.substring(index);
}
return simpleName;
}
private static boolean isAsciiDigit(char c) {
return '0' <= c && c <= '9';
}
/**
* Get all classes from the specified type with filters
*
* @param type the specified type
* @param classFilters class filters
* @return non-null read-only {@link Set}
*/
public static Set> getAllClasses(Class> type, Predicate>... classFilters) {
return getAllClasses(type, true, classFilters);
}
/**
* Get all classes(may include self type) from the specified type with filters
*
* @param type the specified type
* @param includedSelf included self type or not
* @param classFilters class filters
* @return non-null read-only {@link Set}
*/
public static Set> getAllClasses(Class> type, boolean includedSelf, Predicate>... classFilters) {
if (type == null || type.isPrimitive()) {
return emptySet();
}
List> allClasses = new LinkedList<>();
Class> superClass = type.getSuperclass();
while (superClass != null) {
// add current super class
allClasses.add(superClass);
superClass = superClass.getSuperclass();
}
// FIFO -> FILO
reverse(allClasses);
if (includedSelf) {
allClasses.add(type);
}
// Keep the same order from List
return ofSet(filterAll(allClasses, classFilters));
}
/**
* the semantics is same as {@link Class#isAssignableFrom(Class)}
*
* @param targetType the target type
* @param superTypes the super types
* @return see {@link Class#isAssignableFrom(Class)}
* @since 1.0.0
*/
public static boolean isDerived(Class> targetType, Class>... superTypes) {
// any argument is null
if (superTypes == null || superTypes.length == 0 || targetType == null) {
return false;
}
boolean derived = false;
for (Class> superType : superTypes) {
if (isAssignableFrom(superType, targetType)) {
derived = true;
break;
}
}
return derived;
}
public static T newInstance(Class type, Object... args) {
int length = length(args);
List> constructors = findDeclaredConstructors(type, constructor -> {
Class>[] parameterTypes = constructor.getParameterTypes();
if (length != parameterTypes.length) {
return false;
}
for (int i = 0; i < length; i++) {
Object arg = args[i];
Class> parameterType = parameterTypes[i];
if (!parameterType.isInstance(arg)) {
return false;
}
}
return true;
});
if (constructors.isEmpty()) {
String message = format("No constructor[class : '{}'] matches the arguments : {}", getTypeName(type), Arrays.asList(args));
throw new IllegalArgumentException(message);
}
Constructor constructor = (Constructor) constructors.get(0);
return execute(() -> constructor.newInstance(args));
}
public static Class> getTopComponentType(Object array) {
return array == null ? null : getTopComponentType(array.getClass());
}
public static Class> getTopComponentType(Class> arrayType) {
if (!isArray(arrayType)) {
return null;
}
Class> targetType = null;
Class> componentType = arrayType.getComponentType();
while (componentType != null) {
targetType = componentType;
componentType = getTopComponentType(componentType);
}
return targetType;
}
/**
* Cast the given object to the specified type
*
* @param object the object
* @param castType the type to cast
* @param the type to cast
* @return the casted instance if and only if object
is an instance of castType
,
* null
otherwise
*/
public static T cast(Object object, Class castType) {
if (object == null || castType == null) {
return null;
}
return castType.isInstance(object) ? castType.cast(object) : null;
}
}