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

io.microsphere.util.ClassUtils Maven / Gradle / Ivy

The newest version!
/**
 *
 */
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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy