diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 52853dd19ad..8cbd7024cba 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -303,7 +303,9 @@ public abstract class ClassUtils { * (that is, the class could not be found or the class file could not be loaded) * @see #forName(String, ClassLoader) */ - public static Class resolveClassName(String className, @Nullable ClassLoader classLoader) throws IllegalArgumentException { + public static Class resolveClassName(String className, @Nullable ClassLoader classLoader) + throws IllegalArgumentException { + try { return forName(className, classLoader); } @@ -315,6 +317,89 @@ public abstract class ClassUtils { } } + /** + * Determine whether the {@link Class} identified by the supplied name is present + * and can be loaded. Will return {@code 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 + * (may be {@code null} which indicates the default class loader) + * @return whether the specified class is present + */ + public static boolean isPresent(String className, @Nullable ClassLoader classLoader) { + try { + forName(className, classLoader); + return true; + } + catch (Throwable ex) { + // Class or one of its dependencies is not present... + return 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 + * (may be {@code null} in which case this method will always return {@code true}) + */ + public static boolean isVisible(Class clazz, @Nullable ClassLoader classLoader) { + if (classLoader == null) { + return true; + } + try { + return (clazz == classLoader.loadClass(clazz.getName())); + // Else: different class with same name found + } + catch (ClassNotFoundException ex) { + // No corresponding class found at all + return false; + } + } + + /** + * 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 + * (may be {@code null} which indicates the system class loader) + */ + public static boolean isCacheSafe(Class clazz, @Nullable ClassLoader classLoader) { + Assert.notNull(clazz, "Class must not be null"); + try { + ClassLoader target = clazz.getClassLoader(); + // Common cases + if (target == classLoader || target == null) { + return true; + } + if (classLoader == null) { + return false; + } + // Check for match in ancestors -> positive + ClassLoader 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 (SecurityException ex) { + // Fall through to Class reference comparison below + } + + // Fallback for ClassLoaders without parent/child relationship: + // safe if same Class can be loaded from given ClassLoader + return (classLoader != null && isVisible(clazz, classLoader)); + } + /** * Resolve the given class name as primitive class, if appropriate, * according to the JVM's naming rules for primitive classes. @@ -338,23 +423,406 @@ public abstract class ClassUtils { } /** - * Determine whether the {@link Class} identified by the supplied name is present - * and can be loaded. Will return {@code 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 - * (may be {@code null}, which indicates the default class loader) - * @return whether the specified class is present + * Check if the given class represents a primitive wrapper, + * 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 class */ - public static boolean isPresent(String className, @Nullable ClassLoader classLoader) { - try { - forName(className, classLoader); + public static boolean isPrimitiveWrapper(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + return primitiveWrapperTypeMap.containsKey(clazz); + } + + /** + * Check if the given class represents a primitive (i.e. boolean, byte, + * char, short, int, long, float, or double) or a primitive wrapper + * (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 or primitive wrapper class + */ + public static boolean isPrimitiveOrWrapper(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + 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 + */ + public static boolean isPrimitiveArray(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + return (clazz.isArray() && clazz.getComponentType().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 + */ + public static boolean isPrimitiveWrapperArray(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + return (clazz.isArray() && isPrimitiveWrapper(clazz.getComponentType())); + } + + /** + * 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 + */ + public static Class resolvePrimitiveIfNecessary(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + return (clazz.isPrimitive() && clazz != void.class ? primitiveTypeToWrapperMap.get(clazz) : clazz); + } + + /** + * Check if the right-hand side type may be 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 + * @see TypeUtils#isAssignable + */ + public static boolean isAssignable(Class lhsType, Class rhsType) { + Assert.notNull(lhsType, "Left-hand side type must not be null"); + Assert.notNull(rhsType, "Right-hand side type must not be null"); + if (lhsType.isAssignableFrom(rhsType)) { return true; } - catch (Throwable ex) { - // Class or one of its dependencies is not present... - return false; + if (lhsType.isPrimitive()) { + Class resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); + if (lhsType == resolvedPrimitive) { + return true; + } } + else { + Class resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); + if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { + return true; + } + } + return false; + } + + /** + * 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 + */ + public static boolean isAssignableValue(Class type, @Nullable Object value) { + Assert.notNull(type, "Type must not be null"); + return (value != null ? isAssignable(type, value.getClass()) : !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 + */ + public static String convertResourcePathToClassName(String resourcePath) { + Assert.notNull(resourcePath, "Resource path must not be null"); + 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 + */ + public static String convertClassNameToResourcePath(String className) { + Assert.notNull(className, "Class name must not be null"); + return className.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR); + } + + /** + * Return a path suitable for use with {@code ClassLoader.getResource} + * (also suitable for use with {@code 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 may be used to build a path suitable for + * loading a resource file that is in the same package as a class file, + * although {@link org.springframework.core.io.ClassPathResource} is usually + * even more convenient. + * @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 + */ + public static String addResourcePathToPackagePath(Class clazz, String resourceName) { + Assert.notNull(resourceName, "Resource name must not be null"); + if (!resourceName.startsWith("/")) { + return classPackageAsResourcePath(clazz) + '/' + resourceName; + } + return 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 {@code ClassLoader.getResource()}. For it to be fed to + * {@code Class.getResource} instead, a leading slash would also have + * to be prepended to the returned value. + * @param clazz the input class. A {@code 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 + */ + public static String classPackageAsResourcePath(@Nullable Class clazz) { + if (clazz == null) { + return ""; + } + String className = clazz.getName(); + int packageEndIndex = className.lastIndexOf(PACKAGE_SEPARATOR); + if (packageEndIndex == -1) { + return ""; + } + String 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 {@code 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() + */ + public static String classNamesToString(Class... classes) { + return classNamesToString(Arrays.asList(classes)); + } + + /** + * Build a String that consists of the names of the classes/interfaces + * in the given collection. + *

Basically like {@code AbstractCollection.toString()}, but stripping + * the "class "/"interface " prefix before every class name. + * @param classes a Collection of Class objects (may be {@code null}) + * @return a String of form "[com.foo.Bar, com.foo.Baz]" + * @see java.util.AbstractCollection#toString() + */ + public static String classNamesToString(@Nullable Collection> classes) { + if (CollectionUtils.isEmpty(classes)) { + return "[]"; + } + StringBuilder sb = new StringBuilder("["); + for (Iterator> it = classes.iterator(); it.hasNext(); ) { + Class clazz = it.next(); + sb.append(clazz.getName()); + if (it.hasNext()) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + /** + * Copy the given {@code Collection} into a {@code Class} array. + *

The {@code Collection} must contain {@code Class} elements only. + * @param collection the {@code Collection} to copy + * @return the {@code Class} array + * @since 3.1 + * @see StringUtils#toStringArray + */ + public static Class[] toClassArray(Collection> collection) { + return collection.toArray(new Class[0]); + } + + /** + * 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 + */ + public static Class[] getAllInterfaces(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + return getAllInterfacesForClass(instance.getClass()); + } + + /** + * 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 + */ + public static Class[] getAllInterfacesForClass(Class clazz) { + 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 + * (may be {@code null} when accepting all declared interfaces) + * @return all interfaces that the given object implements as an array + */ + public static Class[] getAllInterfacesForClass(Class clazz, @Nullable ClassLoader classLoader) { + 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 + */ + public static Set> getAllInterfacesAsSet(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + return getAllInterfacesForClassAsSet(instance.getClass()); + } + + /** + * 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 + */ + public static Set> getAllInterfacesForClassAsSet(Class clazz) { + 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 + * (may be {@code null} when accepting all declared interfaces) + * @return all interfaces that the given object implements as a Set + */ + public static Set> getAllInterfacesForClassAsSet(Class clazz, @Nullable ClassLoader classLoader) { + Assert.notNull(clazz, "Class must not be null"); + if (clazz.isInterface() && isVisible(clazz, classLoader)) { + return Collections.>singleton(clazz); + } + Set> interfaces = new LinkedHashSet<>(); + Class current = clazz; + while (current != null) { + Class[] ifcs = current.getInterfaces(); + for (Class ifc : ifcs) { + interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader)); + } + current = current.getSuperclass(); + } + return interfaces; + } + + /** + * Create a composite interface Class for the given interfaces, + * implementing the given interfaces in one single Class. + *

This implementation builds a JDK proxy class for the given interfaces. + * @param interfaces the interfaces to merge + * @param classLoader the ClassLoader to create the composite Class in + * @return the merged interface as Class + * @see java.lang.reflect.Proxy#getProxyClass + */ + @SuppressWarnings("deprecation") + public static Class createCompositeInterface(Class[] interfaces, @Nullable ClassLoader classLoader) { + Assert.notEmpty(interfaces, "Interfaces must not be empty"); + return Proxy.getProxyClass(classLoader, 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 {@code null} if none found. If any of the + * given classes is {@code null}, the other class will be returned. + * @since 3.2.6 + */ + @Nullable + public static Class determineCommonAncestor(@Nullable Class clazz1, @Nullable Class clazz2) { + if (clazz1 == null) { + return clazz2; + } + if (clazz2 == null) { + return clazz1; + } + if (clazz1.isAssignableFrom(clazz2)) { + return clazz1; + } + if (clazz2.isAssignableFrom(clazz1)) { + return clazz2; + } + Class ancestor = clazz1; + do { + ancestor = ancestor.getSuperclass(); + if (ancestor == null || Object.class == ancestor) { + return null; + } + } + while (!ancestor.isAssignableFrom(clazz2)); + return ancestor; + } + + /** + * Determine whether the given interface is a common Java language interface: + * {@link Serializable}, {@link Externalizable}, {@link Closeable}, {@link AutoCloseable}, + * {@link Cloneable}, {@link Comparable} - all of which can be ignored when looking + * for 'primary' user-level interfaces. Common characteristics: no service-level + * operations, no bean property methods, no default methods. + * @param ifc the interface to check + * @since 5.0.3 + */ + public static boolean isJavaLanguageInterface(Class ifc) { + return javaLanguageInterfaces.contains(ifc); + } + + /** + * Determine if the supplied class is an inner class, + * i.e. a non-static member of an enclosing class. + * @return {@code true} if the supplied class is an inner class + * @since 5.0.5 + * @see Class#isMemberClass() + */ + public static boolean isInnerClass(Class clazz) { + return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())); + } + + /** + * Check whether the given object is a CGLIB proxy. + * @param object the object to check + * @see #isCglibProxyClass(Class) + * @see org.springframework.aop.support.AopUtils#isCglibProxy(Object) + */ + public static boolean isCglibProxy(Object object) { + return isCglibProxyClass(object.getClass()); + } + + /** + * Check whether the specified class is a CGLIB-generated class. + * @param clazz the class to check + * @see #isCglibProxyClassName(String) + */ + public static boolean isCglibProxyClass(@Nullable Class clazz) { + return (clazz != null && isCglibProxyClassName(clazz.getName())); + } + + /** + * Check whether the specified class name is a CGLIB-generated class. + * @param className the class name to check + */ + public static boolean isCglibProxyClassName(@Nullable String className) { + return (className != null && className.contains(CGLIB_CLASS_SEPARATOR)); } /** @@ -386,48 +854,45 @@ public abstract class ClassUtils { } /** - * Determine if the supplied class is an inner class, - * i.e. a non-static member of an enclosing class. - * @return {@code true} if the supplied class is an inner class - * @since 5.0.5 - * @see Class#isMemberClass() + * 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 */ - public static boolean isInnerClass(Class clazz) { - return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())); + @Nullable + public static String getDescriptiveType(@Nullable Object value) { + if (value == null) { + return null; + } + Class clazz = value.getClass(); + if (Proxy.isProxyClass(clazz)) { + StringBuilder result = new StringBuilder(clazz.getName()); + result.append(" implementing "); + Class[] ifcs = clazz.getInterfaces(); + for (int i = 0; i < ifcs.length; i++) { + result.append(ifcs[i].getName()); + if (i < ifcs.length - 1) { + result.append(','); + } + } + return result.toString(); + } + else { + return clazz.getTypeName(); + } } /** - * 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 + * Check whether the given class matches the user-specified type name. + * @param clazz the class to check + * @param typeName the type name to match */ - public static boolean isCacheSafe(Class clazz, @Nullable ClassLoader classLoader) { - Assert.notNull(clazz, "Class must not be null"); - try { - ClassLoader target = clazz.getClassLoader(); - if (target == null) { - return true; - } - ClassLoader cur = classLoader; - if (cur == target) { - return true; - } - while (cur != null) { - cur = cur.getParent(); - if (cur == target) { - return true; - } - } - return false; - } - catch (SecurityException ex) { - // Probably from the system ClassLoader - let's consider it safe. - return true; - } + public static boolean matchesTypeName(Class clazz, @Nullable String typeName) { + return (typeName != null && + (typeName.equals(clazz.getTypeName()) || typeName.equals(clazz.getSimpleName()))); } - /** * Get the class name without the qualified package name. * @param className the className to get the short name for @@ -542,47 +1007,6 @@ public abstract class ClassUtils { return (clazz != null ? clazz : method.getDeclaringClass()).getName() + '.' + method.getName(); } - /** - * 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 - */ - @Nullable - public static String getDescriptiveType(@Nullable Object value) { - if (value == null) { - return null; - } - Class clazz = value.getClass(); - if (Proxy.isProxyClass(clazz)) { - StringBuilder result = new StringBuilder(clazz.getName()); - result.append(" implementing "); - Class[] ifcs = clazz.getInterfaces(); - for (int i = 0; i < ifcs.length; i++) { - result.append(ifcs[i].getName()); - if (i < ifcs.length - 1) { - result.append(','); - } - } - return result.toString(); - } - else { - return 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 - */ - public static boolean matchesTypeName(Class clazz, @Nullable String typeName) { - return (typeName != null && - (typeName.equals(clazz.getTypeName()) || typeName.equals(clazz.getSimpleName()))); - } - - /** * Determine whether the given class has a public constructor with the given signature. *

Essentially translates {@code NoSuchMethodException} to "false". @@ -868,419 +1292,4 @@ public abstract class ClassUtils { } } - - /** - * Check if the given class represents a primitive wrapper, - * 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 class - */ - public static boolean isPrimitiveWrapper(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return primitiveWrapperTypeMap.containsKey(clazz); - } - - /** - * Check if the given class represents a primitive (i.e. boolean, byte, - * char, short, int, long, float, or double) or a primitive wrapper - * (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 or primitive wrapper class - */ - public static boolean isPrimitiveOrWrapper(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - 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 - */ - public static boolean isPrimitiveArray(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isArray() && clazz.getComponentType().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 - */ - public static boolean isPrimitiveWrapperArray(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isArray() && isPrimitiveWrapper(clazz.getComponentType())); - } - - /** - * 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 - */ - public static Class resolvePrimitiveIfNecessary(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isPrimitive() && clazz != void.class ? primitiveTypeToWrapperMap.get(clazz) : clazz); - } - - /** - * Check if the right-hand side type may be 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 - * @see TypeUtils#isAssignable - */ - public static boolean isAssignable(Class lhsType, Class rhsType) { - Assert.notNull(lhsType, "Left-hand side type must not be null"); - Assert.notNull(rhsType, "Right-hand side type must not be null"); - if (lhsType.isAssignableFrom(rhsType)) { - return true; - } - if (lhsType.isPrimitive()) { - Class resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); - if (lhsType == resolvedPrimitive) { - return true; - } - } - else { - Class resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); - if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { - return true; - } - } - return false; - } - - /** - * 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 - */ - public static boolean isAssignableValue(Class type, @Nullable Object value) { - Assert.notNull(type, "Type must not be null"); - return (value != null ? isAssignable(type, value.getClass()) : !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 - */ - public static String convertResourcePathToClassName(String resourcePath) { - Assert.notNull(resourcePath, "Resource path must not be null"); - 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 - */ - public static String convertClassNameToResourcePath(String className) { - Assert.notNull(className, "Class name must not be null"); - return className.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR); - } - - /** - * Return a path suitable for use with {@code ClassLoader.getResource} - * (also suitable for use with {@code 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 may be used to build a path suitable for - * loading a resource file that is in the same package as a class file, - * although {@link org.springframework.core.io.ClassPathResource} is usually - * even more convenient. - * @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 - */ - public static String addResourcePathToPackagePath(Class clazz, String resourceName) { - Assert.notNull(resourceName, "Resource name must not be null"); - if (!resourceName.startsWith("/")) { - return classPackageAsResourcePath(clazz) + '/' + resourceName; - } - return 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 {@code ClassLoader.getResource()}. For it to be fed to - * {@code Class.getResource} instead, a leading slash would also have - * to be prepended to the returned value. - * @param clazz the input class. A {@code 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 - */ - public static String classPackageAsResourcePath(@Nullable Class clazz) { - if (clazz == null) { - return ""; - } - String className = clazz.getName(); - int packageEndIndex = className.lastIndexOf(PACKAGE_SEPARATOR); - if (packageEndIndex == -1) { - return ""; - } - String 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 {@code 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() - */ - public static String classNamesToString(Class... classes) { - return classNamesToString(Arrays.asList(classes)); - } - - /** - * Build a String that consists of the names of the classes/interfaces - * in the given collection. - *

Basically like {@code AbstractCollection.toString()}, but stripping - * the "class "/"interface " prefix before every class name. - * @param classes a Collection of Class objects (may be {@code null}) - * @return a String of form "[com.foo.Bar, com.foo.Baz]" - * @see java.util.AbstractCollection#toString() - */ - public static String classNamesToString(@Nullable Collection> classes) { - if (CollectionUtils.isEmpty(classes)) { - return "[]"; - } - StringBuilder sb = new StringBuilder("["); - for (Iterator> it = classes.iterator(); it.hasNext(); ) { - Class clazz = it.next(); - sb.append(clazz.getName()); - if (it.hasNext()) { - sb.append(", "); - } - } - sb.append("]"); - return sb.toString(); - } - - /** - * Copy the given {@code Collection} into a {@code Class} array. - *

The {@code Collection} must contain {@code Class} elements only. - * @param collection the {@code Collection} to copy - * @return the {@code Class} array - * @since 3.1 - * @see StringUtils#toStringArray - */ - public static Class[] toClassArray(Collection> collection) { - return collection.toArray(new Class[0]); - } - - /** - * 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 - */ - public static Class[] getAllInterfaces(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - return getAllInterfacesForClass(instance.getClass()); - } - - /** - * 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 - */ - public static Class[] getAllInterfacesForClass(Class clazz) { - 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 - * (may be {@code null} when accepting all declared interfaces) - * @return all interfaces that the given object implements as an array - */ - public static Class[] getAllInterfacesForClass(Class clazz, @Nullable ClassLoader classLoader) { - 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 - */ - public static Set> getAllInterfacesAsSet(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - return getAllInterfacesForClassAsSet(instance.getClass()); - } - - /** - * 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 - */ - public static Set> getAllInterfacesForClassAsSet(Class clazz) { - 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 - * (may be {@code null} when accepting all declared interfaces) - * @return all interfaces that the given object implements as a Set - */ - public static Set> getAllInterfacesForClassAsSet(Class clazz, @Nullable ClassLoader classLoader) { - Assert.notNull(clazz, "Class must not be null"); - if (clazz.isInterface() && isVisible(clazz, classLoader)) { - return Collections.>singleton(clazz); - } - Set> interfaces = new LinkedHashSet<>(); - Class current = clazz; - while (current != null) { - Class[] ifcs = current.getInterfaces(); - for (Class ifc : ifcs) { - interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader)); - } - current = current.getSuperclass(); - } - return interfaces; - } - - /** - * Create a composite interface Class for the given interfaces, - * implementing the given interfaces in one single Class. - *

This implementation builds a JDK proxy class for the given interfaces. - * @param interfaces the interfaces to merge - * @param classLoader the ClassLoader to create the composite Class in - * @return the merged interface as Class - * @see java.lang.reflect.Proxy#getProxyClass - */ - @SuppressWarnings("deprecation") - public static Class createCompositeInterface(Class[] interfaces, @Nullable ClassLoader classLoader) { - Assert.notEmpty(interfaces, "Interfaces must not be empty"); - return Proxy.getProxyClass(classLoader, 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 {@code null} if none found. If any of the - * given classes is {@code null}, the other class will be returned. - * @since 3.2.6 - */ - @Nullable - public static Class determineCommonAncestor(@Nullable Class clazz1, @Nullable Class clazz2) { - if (clazz1 == null) { - return clazz2; - } - if (clazz2 == null) { - return clazz1; - } - if (clazz1.isAssignableFrom(clazz2)) { - return clazz1; - } - if (clazz2.isAssignableFrom(clazz1)) { - return clazz2; - } - Class ancestor = clazz1; - do { - ancestor = ancestor.getSuperclass(); - if (ancestor == null || Object.class == ancestor) { - return null; - } - } - while (!ancestor.isAssignableFrom(clazz2)); - return ancestor; - } - - /** - * 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 (may be {@code null}, - * in which case this method will always return {@code true}) - */ - public static boolean isVisible(Class clazz, @Nullable ClassLoader classLoader) { - if (classLoader == null) { - return true; - } - try { - Class actualClass = classLoader.loadClass(clazz.getName()); - return (clazz == actualClass); - // Else: different interface class found... - } - catch (ClassNotFoundException ex) { - // No interface class found... - return false; - } - } - - /** - * Determine whether the given interface is a common Java language interface: - * {@link Serializable}, {@link Externalizable}, {@link Closeable}, {@link AutoCloseable}, - * {@link Cloneable}, {@link Comparable} - all of which can be ignored when looking - * for 'primary' user-level interfaces. Common characteristics: no service-level - * operations, no bean property methods, no default methods. - * @param ifc the interface to check - * @since 5.0.3 - */ - public static boolean isJavaLanguageInterface(Class ifc) { - return javaLanguageInterfaces.contains(ifc); - } - - /** - * Check whether the given object is a CGLIB proxy. - * @param object the object to check - * @see #isCglibProxyClass(Class) - * @see org.springframework.aop.support.AopUtils#isCglibProxy(Object) - */ - public static boolean isCglibProxy(Object object) { - return isCglibProxyClass(object.getClass()); - } - - /** - * Check whether the specified class is a CGLIB-generated class. - * @param clazz the class to check - * @see #isCglibProxyClassName(String) - */ - public static boolean isCglibProxyClass(@Nullable Class clazz) { - return (clazz != null && isCglibProxyClassName(clazz.getName())); - } - - /** - * Check whether the specified class name is a CGLIB-generated class. - * @param className the class name to check - */ - public static boolean isCglibProxyClassName(@Nullable String className) { - return (className != null && className.contains(CGLIB_CLASS_SEPARATOR)); - } - } diff --git a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java index 8786363c681..5d783df70d9 100644 --- a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java @@ -16,6 +16,7 @@ package org.springframework.util; +import java.io.Externalizable; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -44,20 +45,21 @@ import static org.junit.Assert.*; * @author Rob Harrop * @author Rick Evans */ -@SuppressWarnings({ "rawtypes", "unchecked" }) public class ClassUtilsTests { private ClassLoader classLoader = getClass().getClassLoader(); + @Before - public void setUp() { + public void clearStatics() { InnerClass.noArgCalled = false; InnerClass.argCalled = false; InnerClass.overloadedCalled = false; } + @Test - public void testIsPresent() throws Exception { + public void testIsPresent() { assertTrue(ClassUtils.isPresent("java.lang.String", classLoader)); assertFalse(ClassUtils.isPresent("java.lang.MySpecialString", classLoader)); } @@ -114,6 +116,36 @@ public class ClassUtilsTests { assertEquals(double[].class, ClassUtils.forName(double[].class.getName(), classLoader)); } + @Test + public void testIsCacheSafe() { + ClassLoader childLoader1 = new ClassLoader(classLoader) {}; + ClassLoader childLoader2 = new ClassLoader(classLoader) {}; + ClassLoader childLoader3 = new ClassLoader(classLoader) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return childLoader1.loadClass(name); + } + }; + Class composite = ClassUtils.createCompositeInterface( + new Class[] {Serializable.class, Externalizable.class}, childLoader1); + + assertTrue(ClassUtils.isCacheSafe(String.class, null)); + assertTrue(ClassUtils.isCacheSafe(String.class, classLoader)); + assertTrue(ClassUtils.isCacheSafe(String.class, childLoader1)); + assertTrue(ClassUtils.isCacheSafe(String.class, childLoader2)); + assertTrue(ClassUtils.isCacheSafe(String.class, childLoader3)); + assertFalse(ClassUtils.isCacheSafe(InnerClass.class, null)); + assertTrue(ClassUtils.isCacheSafe(InnerClass.class, classLoader)); + assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader1)); + assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader2)); + assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader3)); + assertFalse(ClassUtils.isCacheSafe(composite, null)); + assertFalse(ClassUtils.isCacheSafe(composite, classLoader)); + assertTrue(ClassUtils.isCacheSafe(composite, childLoader1)); + assertFalse(ClassUtils.isCacheSafe(composite, childLoader2)); + assertTrue(ClassUtils.isCacheSafe(composite, childLoader3)); + } + @Test public void testGetShortName() { String className = ClassUtils.getShortName(getClass()); @@ -199,7 +231,7 @@ public class ClassUtilsTests { } @Test - public void testHasMethod() throws Exception { + public void testHasMethod() { assertTrue(ClassUtils.hasMethod(Collection.class, "size")); assertTrue(ClassUtils.hasMethod(Collection.class, "remove", Object.class)); assertFalse(ClassUtils.hasMethod(Collection.class, "remove")); @@ -207,7 +239,7 @@ public class ClassUtilsTests { } @Test - public void testGetMethodIfAvailable() throws Exception { + public void testGetMethodIfAvailable() { Method method = ClassUtils.getMethodIfAvailable(Collection.class, "size"); assertNotNull(method); assertEquals("size", method.getName()); @@ -278,7 +310,7 @@ public class ClassUtilsTests { @Test public void testClassPackageAsResourcePath() { String result = ClassUtils.classPackageAsResourcePath(Proxy.class); - assertTrue(result.equals("java/lang/reflect")); + assertEquals("java/lang/reflect", result); } @Test @@ -294,7 +326,7 @@ public class ClassUtilsTests { @Test public void testGetAllInterfaces() { DerivedTestObject testBean = new DerivedTestObject(); - List ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean)); + List> ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean)); assertEquals("Correct number of interfaces", 4, ifcs.size()); assertTrue("Contains Serializable", ifcs.contains(Serializable.class)); assertTrue("Contains ITestBean", ifcs.contains(ITestObject.class)); @@ -303,13 +335,13 @@ public class ClassUtilsTests { @Test public void testClassNamesToString() { - List ifcs = new LinkedList(); + List> ifcs = new LinkedList<>(); ifcs.add(Serializable.class); ifcs.add(Runnable.class); assertEquals("[interface java.io.Serializable, interface java.lang.Runnable]", ifcs.toString()); assertEquals("[java.io.Serializable, java.lang.Runnable]", ClassUtils.classNamesToString(ifcs)); - List classes = new LinkedList(); + List> classes = new LinkedList<>(); classes.add(LinkedList.class); classes.add(Integer.class); assertEquals("[class java.util.LinkedList, class java.lang.Integer]", classes.toString()); @@ -319,7 +351,7 @@ public class ClassUtilsTests { assertEquals("[java.util.List]", ClassUtils.classNamesToString(List.class)); assertEquals("[]", Collections.EMPTY_LIST.toString()); - assertEquals("[]", ClassUtils.classNamesToString(Collections.EMPTY_LIST)); + assertEquals("[]", ClassUtils.classNamesToString(Collections.emptyList())); } @Test