001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.util;
017
018
019import org.apache.ibatis.javassist.util.proxy.ProxyObject;
020
021import java.lang.annotation.Annotation;
022import java.lang.reflect.*;
023import java.util.*;
024import java.util.function.Predicate;
025
026/**
027 * 类实例创建者创建者
028 *
029 * @author michael
030 * @date 17/3/21
031 */
032@SuppressWarnings("unchecked")
033public class ClassUtil {
034
035    private ClassUtil() {
036    }
037
038    private static final String[] OBJECT_METHODS = new String[]{
039        "toString",
040        "getClass",
041        "equals",
042        "hashCode",
043        "wait",
044        "notify",
045        "notifyAll",
046        "clone",
047        "finalize"
048    };
049
050    //proxy frameworks
051    private static final List<String> PROXY_CLASS_NAMES = Arrays.asList("net.sf.cglib.proxy.Factory"
052        // cglib
053        , "org.springframework.cglib.proxy.Factory"
054
055        // javassist
056        , "javassist.util.proxy.ProxyObject"
057        , "org.apache.ibatis.javassist.util.proxy.ProxyObject");
058    private static final String ENHANCER_BY = "$$EnhancerBy";
059    private static final String JAVASSIST_BY = "_$$_";
060
061    public static boolean isProxy(Class<?> clazz) {
062        for (Class<?> cls : clazz.getInterfaces()) {
063            if (PROXY_CLASS_NAMES.contains(cls.getName())) {
064                return true;
065            }
066        }
067        //java proxy
068        return Proxy.isProxyClass(clazz);
069    }
070
071    public static <T> Class<T> getUsefulClass(Class<T> clazz) {
072
073        if (ProxyObject.class.isAssignableFrom(clazz)) {
074            return (Class<T>) clazz.getSuperclass();
075        }
076
077        if (isProxy(clazz)) {
078            return getJdkProxySuperClass(clazz);
079        }
080
081        //ControllerTest$ServiceTest$$EnhancerByGuice$$40471411#hello   -------> Guice
082        //com.demo.blog.Blog$$EnhancerByCGLIB$$69a17158  ----> CGLIB
083        //io.jboot.test.app.TestAppListener_$$_jvstb9f_0 ------> javassist
084        final String name = clazz.getName();
085        if (name.contains(ENHANCER_BY) || name.contains(JAVASSIST_BY)) {
086            return (Class<T>) clazz.getSuperclass();
087        }
088
089        return clazz;
090    }
091
092
093    public static Class<?> getWrapType(Class<?> clazz) {
094        if (clazz == null || !clazz.isPrimitive()) {
095            return clazz;
096        }
097        if (clazz == Integer.TYPE) {
098            return Integer.class;
099        } else if (clazz == Long.TYPE) {
100            return Long.class;
101        } else if (clazz == Boolean.TYPE) {
102            return Boolean.class;
103        } else if (clazz == Float.TYPE) {
104            return Float.class;
105        } else if (clazz == Double.TYPE) {
106            return Double.class;
107        } else if (clazz == Short.TYPE) {
108            return Short.class;
109        } else if (clazz == Character.TYPE) {
110            return Character.class;
111        } else if (clazz == Byte.TYPE) {
112            return Byte.class;
113        } else if (clazz == Void.TYPE) {
114            return Void.class;
115        }
116        return clazz;
117    }
118
119
120    public static boolean isArray(Class<?> clazz) {
121        return clazz.isArray()
122            || clazz == int[].class
123            || clazz == long[].class
124            || clazz == short[].class
125            || clazz == float[].class
126            || clazz == double[].class;
127    }
128
129    public static boolean canInstance(int mod) {
130        return !Modifier.isAbstract(mod) || !Modifier.isInterface(mod);
131    }
132
133
134    public static <T> T newInstance(Class<T> clazz) {
135        try {
136            Constructor<?> defaultConstructor = null;
137            Constructor<?> otherConstructor = null;
138
139            Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
140            for (Constructor<?> constructor : declaredConstructors) {
141                if (constructor.getParameterCount() == 0 && Modifier.isPublic(constructor.getModifiers())) {
142                    defaultConstructor = constructor;
143                } else if (Modifier.isPublic(constructor.getModifiers())) {
144                    otherConstructor = constructor;
145                }
146            }
147            if (defaultConstructor != null) {
148                return (T) defaultConstructor.newInstance();
149            } else if (otherConstructor != null) {
150                Class<?>[] parameterTypes = otherConstructor.getParameterTypes();
151                Object[] parameters = new Object[parameterTypes.length];
152                for (int i = 0; i < parameterTypes.length; i++) {
153                    if (parameterTypes[i].isPrimitive()) {
154                        parameters[i] = ConvertUtil.getPrimitiveDefaultValue(parameterTypes[i]);
155                    } else {
156                        parameters[i] = null;
157                    }
158                }
159                return (T) otherConstructor.newInstance(parameters);
160            }
161            // 没有任何构造函数的情况下,去查找 static 工厂方法,满足 lombok 注解的需求
162            else {
163                Method factoryMethod = ClassUtil.getFirstMethod(clazz, m -> m.getParameterCount() == 0
164                    && m.getReturnType().isAssignableFrom(clazz)
165                    && Modifier.isPublic(m.getModifiers())
166                    && Modifier.isStatic(m.getModifiers()));
167
168                if (factoryMethod != null) {
169                    return (T) factoryMethod.invoke(null);
170                }
171            }
172            throw new IllegalArgumentException("the class \"" + clazz.getName() + "\" has no constructor.");
173        } catch (Exception e) {
174            throw new RuntimeException("Can not newInstance class: " + clazz.getName(), e);
175        }
176    }
177
178
179    public static <T> T newInstance(Class<T> clazz, Object... paras) {
180        try {
181            Constructor<?>[] constructors = clazz.getDeclaredConstructors();
182            for (Constructor<?> constructor : constructors) {
183                if (isMatchedParas(constructor, paras)) {
184                    Object ret = constructor.newInstance(paras);
185                    return (T) ret;
186                }
187            }
188            throw new IllegalArgumentException("Can not find constructor by paras: \"" + Arrays.toString(paras) + "\" in class[" + clazz.getName() + "]");
189        } catch (Exception e) {
190            throw new RuntimeException(e.toString(), e);
191        }
192    }
193
194
195    private static boolean isMatchedParas(Constructor<?> constructor, Object[] paras) {
196        if (constructor.getParameterCount() == 0) {
197            return paras == null || paras.length == 0;
198        }
199
200        if (constructor.getParameterCount() > 0
201            && (paras == null || paras.length != constructor.getParameterCount())) {
202            return false;
203        }
204
205        Class<?>[] parameterTypes = constructor.getParameterTypes();
206        for (int i = 0; i < parameterTypes.length; i++) {
207            Class<?> parameterType = parameterTypes[i];
208            Object paraObject = paras[i];
209            if (paraObject != null && !parameterType.isAssignableFrom(paraObject.getClass())) {
210                return false;
211            }
212        }
213
214        return true;
215    }
216
217
218    public static List<Field> getAllFields(Class<?> clazz) {
219        List<Field> fields = new ArrayList<>();
220        doGetFields(clazz, fields, null, false);
221        return fields;
222    }
223
224    public static List<Field> getAllFields(Class<?> clazz, Predicate<Field> predicate) {
225        List<Field> fields = new ArrayList<>();
226        doGetFields(clazz, fields, predicate, false);
227        return fields;
228    }
229
230    public static Field getFirstField(Class<?> clazz, Predicate<Field> predicate) {
231        List<Field> fields = new ArrayList<>();
232        doGetFields(clazz, fields, predicate, true);
233        return fields.isEmpty() ? null : fields.get(0);
234    }
235
236    /**
237     * 应用类及其除Object外的所有父类
238     *
239     * @param clazz  需要应用的类
240     * @param checkToContinue 应用当前类并检测是否继续应用, 返回false则停止应用, 返回true继续向上取父类
241     * @author KAMOsama
242     */
243    public static void applyAllClass(Class<?> clazz, Predicate<Class<?>> checkToContinue) {
244        Class<?> currentClass = clazz;
245        while (currentClass != null && currentClass != Object.class && checkToContinue.test(currentClass)) {
246            currentClass = currentClass.getSuperclass();
247        }
248    }
249
250    private static void doGetFields(Class<?> clazz, List<Field> fields, Predicate<Field> predicate, boolean firstOnly) {
251        applyAllClass(clazz, currentClass -> {
252            Field[] declaredFields = currentClass.getDeclaredFields();
253            for (Field declaredField : declaredFields) {
254                if (predicate == null || predicate.test(declaredField)) {
255                    fields.add(declaredField);
256                    if (firstOnly) {
257                        break;
258                    }
259                }
260            }
261            // 不止要获取第一个或集合为空就继续获取遍历父类
262            return !firstOnly || fields.isEmpty();
263        });
264    }
265
266    public static List<Method> getAllMethods(Class<?> clazz) {
267        List<Method> methods = new ArrayList<>();
268        doGetMethods(clazz, methods, null, false);
269        return methods;
270    }
271
272    public static List<Method> getAllMethods(Class<?> clazz, Predicate<Method> predicate) {
273        List<Method> methods = new ArrayList<>();
274        doGetMethods(clazz, methods, predicate, false);
275        return methods;
276    }
277
278    public static Method getAnyMethod(Class<?> clazz, String... methodNames) {
279        return getFirstMethod(clazz, method -> ArrayUtil.contains(methodNames, method.getName()));
280    }
281
282    public static Method getFirstMethod(Class<?> clazz, Predicate<Method> predicate) {
283        List<Method> methods = new ArrayList<>();
284        doGetMethods(clazz, methods, predicate, true);
285        return methods.isEmpty() ? null : methods.get(0);
286    }
287
288    public static Method getFirstMethodByAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) {
289        Set<Class<?>> visited = new HashSet<>();
290        return findMethod(clazz, annotationClass, visited);
291    }
292
293    private static Method findMethod(Class<?> clazz, Class<? extends Annotation> annotationClass, Set<Class<?>> visited) {
294        if (clazz == null || clazz == Object.class || visited.contains(clazz)) {
295            return null;
296        }
297        visited.add(clazz);
298
299        Method[] methods = clazz.getDeclaredMethods();
300        for (Method method : methods) {
301            if (method.isAnnotationPresent(annotationClass)) {
302                return method;
303            }
304        }
305
306        for (Class<?> inter : clazz.getInterfaces()) {
307            Method method = findMethod(inter, annotationClass, visited);
308            if (method != null) {
309                return method;
310            }
311        }
312
313        return findMethod(clazz.getSuperclass(), annotationClass, visited);
314    }
315
316
317    private static void doGetMethods(Class<?> clazz, List<Method> methods, Predicate<Method> predicate, boolean firstOnly) {
318        applyAllClass(clazz, currentClass -> {
319            Method[] declaredMethods = currentClass.getDeclaredMethods();
320            if (currentClass.isInterface()) {
321                for (Method method : declaredMethods) {
322                    // 接口类只需要获取 default 方法
323                    if (method.isDefault() && (predicate == null || predicate.test(method))) {
324                        methods.add(method);
325                        if (firstOnly) {
326                            break;
327                        }
328                    }
329                }
330            } else {
331                for (Method method : declaredMethods) {
332                    if (predicate == null || predicate.test(method)) {
333                        methods.add(method);
334                        if (firstOnly) {
335                            break;
336                        }
337                    }
338                }
339            }
340            // 只获取第一个并且集合不为空就结束遍历
341            if (firstOnly && !methods.isEmpty()) {
342                return false;
343            }
344            Class<?>[] interfaces = currentClass.getInterfaces();
345            for (Class<?> anInterface : interfaces) {
346                doGetMethods(anInterface, methods, predicate, firstOnly);
347                // 只获取第一个并且集合不为空就结束遍历
348                if (firstOnly && !methods.isEmpty()) {
349                    return false;
350                }
351            }
352            return true;
353        });
354    }
355
356    private static <T> Class<T> getJdkProxySuperClass(Class<T> clazz) {
357        final Class<?> proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
358        return (Class<T>) proxyClass.getInterfaces()[0];
359    }
360
361
362    public static boolean isGetterMethod(Method method, String property) {
363        String methodName = method.getName();
364        if (methodName.startsWith("get") && methodName.length() > 3) {
365            return StringUtil.firstCharToUpperCase(property).equals(methodName.substring(3));
366        } else if (methodName.startsWith("is") && methodName.length() > 2) {
367            return StringUtil.firstCharToUpperCase(property).equals(methodName.substring(2));
368        } else {
369            return false;
370        }
371    }
372
373    public static boolean isObjectMethod(String methodName) {
374        return ArrayUtil.contains(OBJECT_METHODS, methodName);
375    }
376
377}