com.blr19c.falowp.bot.system.utils.ClassUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of falowp-bot-system Show documentation
Show all versions of falowp-bot-system Show documentation
FalowpBot system infrastructure
The newest version!
package com.blr19c.falowp.bot.system.utils
import java.beans.Introspector
import java.io.Closeable
import java.io.Externalizable
import java.io.Serializable
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.Proxy
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* util来源于spring
*/
@Suppress("MemberVisibilityCanBePrivate", "UNUSED")
object ClassUtils {
/**
* Suffix for array class names: `"[]"`.
*/
const val ARRAY_SUFFIX = "[]"
/**
* Prefix for internal array class names: `"["`.
*/
private const val INTERNAL_ARRAY_PREFIX = "["
/**
* Prefix for internal non-primitive array class names: `"[L"`.
*/
private const val NON_PRIMITIVE_ARRAY_PREFIX = "[L"
/**
* A reusable empty class array constant.
*/
private val EMPTY_CLASS_ARRAY = arrayOf>()
/**
* The package separator character: `'.'`.
*/
private const val PACKAGE_SEPARATOR = '.'
/**
* The path separator character: `'/'`.
*/
private const val PATH_SEPARATOR = '/'
/**
* The nested class separator character: `'$'`.
*/
private const val NESTED_CLASS_SEPARATOR = '$'
/**
* The CGLIB class separator: `"$$"`.
*/
const val CGLIB_CLASS_SEPARATOR = "$$"
/**
* The ".class" file suffix.
*/
const val CLASS_FILE_SUFFIX = ".class"
/**
* Map with primitive wrapper type as key and corresponding primitive
*/
private val primitiveWrapperTypeMap: MutableMap, Class<*>?> = IdentityHashMap(9)
/**
* Map with primitive type as key and corresponding wrapper
*/
private val primitiveTypeToWrapperMap: MutableMap?, Class<*>> = IdentityHashMap(9)
/**
* Map with primitive type name as key and corresponding primitive
* type as value, for example: "int" -> "int.class".
*/
private val primitiveTypeNameMap: MutableMap?> = HashMap(32)
/**
* Map with common Java language class name as key and corresponding Class as value.
* Primarily for efficient deserialization of remote invocations.
*/
private val commonClassCache: MutableMap> = HashMap(64)
/**
* Common Java language interfaces which are supposed to be ignored
* when searching for 'primary' user-level interfaces.
*/
private var javaLanguageInterfaces: Set>? = null
/**
* Cache for equivalent methods on an interface implemented by the declaring class.
*/
private val interfaceMethodCache: MutableMap = ConcurrentHashMap(256)
init {
primitiveWrapperTypeMap[Boolean::class.java] = Boolean::class.javaPrimitiveType
primitiveWrapperTypeMap[Byte::class.java] = Byte::class.javaPrimitiveType
primitiveWrapperTypeMap[Char::class.java] = Char::class.javaPrimitiveType
primitiveWrapperTypeMap[Double::class.java] = Double::class.javaPrimitiveType
primitiveWrapperTypeMap[Float::class.java] = Float::class.javaPrimitiveType
primitiveWrapperTypeMap[Int::class.java] = Int::class.javaPrimitiveType
primitiveWrapperTypeMap[Long::class.java] = Long::class.javaPrimitiveType
primitiveWrapperTypeMap[Short::class.java] = Short::class.javaPrimitiveType
primitiveWrapperTypeMap[Void::class.java] = Void.TYPE
// Map entry iteration is less expensive to initialize than forEach with lambdas
for ((key, value) in primitiveWrapperTypeMap) {
primitiveTypeToWrapperMap[value] = key
registerCommonClasses(key)
}
val primitiveTypes: MutableSet?> = HashSet(32)
primitiveTypes.addAll(primitiveWrapperTypeMap.values)
Collections.addAll(
primitiveTypes,
BooleanArray::class.java,
ByteArray::class.java,
CharArray::class.java,
DoubleArray::class.java,
FloatArray::class.java,
IntArray::class.java,
LongArray::class.java,
ShortArray::class.java
)
for (primitiveType in primitiveTypes) {
primitiveTypeNameMap[primitiveType!!.getName()] = primitiveType
}
registerCommonClasses(
Array::class.java, Array::class.java, Array::class.java, Array::class.java,
Array::class.java, Array::class.java, Array::class.java, Array::class.java
)
registerCommonClasses(
Number::class.java, Array::class.java, String::class.java, Array::class.java,
Class::class.java, Array::class.java, Any::class.java, Array::class.java
)
registerCommonClasses(
Throwable::class.java, Exception::class.java, RuntimeException::class.java,
Error::class.java, StackTraceElement::class.java, Array::class.java
)
registerCommonClasses(
Enum::class.java,
Iterable::class.java,
MutableIterator::class.java,
Enumeration::class.java,
MutableCollection::class.java,
MutableList::class.java,
MutableSet::class.java,
MutableMap::class.java,
MutableMap.MutableEntry::class.java,
Optional::class.java
)
val javaLanguageInterfaceArray = arrayOf(
Serializable::class.java, Externalizable::class.java,
Closeable::class.java, AutoCloseable::class.java, Cloneable::class.java, Comparable::class.java
)
registerCommonClasses(*javaLanguageInterfaceArray)
javaLanguageInterfaces = HashSet(listOf(*javaLanguageInterfaceArray))
}
/**
* Register the given common classes with the ClassUtils cache.
*/
private fun registerCommonClasses(vararg commonClasses: Class<*>) {
for (clazz in commonClasses) {
commonClassCache[clazz.getName()] = clazz
}
}
val defaultClassLoader: ClassLoader?
/**
* Return the default ClassLoader to use: typically the thread context
* ClassLoader, if available; the ClassLoader that loaded the ClassUtils
* class will be used as fallback.
*
* Call this method if you intend to use the thread context ClassLoader
* in a scenario where you clearly prefer a non-null ClassLoader reference:
* for example, for class path resource loading (but not necessarily for
* `Class.forName`, which accepts a `null` ClassLoader
* reference as well).
*
* @return the default ClassLoader (only `null` if even the system
* ClassLoader isn't accessible)
* @see Thread.getContextClassLoader
* @see ClassLoader.getSystemClassLoader
*/
get() {
var cl: ClassLoader? = null
try {
cl = Thread.currentThread().getContextClassLoader()
} catch (ex: Throwable) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils::class.java.getClassLoader()
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader()
} catch (ex: Throwable) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl
}
/**
* Override the thread context ClassLoader with the environment's bean ClassLoader
* if necessary, i.e. if the bean ClassLoader is not equivalent to the thread
* context ClassLoader already.
*
* @param classLoaderToUse the actual ClassLoader to use for the thread context
* @return the original thread context ClassLoader, or `null` if not overridden
*/
fun overrideThreadContextClassLoader(classLoaderToUse: ClassLoader?): ClassLoader? {
val currentThread = Thread.currentThread()
val threadContextClassLoader = currentThread.getContextClassLoader()
return if (classLoaderToUse != null && classLoaderToUse != threadContextClassLoader) {
currentThread.setContextClassLoader(classLoaderToUse)
threadContextClassLoader
} else {
null
}
}
/**
* Replacement for `Class.forName()` that also returns Class instances
* for primitives (e.g. "int") and array class names (e.g. "String[]").
* Furthermore, it is also capable of resolving nested class names in Java source
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
*
* @param name the name of the Class
* @param classLoader the class loader to use
* @return a class instance for the supplied name
* @throws ClassNotFoundException if the class was not found
* @throws LinkageError if the class file could not be loaded
* @see Class.forName
*/
@Throws(ClassNotFoundException::class, LinkageError::class)
fun forName(name: String, classLoader: ClassLoader?): Class<*> {
var clazz = resolvePrimitiveClassName(name)
if (clazz == null) {
clazz = commonClassCache[name]
}
if (clazz != null) {
return clazz
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
val elementClassName = name.substring(0, name.length - ARRAY_SUFFIX.length)
val elementClass = forName(elementClassName, classLoader)
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
}
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
val elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length, name.length - 1)
val elementClass = forName(elementName, classLoader)
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
}
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
val elementName = name.substring(INTERNAL_ARRAY_PREFIX.length)
val elementClass = forName(elementName, classLoader)
return java.lang.reflect.Array.newInstance(elementClass, 0).javaClass
}
var clToUse = classLoader
if (clToUse == null) {
clToUse = defaultClassLoader
}
return try {
Class.forName(name, false, clToUse)
} catch (ex: ClassNotFoundException) {
val lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR)
if (lastDotIndex != -1) {
val nestedClassName =
name.substring(0, lastDotIndex) + NESTED_CLASS_SEPARATOR + name.substring(lastDotIndex + 1)
try {
return Class.forName(nestedClassName, false, clToUse)
} catch (ex2: ClassNotFoundException) {
// Swallow - let original exception get through
}
}
throw ex
}
}
/**
* Resolve the given class name into a Class instance. Supports
* primitives (like "int") and array class names (like "String[]").
*
* This is effectively equivalent to the `forName`
* method with the same arguments, with the only difference being
* the exceptions thrown in case of class loading failure.
*
* @param className the name of the Class
* @param classLoader the class loader to use
* @return a class instance for the supplied name
* @throws IllegalArgumentException if the class name was not resolvable
* (that is, the class could not be found or the class file could not be loaded)
* @throws IllegalStateException if the corresponding class is resolvable but
* there was a readability mismatch in the inheritance hierarchy of the class
* (typically a missing dependency declaration in a Jigsaw module definition
* for a superclass or interface implemented by the class to be loaded here)
* @see .forName
*/
@Throws(IllegalArgumentException::class)
fun resolveClassName(className: String, classLoader: ClassLoader?): Class<*> {
return try {
forName(className, classLoader)
} catch (err: IllegalAccessError) {
throw IllegalStateException(
"Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.message, err
)
} catch (err: LinkageError) {
throw IllegalArgumentException("Unresolvable class definition for class [$className]", err)
} catch (ex: ClassNotFoundException) {
throw IllegalArgumentException("Could not find class [$className]", ex)
}
}
/**
* Determine whether the [Class] identified by the supplied name is present
* and can be loaded. Will return `false` if either the class or
* one of its dependencies is not present or cannot be loaded.
*
* @param className the name of the class to check
* @param classLoader the class loader to use
* (maybe `null` which indicates the default class loader)
* @return whether the specified class is present (including all of its
* superclasses and interfaces)
* @throws IllegalStateException if the corresponding class is resolvable but
* there was a readability mismatch in the inheritance hierarchy of the class
* (typically a missing dependency declaration in a Jigsaw module definition
* for a superclass or interface implemented by the class to be checked here)
*/
fun isPresent(className: String, classLoader: ClassLoader?): Boolean {
return try {
forName(className, classLoader)
true
} catch (err: IllegalAccessError) {
throw IllegalStateException(
"Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.message, err
)
} catch (ex: Throwable) {
// Typically ClassNotFoundException or NoClassDefFoundError...
false
}
}
/**
* Check whether the given class is visible in the given ClassLoader.
*
* @param clazz the class to check (typically an interface)
* @param classLoader the ClassLoader to check against
* (maybe `null` in which case this method will always return `true`)
*/
fun isVisible(clazz: Class<*>, classLoader: ClassLoader?): Boolean {
if (classLoader == null) {
return true
}
try {
if (clazz.getClassLoader() === classLoader) {
return true
}
} catch (ex: SecurityException) {
// Fall through to loadable check below
}
// Visible if same Class can be loaded from given ClassLoader
return isLoadable(clazz, classLoader)
}
/**
* Check whether the given class is cache-safe in the given context,
* i.e. whether it is loaded by the given ClassLoader or a parent of it.
*
* @param clazz the class to analyze
* @param classLoader the ClassLoader to potentially cache metadata in
* (maybe `null` which indicates the system class loader)
*/
fun isCacheSafe(clazz: Class<*>, classLoader: ClassLoader?): Boolean {
try {
var target = clazz.getClassLoader()
// Common cases
if (target === classLoader || target == null) {
return true
}
if (classLoader == null) {
return false
}
// Check for match in ancestors -> positive
var current = classLoader
while (current != null) {
current = current.getParent()
if (current === target) {
return true
}
}
// Check for match in children -> negative
while (target != null) {
target = target.getParent()
if (target === classLoader) {
return false
}
}
} catch (ex: SecurityException) {
// Fall through to loadable check below
}
// Fallback for ClassLoaders without parent/child relationship:
// safe if same Class can be loaded from given ClassLoader
return classLoader != null && isLoadable(clazz, classLoader)
}
/**
* Check whether the given class is loadable in the given ClassLoader.
*
* @param clazz the class to check (typically an interface)
* @param classLoader the ClassLoader to check against
* @since 5.0.6
*/
private fun isLoadable(clazz: Class<*>, classLoader: ClassLoader): Boolean {
return try {
clazz == classLoader.loadClass(clazz.getName())
// Else: different class with same name found
} catch (ex: ClassNotFoundException) {
// No corresponding class found at all
false
}
}
/**
* Resolve the given class name as primitive class, if appropriate,
* Does *not* support the "[]" suffix notation for primitive arrays;
* this is only supported by [.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
*/
fun resolvePrimitiveClassName(name: String?): Class<*>? {
var result: Class<*>? = 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 <= 7) {
// Could be a primitive - likely.
result = primitiveTypeNameMap[name]
}
return result
}
/**
* Check if the given class represents a primitive wrapper,
* i.e. Boolean, Byte, Character, Short, Integer, Long, Float, Double, or
* Void.
*
* @param clazz the class to check
* @return whether the given class is a primitive wrapper class
*/
fun isPrimitiveWrapper(clazz: Class<*>): Boolean {
return primitiveWrapperTypeMap.containsKey(clazz)
}
/**
* Check if the given class represents a primitive (i.e. boolean, byte,
* char, short, int, long, float, or double), `void`, or a wrapper for
* those types (i.e. Boolean, Byte, Character, Short, Integer, Long, Float,
* Double, or Void).
*
* @param clazz the class to check
* @return `true` if the given class represents a primitive, void, or
* a wrapper class
*/
fun isPrimitiveOrWrapper(clazz: Class<*>): Boolean {
return clazz.isPrimitive || isPrimitiveWrapper(clazz)
}
/**
* Check if the given class represents an array of primitives,
* i.e. boolean, byte, char, short, int, long, float, or double.
*
* @param clazz the class to check
* @return whether the given class is a primitive array class
*/
fun isPrimitiveArray(clazz: Class<*>): Boolean {
return clazz.isArray && clazz.componentType.isPrimitive
}
/**
* Check if the given class represents an array of primitive wrappers,
* i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
*
* @param clazz the class to check
* @return whether the given class is a primitive wrapper array class
*/
fun isPrimitiveWrapperArray(clazz: Class<*>): Boolean {
return clazz.isArray && isPrimitiveWrapper(clazz.componentType)
}
/**
* Resolve the given class if it is a primitive class,
* returning the corresponding primitive wrapper type instead.
*
* @param clazz the class to check
* @return the original class, or a primitive wrapper for the original primitive type
*/
fun resolvePrimitiveIfNecessary(clazz: Class<*>): Class<*> {
return if (clazz.isPrimitive && clazz != Void.TYPE) primitiveTypeToWrapperMap[clazz]!! else clazz
}
/**
* Check if the right-hand side type maybe assigned to the left-hand side
* type, assuming setting by reflection. Considers primitive wrapper
* classes as assignable to the corresponding primitive types.
*
* @param lhsType the target type
* @param rhsType the value type that should be assigned to the target type
* @return if the target type is assignable from the value type
*/
fun isAssignable(lhsType: Class<*>, rhsType: Class<*>): Boolean {
if (lhsType.isAssignableFrom(rhsType)) {
return true
}
return if (lhsType.isPrimitive) {
val resolvedPrimitive = primitiveWrapperTypeMap[rhsType]
lhsType == resolvedPrimitive
} else {
val resolvedWrapper = primitiveTypeToWrapperMap[rhsType]
resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)
}
}
/**
* Determine if the given type is assignable from the given value,
* assuming setting by reflection. Considers primitive wrapper classes
* as assignable to the corresponding primitive types.
*
* @param type the target type
* @param value the value that should be assigned to the type
* @return if the type is assignable from the value
*/
fun isAssignableValue(type: Class<*>, value: Any?): Boolean {
return if (value != null) isAssignable(type, value.javaClass) else !type.isPrimitive
}
/**
* Convert a "/"-based resource path to a "."-based fully qualified class name.
*
* @param resourcePath the resource path pointing to a class
* @return the corresponding fully qualified class name
*/
fun convertResourcePathToClassName(resourcePath: String): String {
return resourcePath.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR)
}
/**
* Convert a "."-based fully qualified class name to a "/"-based resource path.
*
* @param className the fully qualified class name
* @return the corresponding resource path, pointing to the class
*/
fun convertClassNameToResourcePath(className: String): String {
return className.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR)
}
/**
* Return a path suitable for use with `ClassLoader.getResource`
* (also suitable for use with `Class.getResource` by prepending a
* slash ('/') to the return value). Built by taking the package of the specified
* class file, converting all dots ('.') to slashes ('/'), adding a trailing slash
* if necessary, and concatenating the specified resource name to this.
*
As such, this function maybe used to build a path suitable for
* loading a resource file that is in the same package as a class file,
*
* @param clazz the Class whose package will be used as the base
* @param resourceName the resource name to append. A leading slash is optional.
* @return the built-up resource path
* @see ClassLoader.getResource
*
* @see Class.getResource
*/
fun addResourcePathToPackagePath(clazz: Class<*>?, resourceName: String): String {
return if (!resourceName.startsWith("/")) {
classPackageAsResourcePath(clazz) + '/' + resourceName
} else classPackageAsResourcePath(clazz) + resourceName
}
/**
* Given an input class object, return a string which consists of the
* class's package name as a pathname, i.e., all dots ('.') are replaced by
* slashes ('/'). Neither a leading nor trailing slash is added. The result
* could be concatenated with a slash and the name of a resource and fed
* directly to `ClassLoader.getResource()`. For it to be fed to
* `Class.getResource` instead, a leading slash would also have
* to be prepended to the returned value.
*
* @param clazz the input class. A `null` value or the default
* (empty) package will result in an empty string ("") being returned.
* @return a path which represents the package name
* @see ClassLoader.getResource
*
* @see Class.getResource
*/
fun classPackageAsResourcePath(clazz: Class<*>?): String {
if (clazz == null) {
return ""
}
val className = clazz.getName()
val packageEndIndex = className.lastIndexOf(PACKAGE_SEPARATOR)
if (packageEndIndex == -1) {
return ""
}
val packageName = className.substring(0, packageEndIndex)
return packageName.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR)
}
/**
* Build a String that consists of the names of the classes/interfaces
* in the given array.
*
* Basically like `AbstractCollection.toString()`, but stripping
* the "class "/"interface " prefix before every class name.
*
* @param classes an array of Class objects
* @return a String of form "[com.foo.Bar, com.foo.Baz]"
* @see java.util.AbstractCollection.toString
*/
fun classNamesToString(vararg classes: Class<*>?): String {
return classNamesToString(listOf(*classes))
}
fun classNamesToString(classes: Collection?>): String {
if (classes.isEmpty()) {
return "[]"
}
val stringJoiner = StringJoiner(", ", "[", "]")
for (clazz in classes) {
stringJoiner.add(clazz!!.getName())
}
return stringJoiner.toString()
}
/**
* Copy the given `Collection` into a `Class` array.
*
* The `Collection` must contain `Class` elements only.
*
* @param collection the `Collection` to copy
* @return the `Class` array
*
* @since 3.1
*/
fun toClassArray(collection: Collection?>): Array> {
return if (collection.isNotEmpty()) collection.filterNotNull().toTypedArray() else EMPTY_CLASS_ARRAY
}
/**
* Return all interfaces that the given instance implements as an array,
* including ones implemented by superclasses.
*
* @param instance the instance to analyze for interfaces
* @return all interfaces that the given instance implements as an array
*/
fun getAllInterfaces(instance: Any): Array> {
return getAllInterfacesForClass(instance.javaClass)
}
/**
* Return all interfaces that the given class implements as an array,
* including ones implemented by superclasses.
*
* If the class itself is an interface, it gets returned as sole interface.
*
* @param clazz the class to analyze for interfaces
* @return all interfaces that the given object implements as an array
*/
fun getAllInterfacesForClass(clazz: Class<*>): Array> {
return getAllInterfacesForClass(clazz, null)
}
/**
* Return all interfaces that the given class implements as an array,
* including ones implemented by superclasses.
*
* If the class itself is an interface, it gets returned as sole interface.
*
* @param clazz the class to analyze for interfaces
* @param classLoader the ClassLoader that the interfaces need to be visible in
* (maybe `null` when accepting all declared interfaces)
* @return all interfaces that the given object implements as an array
*/
fun getAllInterfacesForClass(clazz: Class<*>, classLoader: ClassLoader?): Array> {
return toClassArray(getAllInterfacesForClassAsSet(clazz, classLoader))
}
/**
* Return all interfaces that the given instance implements as a Set,
* including ones implemented by superclasses.
*
* @param instance the instance to analyze for interfaces
* @return all interfaces that the given instance implements as a Set
*/
fun getAllInterfacesAsSet(instance: Any): Set?> {
return getAllInterfacesForClassAsSet(instance.javaClass)
}
/**
* Return all interfaces that the given class implements as a Set,
* including ones implemented by superclasses.
*
* If the class itself is an interface, it gets returned as sole interface.
*
* @param clazz the class to analyze for interfaces
* @return all interfaces that the given object implements as a Set
*/
fun getAllInterfacesForClassAsSet(clazz: Class<*>): Set?> {
return getAllInterfacesForClassAsSet(clazz, null)
}
/**
* Return all interfaces that the given class implements as a Set,
* including ones implemented by superclasses.
*
* If the class itself is an interface, it gets returned as sole interface.
*
* @param clazz the class to analyze for interfaces
* @param classLoader the ClassLoader that the interfaces need to be visible in
* (maybe `null` when accepting all declared interfaces)
* @return all interfaces that the given object implements as a Set
*/
fun getAllInterfacesForClassAsSet(clazz: Class<*>, classLoader: ClassLoader?): Set?> {
if (clazz.isInterface && isVisible(clazz, classLoader)) {
return setOf(clazz)
}
val interfaces: MutableSet?> = LinkedHashSet()
var current: Class<*>? = clazz
while (current != null) {
val cs = current.interfaces
for (ifc in cs) {
if (isVisible(ifc, classLoader)) {
interfaces.add(ifc)
}
}
current = current.superclass
}
return interfaces
}
/**
* Determine the common ancestor of the given classes, if any.
*
* @param clazz1 the class to introspect
* @param clazz2 the other class to introspect
* @return the common ancestor (i.e. common superclass, one interface
* extending the other), or `null` if none found. If any of the
* given classes is `null`, the other class will be returned.
* @since 3.2.6
*/
fun determineCommonAncestor(clazz1: Class<*>?, clazz2: Class<*>?): Class<*>? {
if (clazz1 == null) {
return clazz2
}
if (clazz2 == null) {
return clazz1
}
if (clazz1.isAssignableFrom(clazz2)) {
return clazz1
}
if (clazz2.isAssignableFrom(clazz1)) {
return clazz2
}
var ancestor = clazz1
do {
ancestor = ancestor!!.superclass
if (ancestor == null || Any::class.java == ancestor) {
return null
}
} while (!ancestor!!.isAssignableFrom(clazz2))
return ancestor
}
fun isJavaLanguageInterface(ifc: Class<*>): Boolean {
return javaLanguageInterfaces!!.contains(ifc)
}
/**
* Determine if the supplied class is an *inner class*,
* i.e. a non-static member of an enclosing class.
*
* @return `true` if the supplied class is an inner class
* @see Class.isMemberClass
* @since 5.0.5
*/
fun isInnerClass(clazz: Class<*>): Boolean {
return clazz.isMemberClass && !Modifier.isStatic(clazz.modifiers)
}
/**
* Return the user-defined class for the given instance: usually simply
* the class of the given instance, but the original class in case of a
* CGLIB-generated subclass.
*
* @param instance the instance to check
* @return the user-defined class
*/
fun getUserClass(instance: Any): Class<*> {
return getUserClass(instance.javaClass)
}
/**
* Return the user-defined class for the given class: usually simply the given
* class, but the original class in case of a CGLIB-generated subclass.
*
* @param clazz the class to check
* @return the user-defined class
*/
fun getUserClass(clazz: Class<*>): Class<*> {
if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
val superclass = clazz.superclass
if (superclass != null && superclass != Any::class.java) {
return superclass
}
}
return clazz
}
/**
* Return a descriptive name for the given object's type: usually simply
* the class name, but component type class name + "[]" for arrays,
* and an appended list of implemented interfaces for JDK proxies.
*
* @param value the value to introspect
* @return the qualified name of the class
*/
fun getDescriptiveType(value: Any?): String? {
if (value == null) {
return null
}
val clazz: Class<*> = value.javaClass
return if (Proxy.isProxyClass(clazz)) {
val prefix = clazz.getName() + " implementing "
val result = StringJoiner(",", prefix, "")
for (ifc in clazz.interfaces) {
result.add(ifc.getName())
}
result.toString()
} else {
clazz.getTypeName()
}
}
/**
* Check whether the given class matches the user-specified type name.
*
* @param clazz the class to check
* @param typeName the type name to match
*/
fun matchesTypeName(clazz: Class<*>, typeName: String?): Boolean {
return typeName != null &&
(typeName == clazz.getTypeName() || typeName == clazz.getSimpleName())
}
/**
* Get the class name without the qualified package name.
*
* @param className the className to get the short name for
* @return the class name of the class without the package name
* @throws IllegalArgumentException if the className is empty
*/
fun getShortName(className: String): String {
val lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR)
var nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR)
if (nameEndIndex == -1) {
nameEndIndex = className.length
}
var shortName = className.substring(lastDotIndex + 1, nameEndIndex)
shortName = shortName.replace(NESTED_CLASS_SEPARATOR, PACKAGE_SEPARATOR)
return shortName
}
/**
* Get the class name without the qualified package name.
*
* @param clazz the class to get the short name for
* @return the class name of the class without the package name
*/
fun getShortName(clazz: Class<*>): String {
return getShortName(getQualifiedName(clazz))
}
/**
* Return the short string name of a Java class in uncapitalized JavaBeans
* property format. Strips the outer class name in case of a nested class.
*
* @param clazz the class
* @return the short name rendered in a standard JavaBeans property format
*/
fun getShortNameAsProperty(clazz: Class<*>): String {
var shortName = getShortName(clazz)
val dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR)
shortName = if (dotIndex != -1) shortName.substring(dotIndex + 1) else shortName
return Introspector.decapitalize(shortName)
}
/**
* Determine the name of the class file, relative to the containing
* package: e.g. "String.class"
*
* @param clazz the class
* @return the file name of the ".class" file
*/
fun getClassFileName(clazz: Class<*>): String {
val className = clazz.getName()
val lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR)
return className.substring(lastDotIndex + 1) + CLASS_FILE_SUFFIX
}
/**
* Determine the name of the package of the given class,
* e.g. "java.lang" for the `java.lang.String` class.
*
* @param clazz the class
* @return the package name, or the empty String if the class
* is defined in the default package
*/
fun getPackageName(clazz: Class<*>): String {
return getPackageName(clazz.getName())
}
/**
* Determine the name of the package of the given fully-qualified class name,
* e.g. "java.lang" for the `java.lang.String` class name.
*
* @param fqClassName the fully-qualified class name
* @return the package name, or the empty String if the class
* is defined in the default package
*/
fun getPackageName(fqClassName: String): String {
val lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR)
return if (lastDotIndex != -1) fqClassName.substring(0, lastDotIndex) else ""
}
/**
* Return the qualified name of the given class: usually simply
* the class name, but component type class name + "[]" for arrays.
*
* @param clazz the class
* @return the qualified name of the class
*/
fun getQualifiedName(clazz: Class<*>): String {
return clazz.getTypeName()
}
/**
* Return the qualified name of the given method, consisting of
* fully qualified interface/class name + "." + method name.
*
* @param method the method
* @return the qualified name of the method
*/
fun getQualifiedMethodName(method: Method): String {
return getQualifiedMethodName(method, null)
}
/**
* Return the qualified name of the given method, consisting of
* fully qualified interface/class name + "." + method name.
*
* @param method the method
* @param clazz the clazz that the method is being invoked on
* (maybe `null` to indicate the method's declaring class)
* @return the qualified name of the method
* @since 4.3.4
*/
fun getQualifiedMethodName(method: Method, clazz: Class<*>?): String {
return (clazz ?: method.declaringClass).getName() + '.' + method.name
}
/**
* Determine whether the given class has a public constructor with the given signature.
*
* Essentially translates `NoSuchMethodException` to "false".
*
* @param clazz the clazz to analyze
* @param paramTypes the parameter types of the method
* @return whether the class has a corresponding constructor
* @see Class.getConstructor
*/
fun hasConstructor(clazz: Class<*>, vararg paramTypes: Class<*>?): Boolean {
return getConstructorIfAvailable(clazz, *paramTypes) != null
}
/**
* Determine whether the given class has a public constructor with the given signature,
* and return it if available (else return `null`).
*
* Essentially translates `NoSuchMethodException` to `null`.
*
* @param clazz the clazz to analyze
* @param paramTypes the parameter types of the method
* @return the constructor, or `null` if not found
* @see Class.getConstructor
*/
fun getConstructorIfAvailable(clazz: Class, vararg paramTypes: Class<*>?): Constructor? {
return try {
clazz.getConstructor(*paramTypes)
} catch (ex: NoSuchMethodException) {
null
}
}
/**
* Determine whether the given class has a public method with the given signature.
*
* @param clazz the clazz to analyze
* @param method the method to look for
* @return whether the class has a corresponding method
* @since 5.2.3
*/
fun hasMethod(clazz: Class<*>, method: Method): Boolean {
if (clazz == method.declaringClass) {
return true
}
val methodName = method.name
val paramTypes = method.parameterTypes
return getMethodOrNull(clazz, methodName, paramTypes) != null
}
/**
* Determine whether the given class has a public method with the given signature.
*
* Essentially translates `NoSuchMethodException` to "false".
*
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* @return whether the class has a corresponding method
* @see Class.getMethod
*/
fun hasMethod(clazz: Class<*>, methodName: String, vararg paramTypes: Class<*>): Boolean {
return getMethodIfAvailable(clazz, methodName, *paramTypes) != null
}
/**
* Determine whether the given class has a public method with the given signature,
* and return it if available (else throws an `IllegalStateException`).
*
* In case of any signature specified, only returns the method if there is a
* unique candidate, i.e. a single public method with the specified name.
*
* Essentially translates `NoSuchMethodException` to `IllegalStateException`.
*
* @param clazz the clazz to analyze
* @param methodName the name of the method
* @param paramTypes the parameter types of the method
* @return the method (never `null`)
* @throws IllegalStateException if the method has not been found
* @see Class.getMethod
*/
fun getMethod(clazz: Class<*>, methodName: String, vararg paramTypes: Class<*>): Method {
return if (paramTypes.isEmpty()) {
try {
clazz.getMethod(methodName, *paramTypes)
} catch (ex: NoSuchMethodException) {
throw IllegalStateException("Expected method not found: $ex")
}
} else {
val candidates = findMethodCandidatesByName(clazz, methodName)
if (candidates.size == 1) {
candidates.iterator().next()
} else if (candidates.isEmpty()) {
throw IllegalStateException("Expected method not found: " + clazz.getName() + '.' + methodName)
} else {
throw IllegalStateException("No unique method found: " + clazz.getName() + '.' + methodName)
}
}
}
fun getMethodIfAvailable(clazz: Class<*>, methodName: String, vararg paramTypes: Class<*>): Method? {
return if (paramTypes.isEmpty()) {
getMethodOrNull(clazz, methodName, arrayOf(*paramTypes))
} else {
val candidates = findMethodCandidatesByName(clazz, methodName)
if (candidates.size == 1) {
candidates.iterator().next()
} else null
}
}
/**
* Return the number of methods with a given name (with any argument types),
* for the given class and/or its superclasses. Includes non-public methods.
*
* @param clazz the clazz to check
* @param methodName the name of the method
* @return the number of methods with the given name
*/
fun getMethodCountForName(clazz: Class<*>, methodName: String): Int {
var count = 0
val declaredMethods = clazz.getDeclaredMethods()
for (method in declaredMethods) {
if (methodName == method.name) {
count++
}
}
val cs = clazz.interfaces
for (ifc in cs) {
count += getMethodCountForName(ifc, methodName)
}
if (clazz.superclass != null) {
count += getMethodCountForName(clazz.superclass, methodName)
}
return count
}
/**
* Does the given class or one of its superclasses at least have one or more
* methods with the supplied name (with any argument types)?
* Includes non-public methods.
*
* @param clazz the clazz to check
* @param methodName the name of the method
* @return whether there is at least one method with the given name
*/
fun hasAtLeastOneMethodWithName(clazz: Class<*>, methodName: String): Boolean {
val declaredMethods = clazz.getDeclaredMethods()
for (method in declaredMethods) {
if (method.name == methodName) {
return true
}
}
val cs = clazz.interfaces
for (ifc in cs) {
if (hasAtLeastOneMethodWithName(ifc, methodName)) {
return true
}
}
return clazz.superclass != null && hasAtLeastOneMethodWithName(clazz.superclass, methodName)
}
/**
* Determine a corresponding interface method for the given method handle, if possible.
*
* This is particularly useful for arriving at a public exported type on Jigsaw
* which can be reflectively invoked without an illegal access warning.
*
* @param method the method to be invoked, potentially from an implementation class
* @return the corresponding interface method, or the original method if none found
* @see .getMostSpecificMethod
*
* @since 5.1
*/
fun getInterfaceMethodIfPossible(method: Method): Method {
return if (!Modifier.isPublic(method.modifiers) || method.declaringClass.isInterface) {
method
} else interfaceMethodCache.computeIfAbsent(method) { key: Method ->
var current = key.declaringClass
while (current != Any::class.java) {
val cs = current.interfaces
for (ifc in cs) {
try {
return@computeIfAbsent ifc.getMethod(key.name, *key.parameterTypes)
} catch (ex: NoSuchMethodException) {
// ignore
}
}
current = current.superclass
}
key
}
}
/**
* Determine whether the given method is declared by the user or at least pointing to
* a user-declared method.
*
* Checks [Method.isSynthetic] (for implementation methods) as well as the
* `GroovyObject` interface (for interface methods; on an implementation class,
* implementations of the `GroovyObject` methods will be marked as synthetic anyway).
* Note that, despite being synthetic, bridge methods ([Method.isBridge]) are considered
* as user-level methods since they are eventually pointing to a user-declared generic method.
*
* @param method the method to check
* @return `true` if the method can be considered as user-declared; `false` otherwise
*/
fun isUserLevelMethod(method: Method): Boolean {
return method.isBridge || !method.isSynthetic && !isGroovyObjectMethod(method)
}
private fun isGroovyObjectMethod(method: Method): Boolean {
return method.declaringClass.getName() == "groovy.lang.GroovyObject"
}
/**
* Determine whether the given method is overridable in the given target class.
*
* @param method the method to check
* @param targetClass the target class to check against
*/
private fun isOverridable(method: Method, targetClass: Class<*>?): Boolean {
if (Modifier.isPrivate(method.modifiers)) {
return false
}
return if (Modifier.isPublic(method.modifiers) || Modifier.isProtected(method.modifiers)) {
true
} else targetClass == null || getPackageName(method.declaringClass) == getPackageName(
targetClass
)
}
/**
* Return a public static method of a class.
*
* @param clazz the class which defines the method
* @param methodName the static method name
* @param args the parameter types to the method
* @return the static method, or `null` if no static method was found
* @throws IllegalArgumentException if the method name is blank or the clazz is null
*/
fun getStaticMethod(clazz: Class<*>, methodName: String, vararg args: Class<*>): Method? {
return try {
val method = clazz.getMethod(methodName, *args)
if (Modifier.isStatic(method.modifiers)) method else null
} catch (ex: NoSuchMethodException) {
null
}
}
private fun getMethodOrNull(clazz: Class<*>, methodName: String, paramTypes: Array>): Method? {
return try {
clazz.getMethod(methodName, *paramTypes)
} catch (ex: NoSuchMethodException) {
null
}
}
private fun findMethodCandidatesByName(clazz: Class<*>, methodName: String): Set {
val candidates: MutableSet = HashSet(1)
val methods = clazz.getMethods()
for (method in methods) {
if (methodName == method.name) {
candidates.add(method)
}
}
return candidates
}
}