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.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
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.rx.util.function.TripleFunc;
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.SerializedLambda;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

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

@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 TripleFunc, TT> converter;
    }

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

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

    public static final Linq COLLECTION_WRITE_METHOD_NAMES = Linq.from("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 = Collections.unmodifiableSet(new HashSet<>(Arrays.toList(Object.class.getMethods())));
    static final String M_0 = "close", CHANGE_TYPE_METHOD = "valueOf";
    static final String GET_PROPERTY = "get", GET_BOOL_PROPERTY = "is", SET_PROPERTY = "set";
    static final int LOOKUP_FLAGS = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PRIVATE;
    //must lazy before thread pool init.
    static final Lazy, Map>>> methodCache = new Lazy<>(MemoryCache::new);
    static final Lazy, Map>> fieldCache = new Lazy<>(MemoryCache::new);
    static final Constructor lookupConstructor;
    static final List> convertBeans = new CopyOnWriteArrayList<>();

    static {
        try {
            lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            setAccess(lookupConstructor);
        } 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(Long.class, Date.class, (sv, tt) -> new Date(sv));
        registerConvert(Long.class, DateTime.class, (sv, tt) -> new DateTime(sv));
        registerConvert(Date.class, Long.class, (sv, tt) -> sv.getTime());
        registerConvert(Date.class, DateTime.class, (sv, tt) -> new DateTime(sv));
        registerConvert(String.class, BigDecimal.class, (sv, tt) -> new BigDecimal(sv));
        registerConvert(String.class, UUID.class, (sv, tt) -> UUID.fromString(sv));
    }

    //region class
    public static String getStackTrace(Thread t) {
        StringBuilder buf = new StringBuilder();
        for (StackTraceElement traceElement : t.getStackTrace()) {
            buf.append("\tat ").appendLine(traceElement);
        }
        return buf.toString();
    }

    public static Linq stackTrace(int takeCount) {
        return Linq.from(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 not exist
        return SecurityManagerEx.INSTANCE.stackClass(2 + depth);
    }

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

    @SneakyThrows
    public static Linq getResources(String namePattern) {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        return Linq.from(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;
    }

    public static String getTypeDescriptor(@NonNull Type type) {
        if (type instanceof Class) {
            return ((Class) type).getName();
        }
        return toJsonString(type);
    }

    @SneakyThrows
    public static Type fromTypeDescriptor(@NonNull String descriptor) {
        if (descriptor.startsWith("{")) {
            Map typeJson = fromJson(descriptor, Map.class);
            return fromParameterizedType(typeJson);
        }
        return ClassUtils.getClass(descriptor);
    }

    @SneakyThrows
    static ParameterizedType fromParameterizedType(Map typeJson) {
        String ownerType = (String) typeJson.get("ownerType");
        String rawType = (String) typeJson.get("rawType");
        List actualTypeArguments = (List) typeJson.get("actualTypeArguments");
        return TypeUtils.parameterizeWithOwner((ownerType == null ? null : ClassUtils.getClass(ownerType)), ClassUtils.getClass(rawType), fromTypeArguments(actualTypeArguments));
    }

    static Type[] fromTypeArguments(List typeArguments) {
        return Linq.from(typeArguments).select(p -> {
            if (p instanceof Map) {
                Map typeArg = (Map) p;
                List lowerBounds = (List) typeArg.get("lowerBounds");
                List upperBounds = (List) typeArg.get("upperBounds");
                if (lowerBounds != null && upperBounds != null) {
                    return TypeUtils.wildcardType()
                            .withLowerBounds(fromTypeArguments(lowerBounds))
                            .withUpperBounds(fromTypeArguments(upperBounds)).build();
                }
                return fromParameterizedType(typeArg);
            }
            return ClassUtils.getClass((String) p);
        }).toArray(Type.class);
    }

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

    @ErrorCode
    @SneakyThrows
    public static  T newInstance(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());
            Constructor ctor = findMatchingExecutable(type, null, args);
            if (ctor != null) {
                setAccess(ctor);
                return (T) ctor.newInstance(args);
            }
        }
        throw new ApplicationException(values(type.getName()));
    }

    public static  T findMatchingExecutable(Class type, String name, Object[] args) {
        Executable executable = null;
        if (name != null) {
            Linq methods = getMethodMap(type).get(name);
            if (methods != null) {
                for (Executable p : methods) {
                    if (match(p, args)) {
                        executable = p;
                        break;
                    }
                }
            }
        } else {
            for (Constructor p : type.getDeclaredConstructors()) {
                if (match(p, args)) {
                    executable = p;
                    break;
                }
            }
        }
        return (T) executable;
    }

    static boolean match(Executable p, Object[] args) {
        if (p.getParameterCount() != args.length) {
            return false;
        }
        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()) {
                    return false;
                }
                continue;
            }
            if (!primitiveToWrapper(parameterType).isInstance(arg)) {
                return false;
            }
//            if (!TypeUtils.isInstance(arg, parameterType)) {
//                return false;
//            }
        }
        return true;
    }

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

        Class declaringClass = method.getDeclaringClass();
        MethodHandle methodHandle;
//        if (Sys.IS_JAVA_11) {
//            methodHandle = MethodHandles.lookup()
//                    .findSpecial(
//                            method.getDeclaringClass(),
//                            method.getName(),
//                            MethodType.methodType(method.getReturnType(), Arrays.EMPTY_CLASS_ARRAY),
//                            method.getDeclaringClass()
//                    );
//        } else {
        methodHandle = lookupConstructor.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) {
        return Strings.hashEquals(method.getName(), M_0) && 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 = findMatchingExecutable(searchType, name, args);
        if (method != null) {
            return (T) method.invoke(instance, args);
        }

        try {
            if (isStatic) {
                Class[] parameterTypes = toClass(args);  //May not right match if args have null value
                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 Method getInterfaceMethod(Method method) {
        Cache cache = Cache.getInstance(MemoryCache.class);
        Object v = cache.get(method, k -> {
            Class type = method.getDeclaringClass();
            NoSuchMethodException lastEx = null;
            for (Class i : type.getInterfaces()) {
                try {
                    return i.getMethod(method.getName(), method.getParameterTypes());
                } catch (NoSuchMethodException e) {
                    lastEx = e;
                }
            }
            if (lastEx != null) {
                log.warn("getInterfaceMethod", lastEx);
            }
            return Cache.NULL_VALUE;
        });
        if (v == Cache.NULL_VALUE) {
            return null;
        }
        return (Method) v;
    }

    public static Map> getMethodMap(Class type) {
        return methodCache.getValue().get(type, k -> {
            Set all = new HashSet<>();
            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);
            }

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

    //region fields
    public static Linq getProperties(Class to) {
        Cache> cache = Cache.getInstance(MemoryCache.class);
        return cache.get(fastCacheKey(Constants.CACHE_REGION_BEAN_PROPERTIES, to), k -> {
            Method getClass = Object.class.getDeclaredMethod("getClass");
            Linq q = Linq.from(to.getMethods());
            Linq> setters = q.where(p -> p.getParameterCount() == 1 && p.getName().startsWith(SET_PROPERTY)).select(p -> Tuple.of(propertyName(p.getName()), p));
            Linq> 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) -> Strings.hashEquals(p.left, 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);
        }

        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 readStaticField(Class type, String name) {
        return readField(type, null, name);
    }

    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 writeStaticField(Class type, String name, T value) {
        writeField(type, null, name, value);
    }

    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(Class type) {
        return fieldCache.getValue().get(type, k -> {
            List all = FieldUtils.getAllFieldsList(type);
            for (Field field : all) {
                setAccess(field);
            }
            return Collections.unmodifiableMap(Linq.from(all).toMap(Field::getName, p -> p));
        });
    }

    public static void setAccess(AccessibleObject member) {
        if (member.isAccessible()) {
            return;
        }
        try {
            if (member instanceof Field) {
                Field field = (Field) member;
                FieldUtils.removeFinalModifier(field);
            }

            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 convertQuietly(Object val, Class toType) {
        return convertQuietly(val, toType, null);
    }

    public static  T convertQuietly(Object val, @NonNull Class toType, T defaultVal) {
        try {
            return ifNull(changeType(val, toType), defaultVal);
        } catch (Throwable e) {
            return defaultVal;
        }
    }

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

    public static  T defaultValue(@NonNull 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, 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.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 = Linq.from(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) {
                    byte val = ((Number) value).byteValue();
                    if (val == 0) {
                        value = Boolean.FALSE;
                    } else if (val == 1) {
                        value = Boolean.TRUE;
                    } else {
                        throw new InvalidException("Value should be 0 or 1");
                    }
                } else {
                    Linq methods = getMethodMap(toType).get(CHANGE_TYPE_METHOD);
                    if (methods == null || fromType.isEnum()) {
                        Class fType = toType;
                        ConvertBean convertBean = Linq.from(convertBeans).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 (Number.class.isAssignableFrom(toType)) {
                        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] == 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;
    }

    public static boolean isBasicType(@NonNull Class type) {
        return type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type) || type == Boolean.class
                || type.isEnum()
                || Date.class.isAssignableFrom(type)
                || type == ULID.class
                || type == Class.class
                || type == UUID.class;
//        Class wrapType;
//        return type == String.class || Number.class.isAssignableFrom(wrapType = primitiveToWrapper(type))
//                || wrapType == Boolean.class
//                || type.isEnum()
//                || Date.class.isAssignableFrom(type)
//                || type == ULID.class
//                || type == Class.class
//                || type == UUID.class;
    }
    //endregion
}