blob: f5fad308a2d4d934989be19527603a5ae0c395ff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.utils;
import org.apache.dubbo.common.convert.ConverterUtil;
import org.apache.dubbo.rpc.model.FrameworkModel;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
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.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
import static org.apache.dubbo.common.function.Streams.filterAll;
import static org.apache.dubbo.common.utils.ArrayUtils.isNotEmpty;
import static org.apache.dubbo.common.utils.CollectionUtils.flip;
import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
public class ClassUtils {
/**
* Suffix for array class names: "[]"
*/
public static final String ARRAY_SUFFIX = "[]";
/**
* Simple Types including:
* <ul>
* <li>{@link Void}</li>
* <li>{@link Boolean}</li>
* <li>{@link Character}</li>
* <li>{@link Byte}</li>
* <li>{@link Integer}</li>
* <li>{@link Float}</li>
* <li>{@link Double}</li>
* <li>{@link String}</li>
* <li>{@link BigDecimal}</li>
* <li>{@link BigInteger}</li>
* <li>{@link Date}</li>
* <li>{@link Object}</li>
* </ul>
*
* @see javax.management.openmbean.SimpleType
* @since 2.7.6
*/
public static final Set<Class<?>> SIMPLE_TYPES = ofSet(
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
);
/**
* Prefix for internal array class names: "[L"
*/
private static final String INTERNAL_ARRAY_PREFIX = "[L";
/**
* Map with primitive type name as key and corresponding primitive type as
* value, for example: "int" -> "int.class".
*/
private static final Map<String, Class<?>> PRIMITIVE_TYPE_NAME_MAP = new HashMap<>(32);
/**
* Map with primitive wrapper type as key and corresponding primitive type
* as value, for example: Integer.class -> int.class.
*/
private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_TYPE_MAP = new HashMap<>(16);
static {
PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, byte.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, char.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, double.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, float.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, int.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, long.class);
PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, short.class);
Set<Class<?>> primitiveTypeNames = new HashSet<>(32);
primitiveTypeNames.addAll(PRIMITIVE_WRAPPER_TYPE_MAP.values());
primitiveTypeNames.addAll(Arrays
.asList(boolean[].class, byte[].class, char[].class, double[].class,
float[].class, int[].class, long[].class, short[].class));
for (Class<?> primitiveTypeName : primitiveTypeNames) {
PRIMITIVE_TYPE_NAME_MAP.put(primitiveTypeName.getName(), primitiveTypeName);
}
}
/**
* Map with primitive type as key and corresponding primitive wrapper type
* as value, for example: int.class -> Integer.class.
*/
private static final Map<Class<?>, Class<?>> WRAPPER_PRIMITIVE_TYPE_MAP = flip(PRIMITIVE_WRAPPER_TYPE_MAP);
/**
* Separator char for package
*/
private static final char PACKAGE_SEPARATOR_CHAR = '.';
public static Class<?> forNameWithThreadContextClassLoader(String name)
throws ClassNotFoundException {
return forName(name, Thread.currentThread().getContextClassLoader());
}
public static Class<?> forNameWithCallerClassLoader(String name, Class<?> caller)
throws ClassNotFoundException {
return forName(name, caller.getClassLoader());
}
public static ClassLoader getCallerClassLoader(Class<?> caller) {
return caller.getClassLoader();
}
/**
* get class loader
*
* @param clazz
* @return class loader
*/
public static ClassLoader getClassLoader(Class<?> clazz) {
ClassLoader cl = null;
if (!clazz.getName().startsWith("org.apache.dubbo")) {
cl = clazz.getClassLoader();
}
if (cl == null) {
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back to system class loader...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = clazz.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
} catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
}
return cl;
}
/**
* 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.
* <p>
* Call this method if you intend to use the thread context ClassLoader in a
* scenario where you absolutely need a non-null ClassLoader reference: for
* example, for class path resource loading (but not necessarily for
* <code>Class.forName</code>, which accepts a <code>null</code> ClassLoader
* reference as well).
*
* @return the default ClassLoader (never <code>null</code>)
* @see java.lang.Thread#getContextClassLoader()
*/
public static ClassLoader getClassLoader() {
return getClassLoader(ClassUtils.class);
}
/**
* Same as <code>Class.forName()</code>, except that it works for primitive
* types.
*/
public static Class<?> forName(String name) throws ClassNotFoundException {
return forName(name, getClassLoader());
}
/**
* Replacement for <code>Class.forName()</code> that also returns Class
* instances for primitives (like "int") and array class names (like
* "String[]").
*
* @param name the name of the Class
* @param classLoader the class loader to use (may be <code>null</code>,
* which indicates the default class loader)
* @return 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(String, boolean, ClassLoader)
*/
public static Class<?> forName(String name, ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz != null) {
return clazz;
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[Ljava.lang.String;" style arrays
int internalArrayMarker = name.indexOf(INTERNAL_ARRAY_PREFIX);
if (internalArrayMarker != -1 && name.endsWith(";")) {
String elementClassName = null;
if (internalArrayMarker == 0) {
elementClassName = name
.substring(INTERNAL_ARRAY_PREFIX.length(), name.length() - 1);
} else if (name.startsWith("[")) {
elementClassName = name.substring(1);
}
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = getClassLoader();
}
return classLoaderToUse.loadClass(name);
}
/**
* Resolve the given class name as primitive class, if appropriate,
* according to the JVM's naming rules for primitive classes.
* <p>
* Also supports the JVM's internal class names for primitive arrays. Does
* <i>not</i> 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 <code>null</code> 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 = (Class<?>) PRIMITIVE_TYPE_NAME_MAP.get(name);
}
return result;
}
public static String toShortString(Object obj) {
if (obj == null) {
return "null";
}
return obj.getClass().getSimpleName() + "@" + System.identityHashCode(obj);
}
public static String simpleClassName(Class<?> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
String className = clazz.getName();
final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
if (lastDotIdx > -1) {
return className.substring(lastDotIdx + 1);
}
return className;
}
/**
* The specified type is primitive type or simple type
*
* @param type the type to test
* @return
* @deprecated as 2.7.6, use {@link Class#isPrimitive()} plus {@link #isSimpleType(Class)} instead
*/
public static boolean isPrimitive(Class<?> type) {
return type != null && (type.isPrimitive() || isSimpleType(type));
}
/**
* The specified type is simple type or not
*
* @param type the type to test
* @return if <code>type</code> is one element of {@link #SIMPLE_TYPES}, return <code>true</code>, or <code>false</code>
* @see #SIMPLE_TYPES
* @since 2.7.6
*/
public static boolean isSimpleType(Class<?> type) {
return SIMPLE_TYPES.contains(type);
}
public static Object convertPrimitive(Class<?> type, String value) {
return convertPrimitive(FrameworkModel.defaultModel(), type, value);
}
public static Object convertPrimitive(FrameworkModel frameworkModel, Class<?> type, String value) {
if (isEmpty(value)) {
return null;
}
Class<?> wrapperType = WRAPPER_PRIMITIVE_TYPE_MAP.getOrDefault(type, type);
Object result = null;
try {
result = frameworkModel.getBeanFactory().getBean(ConverterUtil.class).convertIfPossible(value, wrapperType);
} catch (Exception e) {
// ignore exception
}
return result;
}
/**
* We only check boolean value at this moment.
*
* @param type
* @param value
* @return
*/
public static boolean isTypeMatch(Class<?> type, String value) {
if ((type == boolean.class || type == Boolean.class)
&& !("true".equals(value) || "false".equals(value))) {
return false;
}
return true;
}
/**
* 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}
* @since 2.7.6
*/
public static Set<Class<?>> getAllSuperClasses(Class<?> type, Predicate<Class<?>>... classFilters) {
Set<Class<?>> 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}
* @since 2.7.6
*/
public static Set<Class<?>> getAllInterfaces(Class<?> type, Predicate<Class<?>>... interfaceFilters) {
if (type == null || type.isPrimitive()) {
return emptySet();
}
Set<Class<?>> allInterfaces = new LinkedHashSet<>();
Set<Class<?>> resolved = new LinkedHashSet<>();
Queue<Class<?>> 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}
* @since 2.7.6
*/
public static Set<Class<?>> getAllInheritedTypes(Class<?> type, Predicate<Class<?>>... typeFilters) {
// Add all super classes
Set<Class<?>> 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)}
* @since 2.7.6
*/
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);
}
/**
* Test the specified class name is present in the {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @return If found, return <code>true</code>
* @since 2.7.6
*/
public static boolean isPresent(String className, ClassLoader classLoader) {
try {
forName(className, classLoader);
} catch (Throwable ignored) { // Ignored
return false;
}
return true;
}
/**
* Resolve the {@link Class} by the specified name and {@link ClassLoader}
*
* @param className the name of {@link Class}
* @param classLoader {@link ClassLoader}
* @return If can't be resolved , return <code>null</code>
* @since 2.7.6
*/
public static Class<?> resolveClass(String className, ClassLoader classLoader) {
Class<?> targetClass = null;
try {
targetClass = forName(className, classLoader);
} catch (Throwable ignored) { // Ignored
}
return targetClass;
}
/**
* Is generic class or not?
*
* @param type the target type
* @return if the target type is not null or <code>void</code> or Void.class, return <code>true</code>, or false
* @since 2.7.6
*/
public static boolean isGenericClass(Class<?> type) {
return type != null && !void.class.equals(type) && !Void.class.equals(type);
}
public static boolean hasMethods(Method[] methods) {
if (methods == null || methods.length == 0) {
return false;
}
for (Method m : methods) {
if (m.getDeclaringClass() != Object.class) {
return true;
}
}
return false;
}
private static final String[] OBJECT_METHODS = new String[]{"getClass", "hashCode", "toString", "equals"};
/**
* get method name array.
*
* @return method name array.
*/
public static String[] getMethodNames(Class<?> tClass) {
if (tClass == Object.class) {
return OBJECT_METHODS;
}
Method[] methods = Arrays.stream(tClass.getMethods())
.collect(Collectors.toList())
.toArray(new Method[] {});
List<String> mns = new ArrayList<>(); // method names.
boolean hasMethod = hasMethods(methods);
if (hasMethod) {
for (Method m : methods) {
//ignore Object's method.
if (m.getDeclaringClass() == Object.class) {
continue;
}
String mn = m.getName();
mns.add(mn);
}
}
return mns.toArray(new String[0]);
}
/**
* get method name array.
*
* @return method name array.
*/
public static String[] getDeclaredMethodNames(Class<?> tClass) {
if (tClass == Object.class) {
return OBJECT_METHODS;
}
Method[] methods = Arrays.stream(tClass.getMethods())
.collect(Collectors.toList())
.toArray(new Method[] {});
List<String> dmns = new ArrayList<>(); // method names.
boolean hasMethod = hasMethods(methods);
if (hasMethod) {
for (Method m : methods) {
//ignore Object's method.
if (m.getDeclaringClass() == Object.class) {
continue;
}
String mn = m.getName();
if (m.getDeclaringClass() == tClass) {
dmns.add(mn);
}
}
}
return dmns.toArray(new String[0]);
}
}