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

org.rx.core.Reflects Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package org.rx.core;

import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.rx.annotation.ErrorCode;
import org.rx.bean.*;
import org.rx.core.cache.MemoryCache;
import org.rx.exception.ApplicationException;
import org.rx.exception.InvalidException;
import org.rx.util.Lazy;
import org.rx.util.function.BiFunc;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.InputStream;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;

import static org.rx.core.App.*;
import static org.rx.core.Constants.NON_RAW_TYPES;
import static org.rx.core.Constants.NON_UNCHECKED;
import static org.rx.core.Extends.*;

@SuppressWarnings(NON_UNCHECKED)
@Slf4j
public class Reflects extends ClassUtils {
    //region NestedTypes
    @RequiredArgsConstructor
    public static class PropertyNode implements Serializable {
        private static final long serialVersionUID = 3680733077204898075L;
        public final String propertyName;
        public final Method setter;
        public final Method getter;
    }

    @RequiredArgsConstructor
    static class ConvertBean {
        final Class baseFromType;
        final Class toType;
        final BiFunction, TT> converter;
    }

    static class SecurityManagerEx extends SecurityManager {
        static final SecurityManagerEx INSTANCE = new SecurityManagerEx();

        Class stackClass(int depth) {
            return getClassContext()[depth];
        }
    }
    //endregion

    public static final NQuery COLLECTION_WRITE_METHOD_NAMES = NQuery.of("add", "remove", "addAll", "removeAll", "removeIf", "retainAll", "clear"),
            List_WRITE_METHOD_NAMES = COLLECTION_WRITE_METHOD_NAMES.union(Arrays.toList("replaceAll", "set"));
    public static final Set OBJECT_METHODS = ConcurrentHashMap.newKeySet();
    static final int CLOSE_METHOD_HASH = "close".hashCode();
    static final String CHANGE_TYPE_METHOD = "valueOf";
    static final String GET_PROPERTY = "get", GET_BOOL_PROPERTY = "is", SET_PROPERTY = "set";
    //must lazy before thread pool init.
    static final Lazy> LAZY_CACHE = new Lazy<>(() -> Cache.getInstance(Cache.MEMORY_CACHE));
    static final Lazy, Map>>> METHOD_CACHE = new Lazy<>(MemoryCache::new);
    static final Lazy, Map>> FIELD_CACHE = new Lazy<>(MemoryCache::new);
    static final Constructor LOOKUP_CONSTRUCTOR;
    static final int LOOKUP_FLAGS = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PRIVATE;
    static final List> CONVERT_BEANS = new CopyOnWriteArrayList<>();

    static {
        for (Method method : Object.class.getMethods()) {
            OBJECT_METHODS.add(method);
        }

        try {
            LOOKUP_CONSTRUCTOR = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            setAccess(LOOKUP_CONSTRUCTOR);
        } catch (NoSuchMethodException e) {
            throw InvalidException.sneaky(e);
        }

        registerConvert(Number.class, Decimal.class, (sv, tt) -> Decimal.valueOf(sv.doubleValue()));
        registerConvert(NEnum.class, Integer.class, (sv, tt) -> sv.getValue());
        registerConvert(Date.class, DateTime.class, (sv, tt) -> new DateTime(sv));
        registerConvert(String.class, SUID.class, (sv, tt) -> SUID.valueOf(sv));
    }

    //region class
    public static void dumpStack(StringBuilder msg) {
        for (StackTraceElement stack : stackTrace(12)) {
            msg.appendLine("%s.%s(%s:%s)", stack.getClassName(), stack.getMethodName(), stack.getFileName(), stack.getLineNumber());
        }
    }

    public static NQuery stackTrace(int takeCount) {
        //Thread.currentThread().getStackTrace()性能略差
        return NQuery.of(new Throwable().getStackTrace()).skip(2).take(takeCount);
    }

    public static Class stackClass(int depth) {
        //Throwable.class.getDeclaredMethod("getStackTraceElement", int.class) & Reflection.getCallerClass(2 + depth) java 11 获取不到
        return SecurityManagerEx.INSTANCE.stackClass(2 + depth);
    }

    public static InputStream getResource(String namePattern) {
        InputStream stream = getClassLoader().getResourceAsStream(namePattern);
        if (stream != null) {
            return stream;
        }
        InputStream in = getResources(namePattern).firstOrDefault();
        if (in == null) {
            throw new InvalidException("Resource {} not found", namePattern);
        }
        return in;
    }

    @SneakyThrows
    public static NQuery getResources(String namePattern) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        return NQuery.of(resolver.getResources("classpath*:" + namePattern)).select(InputStreamSource::getInputStream);
    }

    //ClassLoader.getSystemClassLoader()
    public static ClassLoader getClassLoader() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        return loader != null ? loader : Reflects.class.getClassLoader();
    }

    public static  Class loadClass(String className, boolean initialize) {
        return loadClass(className, initialize, true);
    }

    //ClassPath.from(classloader).getTopLevelClasses(packageDirName)
    public static  Class loadClass(String className, boolean initialize, boolean throwOnEmpty) {
        try {
            return (Class) Class.forName(className, initialize, getClassLoader());
        } catch (ClassNotFoundException e) {
            if (!throwOnEmpty) {
                return null;
            }
            throw InvalidException.sneaky(e);
        }
    }

    public static boolean isInstance(Object val, Type type) {
        return TypeUtils.isInstance(val, type);
    }
    //endregion

    public static  String resolveProperty(BiFunc func) {
        SerializedLambda lambda = getLambda(func);
        return propertyName(lambda.getImplMethodName());
    }

    public static  Tuple resolveImpl(BiFunc func) {
        SerializedLambda lambda = getLambda(func);
        String declaredClass = lambda.getImplClass().replace("/", ".");
        return Tuple.of(declaredClass, propertyName(lambda.getImplMethodName()));
    }

    @SneakyThrows
    public static  Field resolve(BiFunc func) {
        SerializedLambda lambda = getLambda(func);
        String declaredClass = lambda.getImplClass().replace("/", ".");
        return getFieldMap(Class.forName(declaredClass)).get(propertyName(lambda.getImplMethodName()));
    }

    static  SerializedLambda getLambda(BiFunc func) {
        SerializedLambda lambda = invokeMethod(func, "writeReplace");
        String implMethodName = lambda.getImplMethodName();
        if (implMethodName.startsWith("lambda$")) {
            throw new IllegalArgumentException("BiFunc can not be LAMBDA EXPR, but only METHOD REFERENCE");
        }
        if (!implMethodName.startsWith(GET_PROPERTY) && !implMethodName.startsWith(GET_BOOL_PROPERTY)) {
            throw new IllegalArgumentException(implMethodName + " is not a GETTER");
        }
        return lambda;
    }

    //region methods
    public static  T newInstance(Class type) {
        return newInstance(type, Arrays.EMPTY_OBJECT_ARRAY);
    }

    @ErrorCode
    @SneakyThrows
    public static  T newInstance(@NonNull Class type, Object... args) {
        if (args == null) {
            args = Arrays.EMPTY_OBJECT_ARRAY;
        }

        try {
            return (T) ConstructorUtils.invokeConstructor(type, args);
        } catch (Exception e) {
            log.warn("Not match any accessible constructors. {}", e.getMessage());
            for (Constructor constructor : type.getDeclaredConstructors()) {
                Class[] paramTypes = constructor.getParameterTypes();
                if (paramTypes.length != args.length) {
                    continue;
                }
                boolean ok = true;
                for (int i = 0; i < paramTypes.length; i++) {
                    if (!TypeUtils.isInstance(args[i], paramTypes[i])) {
                        ok = false;
                        break;
                    }
                }
                if (!ok) {
                    continue;
                }
                setAccess(constructor);
                return (T) constructor.newInstance(args);
            }
        }
        throw new ApplicationException(values(type.getName()));
    }

    @SneakyThrows
    public static  T invokeDefaultMethod(@NonNull Method method, Object instance, Object... args) {
        require(method, method.isDefault());

        Class declaringClass = method.getDeclaringClass();
        MethodHandle methodHandle;
//        if (App.IS_JAVA_11) {
//            methodHandle = MethodHandles.lookup()
//                    .findSpecial(
//                            method.getDeclaringClass(),
//                            method.getName(),
//                            MethodType.methodType(method.getReturnType(), Arrays.EMPTY_CLASS_ARRAY),
//                            method.getDeclaringClass()
//                    );
//        } else {
        methodHandle = LOOKUP_CONSTRUCTOR.newInstance(declaringClass, LOOKUP_FLAGS)
                .unreflectSpecial(method, declaringClass);
//        }
        return (T) methodHandle.bindTo(instance)
                .invokeWithArguments(args);
    }

    public static boolean invokeCloseMethod(Method method, Object instance) {
        if (!isCloseMethod(method)) {
            return false;
        }
        return tryClose(instance);
    }

    public static boolean isCloseMethod(Method method) {
        //String hashcode has cached
        return method.getName().hashCode() == CLOSE_METHOD_HASH && method.getParameterCount() == 0;
    }

    public static  T invokeStaticMethod(Class type, String name, Object... args) {
        return invokeMethod(type, null, name, args);
    }

    public static  T invokeMethod(TT instance, String name, Object... args) {
        return invokeMethod(null, instance, name, args);
    }

    @SneakyThrows
    @ErrorCode
    public static  T invokeMethod(Class type, TT instance, String name, Object... args) {
        boolean isStatic = type != null;
        Class searchType = isStatic ? type : instance.getClass();
        Method method = null;
        NQuery methods = getMethodMap(searchType).get(name);
        if (methods != null) {
            for (Method p : methods) {
                if (p.getParameterCount() != args.length) {
                    continue;
                }

                boolean find = true;
                Class[] parameterTypes = p.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class parameterType = parameterTypes[i];
                    Object arg = args[i];
                    if (arg == null) {
                        if (parameterType.isPrimitive()) {
                            find = false;
                            break;
                        }
                        continue;
                    }
                    if (!primitiveToWrapper(parameterType).isInstance(arg)) {
                        find = false;
                        break;
                    }
                }
                if (find) {
                    method = p;
                    break;
                }
            }
        }
        if (method != null) {
            return (T) method.invoke(instance, args);
        }

        try {
            if (isStatic) {
                Class[] parameterTypes = toClass(args);  //null 不准
                method = MethodUtils.getMatchingMethod(searchType, name, parameterTypes);
                return invokeMethod(method, args);
            } else {
                return (T) MethodUtils.invokeMethod(instance, true, name, args);
            }
        } catch (Exception e) {
            throw new ApplicationException(values(searchType.getName(), name), e);
        }
    }

    @SneakyThrows
    public static  T invokeMethod(Method method, TT instance, Object... args) {
        setAccess(method);
        return (T) method.invoke(instance, args);
    }

    public static Map> getMethodMap(@NonNull Class type) {
        return METHOD_CACHE.getValue().get(type, k -> {
            List all = new ArrayList<>();
            for (Class current = type; current != null; current = current.getSuperclass()) {
                Method[] declared = type.getDeclaredMethods(); //can't get kotlin private methods
                for (Method method : declared) {
                    setAccess(method);
                }
                Collections.addAll(all, declared);
            }

            NQuery defMethods = NQuery.of(type.getInterfaces()).selectMany(p -> NQuery.of(p.getMethods())).where(p -> {
                boolean d = p.isDefault();
                if (d) {
                    setAccess(p);
                }
                return d;
            });
            all.addAll(defMethods.toList());
            return Collections.unmodifiableMap(NQuery.of(all).groupByIntoMap(Method::getName, (p, x) -> x));
        });
    }
    //endregion

    //region fields
    public static NQuery getProperties(Class to) {
        return (NQuery) LAZY_CACHE.getValue().get(hashKey("properties", to), k -> {
            Method getClass = Object.class.getDeclaredMethod("getClass");
            NQuery q = NQuery.of(to.getMethods());
            NQuery> setters = q.where(p -> p.getParameterCount() == 1 && p.getName().startsWith(SET_PROPERTY)).select(p -> Tuple.of(propertyName(p.getName()), p));
            NQuery> getters = q.where(p -> p.getParameterCount() == 0 && p != getClass && (p.getName().startsWith(GET_PROPERTY) || p.getName().startsWith(GET_BOOL_PROPERTY))).select(p -> Tuple.of(propertyName(p.getName()), p));
            return setters.join(getters.toList(), (p, x) -> p.left.equals(x.left), (p, x) -> new PropertyNode(p.left, p.right, x.right));
        });
    }

    public static String propertyName(String getterOrSetter) {
        String name;
        if (getterOrSetter.startsWith(GET_PROPERTY)) {
            name = getterOrSetter.substring(GET_PROPERTY.length());
        } else if (getterOrSetter.startsWith(GET_BOOL_PROPERTY)) {
            name = getterOrSetter.substring(GET_BOOL_PROPERTY.length());
        } else if (getterOrSetter.startsWith(SET_PROPERTY)) {
            name = getterOrSetter.substring(SET_PROPERTY.length());
        } else {
            name = getterOrSetter;
        }
        if (name.isEmpty()) {
            throw new InvalidException("Invalid name {}", getterOrSetter);
        }

//        Introspector.decapitalize
        if (Character.isLowerCase(name.charAt(0))) {
            return name;
        }
        return name.substring(0, 1).toLowerCase() + name.substring(1);
    }

    @SneakyThrows
    public static  void copyPublicFields(T from, T to) {
        for (Field field : getFieldMap(to.getClass()).values()) {
            if (!Modifier.isPublic(field.getModifiers())) {
                continue;
            }
            field.set(to, field.get(from));
        }
    }

    public static  T readField(TT instance, String name) {
        return readField(instance.getClass(), instance, name);
    }

    @SneakyThrows
    public static  T readField(Class type, TT instance, String name) {
        Field field = getFieldMap(type).get(name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return (T) field.get(instance);
    }

    public static  void writeField(TT instance, String name, T value) {
        writeField(instance.getClass(), instance, name, value);
    }

    @SneakyThrows
    public static  void writeField(Class type, TT instance, String name, T value) {
        Field field = getFieldMap(type).get(name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        field.set(instance, changeType(value, field.getType()));
    }

    public static Map getFieldMap(@NonNull Class type) {
        return FIELD_CACHE.getValue().get(type, k -> {
            List all = FieldUtils.getAllFieldsList(type);
            for (Field field : all) {
                setAccess(field);
            }
            return Collections.unmodifiableMap(NQuery.of(all).toMap(Field::getName, p -> p));
        });
    }

    public static void setAccess(AccessibleObject member) {
        if (member.isAccessible()) {
            return;
        }
        try {
            if (System.getSecurityManager() == null) {
                member.setAccessible(true); // <~ Dragons
            } else {
                AccessController.doPrivileged((PrivilegedAction) () -> {
                    member.setAccessible(true);  // <~ moar Dragons
                    return null;
                });
            }
        } catch (Exception e) {
            log.warn("setAccess", e);
        }
    }

    public static  T tryConvert(Object val, Class toType) {
        return tryConvert(val, toType, null);
    }

    public static  T tryConvert(Object val, @NonNull Class toType, T defaultVal) {
        try {
            return ifNull(changeType(val, toType), defaultVal);
        } catch (Exception ex) {
            return defaultVal;
        }
    }

    public static  void registerConvert(@NonNull Class baseFromType, @NonNull Class toType, @NonNull BiFunction, TT> converter) {
        CONVERT_BEANS.add(0, new ConvertBean<>(baseFromType, toType, converter));
    }

    public static  T defaultValue(Class type) {
        return changeType(null, type);
    }

    @SuppressWarnings(NON_RAW_TYPES)
    @ErrorCode("enumError")
    @ErrorCode(cause = NoSuchMethodException.class)
    @ErrorCode(cause = ReflectiveOperationException.class)
    public static  T changeType(Object value, @NonNull Class toType) {
        if (value == null) {
            if (!toType.isPrimitive()) {
                if (toType == List.class) {
                    return (T) Collections.emptyList();
                }
                if (toType == Map.class) {
                    return (T) Collections.emptyMap();
                }
                return null;
            }
            if (toType == boolean.class) {
                return (T) Boolean.FALSE;
            } else {
                value = 0;
            }
        }

        Object fValue = value;
        if (toType == String.class) {
            value = value.toString();
        } else if (toType == UUID.class) {
            value = UUID.fromString(value.toString());
        } else if (toType == BigDecimal.class) {
            value = new BigDecimal(value.toString());
        } else if (toType.isEnum()) {
            boolean failBack = true;
            if (NEnum.class.isAssignableFrom(toType)) {
                if (value instanceof String) {
                    try {
                        value = Integer.valueOf((String) value);
                    } catch (NumberFormatException e) {
                        //ignore
                    }
                }
                if (value instanceof Number) {
                    int val = ((Number) value).intValue();
                    value = NEnum.valueOf((Class) toType, val);
                    failBack = false;
                }
            }
            if (failBack) {
                String val = value.toString();
                value = NQuery.of(toType.getEnumConstants()).singleOrDefault(p -> ((Enum) p).name().equals(val));
            }
            if (value == null) {
                throw new ApplicationException("enumError", values(fValue, toType.getSimpleName()));
            }
        } else if (!toType.isPrimitive() && TypeUtils.isInstance(value, toType)) {
            //int to long | int to Object ok, DO NOTHING
            //long to int not ok
        } else {
            Class fromType = value.getClass();
            try {
                toType = (Class) primitiveToWrapper(toType);
                if (toType == Boolean.class && value instanceof Number) {
                    int val = ((Number) value).intValue();
                    if (val == 0) {
                        value = Boolean.FALSE;
                    } else if (val == 1) {
                        value = Boolean.TRUE;
                    } else {
                        throw new InvalidException("Value should be 0 or 1");
                    }
                } else {
                    NQuery methods = getMethodMap(toType).get(CHANGE_TYPE_METHOD);
                    if (methods == null || fromType.isEnum()) {
                        Class fType = toType;
                        ConvertBean convertBean = NQuery.of(CONVERT_BEANS).firstOrDefault(p -> TypeUtils.isInstance(fValue, p.baseFromType) && p.toType.isAssignableFrom(fType));
                        if (convertBean != null) {
                            return (T) convertBean.converter.apply(value, convertBean.toType);
                        }
                        throw new NoSuchMethodException(CHANGE_TYPE_METHOD);
                    }

                    if (isAssignable(toType, Number.class)) {
                        if (value instanceof Boolean) {
                            if (!(Boolean) value) {
                                value = "0";
                            } else {
                                value = "1";
                            }
                        } else if (value instanceof Number) {
                            //BigDecimal 1.001 to 1
                            Number num = (Number) value;
                            if (toType == Integer.class) {
                                value = num.intValue();
                            } else if (toType == Long.class) {
                                value = num.longValue();
                            } else if (toType == Byte.class) {
                                value = num.byteValue();
                            } else if (toType == Short.class) {
                                value = num.shortValue();
                            }
                        }
                    }

                    Method m = null;
                    for (Method p : methods) {
                        if (!(p.getParameterCount() == 1 && p.getParameterTypes()[0].equals(String.class))) {
                            continue;
                        }
                        m = p;
                        break;
                    }
                    if (m == null) {
                        m = toType.getDeclaredMethod(CHANGE_TYPE_METHOD, String.class);
                    }
                    value = m.invoke(null, value.toString());
                }
            } catch (NoSuchMethodException e) {
                throw new ApplicationException(values(toType), e);
            } catch (ReflectiveOperationException e) {
                throw new ApplicationException(values(fromType, toType, value), e);
            }
        }
        return (T) value;
    }
    //endregion
}