001/*
002 *  Copyright (c) 2022-2023, 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.reflect.Constructor;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.lang.reflect.Proxy;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.List;
029import java.util.function.Predicate;
030
031/**
032 * 类实例创建者创建者
033 *
034 * @author michael
035 * @date 17/3/21
036 */
037@SuppressWarnings("unchecked")
038public class ClassUtil {
039
040    private ClassUtil() {
041    }
042
043    private static final String[] OBJECT_METHODS = new String[]{
044        "toString",
045        "getClass",
046        "equals",
047        "hashCode",
048        "wait",
049        "notify",
050        "notifyAll",
051        "clone",
052        "finalize"
053    };
054
055    //proxy frameworks
056    private static final List<String> PROXY_CLASS_NAMES = Arrays.asList("net.sf.cglib.proxy.Factory"
057        // cglib
058        , "org.springframework.cglib.proxy.Factory"
059
060        // javassist
061        , "javassist.util.proxy.ProxyObject"
062        , "org.apache.ibatis.javassist.util.proxy.ProxyObject");
063    private static final String ENHANCER_BY = "$$EnhancerBy";
064    private static final String JAVASSIST_BY = "_$$_";
065
066    public static boolean isProxy(Class<?> clazz) {
067        for (Class<?> cls : clazz.getInterfaces()) {
068            if (PROXY_CLASS_NAMES.contains(cls.getName())) {
069                return true;
070            }
071        }
072        //java proxy
073        return Proxy.isProxyClass(clazz);
074    }
075
076    public static <T> Class<T> getUsefulClass(Class<T> clazz) {
077
078        if (ProxyObject.class.isAssignableFrom(clazz)) {
079            return (Class<T>) clazz.getSuperclass();
080        }
081
082        if (isProxy(clazz)) {
083            return getJdkProxySuperClass(clazz);
084        }
085
086        //ControllerTest$ServiceTest$$EnhancerByGuice$$40471411#hello   -------> Guice
087        //com.demo.blog.Blog$$EnhancerByCGLIB$$69a17158  ----> CGLIB
088        //io.jboot.test.app.TestAppListener_$$_jvstb9f_0 ------> javassist
089        final String name = clazz.getName();
090        if (name.contains(ENHANCER_BY) || name.contains(JAVASSIST_BY)) {
091            return (Class<T>) clazz.getSuperclass();
092        }
093
094        return clazz;
095    }
096
097
098    public static Class<?> getWrapType(Class<?> clazz) {
099        if (clazz == null || !clazz.isPrimitive()) {
100            return clazz;
101        }
102        if (clazz == Integer.TYPE) {
103            return Integer.class;
104        } else if (clazz == Long.TYPE) {
105            return Long.class;
106        } else if (clazz == Boolean.TYPE) {
107            return Boolean.class;
108        } else if (clazz == Float.TYPE) {
109            return Float.class;
110        } else if (clazz == Double.TYPE) {
111            return Double.class;
112        } else if (clazz == Short.TYPE) {
113            return Short.class;
114        } else if (clazz == Character.TYPE) {
115            return Character.class;
116        } else if (clazz == Byte.TYPE) {
117            return Byte.class;
118        } else if (clazz == Void.TYPE) {
119            return Void.class;
120        }
121        return clazz;
122    }
123
124
125    public static boolean isArray(Class<?> clazz) {
126        return clazz.isArray()
127            || clazz == int[].class
128            || clazz == long[].class
129            || clazz == short[].class
130            || clazz == float[].class
131            || clazz == double[].class;
132    }
133
134    public static boolean canInstance(int mod) {
135        return !Modifier.isAbstract(mod) || !Modifier.isInterface(mod);
136    }
137
138
139    public static <T> T newInstance(Class<T> clazz) {
140        try {
141            Constructor<?> defaultConstructor = null;
142            Constructor<?> otherConstructor = null;
143
144            Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
145            for (Constructor<?> constructor : declaredConstructors) {
146                if (constructor.getParameterCount() == 0 && Modifier.isPublic(constructor.getModifiers())) {
147                    defaultConstructor = constructor;
148                } else if (Modifier.isPublic(constructor.getModifiers())) {
149                    otherConstructor = constructor;
150                }
151            }
152            if (defaultConstructor != null) {
153                return (T) defaultConstructor.newInstance();
154            } else if (otherConstructor != null) {
155                Class<?>[] parameterTypes = otherConstructor.getParameterTypes();
156                Object[] parameters = new Object[parameterTypes.length];
157                for (int i = 0; i < parameterTypes.length; i++) {
158                    if (parameterTypes[i].isPrimitive()) {
159                        parameters[i] = ConvertUtil.getPrimitiveDefaultValue(parameterTypes[i]);
160                    } else {
161                        parameters[i] = null;
162                    }
163                }
164                return (T) otherConstructor.newInstance(parameters);
165            }
166            // 没有任何构造函数的情况下,去查找 static 工厂方法,满足 lombok 注解的需求
167            else {
168                Method factoryMethod = ClassUtil.getFirstMethod(clazz, m -> m.getParameterCount() == 0
169                    && clazz == m.getReturnType()
170                    && Modifier.isPublic(m.getModifiers())
171                    && Modifier.isStatic(m.getModifiers()));
172                if (factoryMethod != null) {
173                    return (T) factoryMethod.invoke(null);
174                }
175            }
176            throw new IllegalArgumentException("the class \"" + clazz.getName() + "\" has no constructor.");
177        } catch (Exception e) {
178            throw new RuntimeException("Can not newInstance class: " + clazz.getName(), e);
179        }
180    }
181
182
183    public static <T> T newInstance(Class<T> clazz, Object... paras) {
184        try {
185            Constructor<?>[] constructors = clazz.getDeclaredConstructors();
186            for (Constructor<?> constructor : constructors) {
187                if (isMatchedParas(constructor, paras)) {
188                    Object ret = constructor.newInstance(paras);
189                    return (T) ret;
190                }
191            }
192            throw new IllegalArgumentException("Can not find constructor by paras: \"" + Arrays.toString(paras) + "\" in class[" + clazz.getName() + "]");
193        } catch (Exception e) {
194            e.printStackTrace();
195        }
196
197        return null;
198    }
199
200
201    private static boolean isMatchedParas(Constructor<?> constructor, Object[] paras) {
202        if (constructor.getParameterCount() == 0) {
203            return paras == null || paras.length == 0;
204        }
205
206        if (constructor.getParameterCount() > 0
207            && (paras == null || paras.length != constructor.getParameterCount())) {
208            return false;
209        }
210
211        Class<?>[] parameterTypes = constructor.getParameterTypes();
212        for (int i = 0; i < parameterTypes.length; i++) {
213            Class<?> parameterType = parameterTypes[i];
214            Object paraObject = paras[i];
215            if (paraObject != null && !parameterType.isAssignableFrom(paraObject.getClass())) {
216                return false;
217            }
218        }
219
220        return true;
221    }
222
223
224    public static List<Field> getAllFields(Class<?> clazz) {
225        List<Field> fields = new ArrayList<>();
226        doGetFields(clazz, fields, null, false);
227        return fields;
228    }
229
230    public static List<Field> getAllFields(Class<?> clazz, Predicate<Field> predicate) {
231        List<Field> fields = new ArrayList<>();
232        doGetFields(clazz, fields, predicate, false);
233        return fields;
234    }
235
236    public static Field getFirstField(Class<?> clazz, Predicate<Field> predicate) {
237        List<Field> fields = new ArrayList<>();
238        doGetFields(clazz, fields, predicate, true);
239        return fields.isEmpty() ? null : fields.get(0);
240    }
241
242    private static void doGetFields(Class<?> clazz, List<Field> fields, Predicate<Field> predicate, boolean firstOnly) {
243        if (clazz == null || clazz == Object.class) {
244            return;
245        }
246
247        Field[] declaredFields = clazz.getDeclaredFields();
248        for (Field declaredField : declaredFields) {
249            if (predicate == null || predicate.test(declaredField)) {
250                fields.add(declaredField);
251                if (firstOnly) {
252                    break;
253                }
254            }
255        }
256
257        if (firstOnly && !fields.isEmpty()) {
258            return;
259        }
260
261        doGetFields(clazz.getSuperclass(), fields, predicate, firstOnly);
262    }
263
264    public static List<Method> getAllMethods(Class<?> clazz) {
265        List<Method> methods = new ArrayList<>();
266        doGetMethods(clazz, methods, null, false);
267        return methods;
268    }
269
270    public static List<Method> getAllMethods(Class<?> clazz, Predicate<Method> predicate) {
271        List<Method> methods = new ArrayList<>();
272        doGetMethods(clazz, methods, predicate, false);
273        return methods;
274    }
275
276    public static Method getAnyMethod(Class<?> clazz, String... methodNames) {
277        return getFirstMethod(clazz, method -> ArrayUtil.contains(methodNames, method.getName()));
278    }
279
280    public static Method getFirstMethod(Class<?> clazz, Predicate<Method> predicate) {
281        List<Method> methods = new ArrayList<>();
282        doGetMethods(clazz, methods, predicate, true);
283        return methods.isEmpty() ? null : methods.get(0);
284    }
285
286
287    private static void doGetMethods(Class<?> clazz, List<Method> methods, Predicate<Method> predicate, boolean firstOnly) {
288        if (clazz == null || clazz == Object.class) {
289            return;
290        }
291
292        Method[] declaredMethods = clazz.getDeclaredMethods();
293        if (clazz.isInterface()) {
294            for (Method method : declaredMethods) {
295                // 接口类只需要获取 default 方法
296                if (method.isDefault() && (predicate == null || predicate.test(method))) {
297                    methods.add(method);
298                    if (firstOnly) {
299                        break;
300                    }
301                }
302            }
303        } else {
304            for (Method method : declaredMethods) {
305                if (predicate == null || predicate.test(method)) {
306                    methods.add(method);
307                    if (firstOnly) {
308                        break;
309                    }
310                }
311            }
312        }
313
314
315        if (firstOnly && !methods.isEmpty()) {
316            return;
317        }
318
319        Class<?>[] interfaces = clazz.getInterfaces();
320        for (Class<?> anInterface : interfaces) {
321            doGetMethods(anInterface, methods, predicate, firstOnly);
322        }
323
324        doGetMethods(clazz.getSuperclass(), methods, predicate, firstOnly);
325    }
326
327
328    private static <T> Class<T> getJdkProxySuperClass(Class<T> clazz) {
329        final Class<?> proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
330        return (Class<T>) proxyClass.getInterfaces()[0];
331    }
332
333
334    public static boolean isGetterMethod(Method method, String property) {
335        String methodName = method.getName();
336        if (methodName.startsWith("get") && methodName.length() > 3) {
337            return StringUtil.firstCharToUpperCase(property).equals(methodName.substring(3));
338        } else if (methodName.startsWith("is") && methodName.length() > 2) {
339            return StringUtil.firstCharToUpperCase(property).equals(methodName.substring(2));
340        } else {
341            return false;
342        }
343    }
344
345    public static boolean isObjectMethod(String methodName) {
346        return ArrayUtil.contains(OBJECT_METHODS, methodName);
347    }
348
349}