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