/* * Copyright (c) 2009-2016, b3log.org & hacpai.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.b3log.latke.util; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.NotFoundException; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; import org.b3log.latke.logging.Level; import org.b3log.latke.logging.Logger; /** * Reflection utilities. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="mailto:wmainlove@gmail.com">Love Yao</a> * @version 1.0.0.5, Dec 5, 2013 */ final public class Reflections { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(Reflections.class.getName()); /** * Private constructor. */ private Reflections() {} /** * the maxFindLength to get the 'this' keyword when resolving the vaibleNames. */ private static final Integer MAX_FIND_LENGTH = 30; /** * Class pool. */ private static final ClassPool CLASS_POOL = ClassPool.getDefault(); static { CLASS_POOL.insertClassPath(new ClassClassPath(Reflections.class)); } /** * getMethodVariableNames in user defined. * * @param clazz the specific clazz * @param targetMethodName the targetMethodName * @param types the types of the method parameters * @return the String[] of names */ public static String[] getMethodVariableNames(final Class<?> clazz, final String targetMethodName, final Class<?>[] types) { CtClass cc; CtMethod cm = null; try { if (null == CLASS_POOL.find(clazz.getName())) { CLASS_POOL.insertClassPath(new ClassClassPath(clazz)); } cc = CLASS_POOL.get(clazz.getName()); final CtClass[] ptypes = new CtClass[types.length]; for (int i = 0; i < ptypes.length; i++) { ptypes[i] = CLASS_POOL.get(types[i].getName()); } cm = cc.getDeclaredMethod(targetMethodName, ptypes); } catch (final NotFoundException e) { LOGGER.log(Level.ERROR, "Get method variable names failed", e); } if (null == cm) { return new String[types.length]; } final MethodInfo methodInfo = cm.getMethodInfo(); final CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); final LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); String[] variableNames = new String[0]; try { variableNames = new String[cm.getParameterTypes().length]; } catch (final NotFoundException e) { LOGGER.log(Level.ERROR, "Get method variable names failed", e); } // final int staticIndex = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; int j = -1; String variableName = null; Boolean ifkill = false; while (!"this".equals(variableName)) { j++; variableName = attr.variableName(j); // to prevent heap error when there being some unknown reasons to resolve the VariableNames if (j > MAX_FIND_LENGTH) { LOGGER.log(Level.WARN, "Maybe resolve to VariableNames error [class=" + clazz.getName() + ", targetMethodName=" + targetMethodName + ']'); ifkill = true; break; } } if (!ifkill) { for (int i = 0; i < variableNames.length; i++) { variableNames[i] = attr.variableName(++j); } } return variableNames; } public static boolean isConcrete(final Type type) { return isConcrete((Class<?>) type); } public static boolean isConcrete(final Class<?> clazz) { final int modifiers = clazz.getModifiers(); return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); } public static boolean isInterface(final Class<?> clazz) { return Modifier.isInterface(clazz.getModifiers()); } public static boolean isAbstract(final Class<?> clazz) { return Modifier.isAbstract(clazz.getModifiers()); } public static boolean isAccessable(final Package subclassPackage, final Package superclassPackage, final int superMemberModifiers) { if (subclassPackage.equals(superclassPackage)) { switch (superMemberModifiers) { case Modifier.PRIVATE: return false; case Modifier.PROTECTED: return true; case Modifier.PUBLIC: return true; default: return true; } } else { switch (superMemberModifiers) { case Modifier.PRIVATE: return false; case Modifier.PROTECTED: return true; case Modifier.PUBLIC: return true; default: return false; } } } public static Set<Field> getInheritedFields(final Class<?> clazz) { final Field[] fields = clazz.getDeclaredFields(); final Set<Field> declaredFieldSet = CollectionUtils.arrayToSet(fields); final Set<Field> ret = new HashSet<Field>(declaredFieldSet); Class<?> currentClass = clazz.getSuperclass(); while (currentClass != null) { final Field[] superFields = currentClass.getDeclaredFields(); for (Field superField : superFields) { if (!Modifier.isPrivate(superField.getModifiers()) && !Modifier.isStatic(superField.getModifiers()) && !containField(ret, superField)) { ret.add(superField); } } currentClass = currentClass.getSuperclass(); } ret.removeAll(declaredFieldSet); return ret; } public static Set<Field> getHiddenFields(final Class<?> clazz) { final Field[] fields = clazz.getDeclaredFields(); final Set<Field> declaredFieldSet = CollectionUtils.arrayToSet(fields); final Set<Field> ret = new HashSet<Field>(); Class<?> currentClass = clazz.getSuperclass(); while (currentClass != null) { final Field[] superFields = currentClass.getDeclaredFields(); for (Field superField : superFields) { final Field match = getMatch(declaredFieldSet, superField); if (!Modifier.isPrivate(superField.getModifiers()) && !Modifier.isStatic(superField.getModifiers()) && match != null) { ret.add(match); } } currentClass = currentClass.getSuperclass(); } return ret; } public static Set<Field> getOwnFields(final Class<?> clazz) { final Field[] fields = clazz.getDeclaredFields(); final Set<Field> declaredFieldSet = CollectionUtils.arrayToSet(fields); final Set<Field> ret = new HashSet<Field>(); final Set<Field> inheritedFields = getInheritedFields(clazz); final Set<Field> overriddenFields = getHiddenFields(clazz); for (final Field declaredField : declaredFieldSet) { if (!containField(inheritedFields, declaredField) && !containField(overriddenFields, declaredField)) { ret.add(declaredField); } } return ret; } public static Set<Method> getOwnMethods(final Class<?> clazz) { final Method[] methods = clazz.getDeclaredMethods(); final Set<Method> declaredMethodSet = CollectionUtils.arrayToSet(methods); final Set<Method> ret = new HashSet<Method>(); final Set<Method> inheritedMethods = getInheritedMethods(clazz); final Set<Method> overriddenMethods = getOverriddenMethods(clazz); for (final Method declaredMethod : declaredMethodSet) { if (!containMethod(inheritedMethods, declaredMethod) && !containMethod(overriddenMethods, declaredMethod)) { ret.add(declaredMethod); } } return ret; } public static Set<Method> getInheritedMethods(final Class<?> clazz) { final Method[] methods = clazz.getDeclaredMethods(); final Set<Method> declaredMethodSet = CollectionUtils.arrayToSet(methods); final Set<Method> ret = new HashSet<Method>(declaredMethodSet); Class<?> currentClass = clazz.getSuperclass(); while (currentClass != null) { final Method[] superMethods = currentClass.getDeclaredMethods(); for (Method superMethod : superMethods) { if (!Modifier.isPrivate(superMethod.getModifiers()) && !Modifier.isStatic(superMethod.getModifiers()) && !containMethod(ret, superMethod)) { ret.add(superMethod); } } currentClass = currentClass.getSuperclass(); } ret.removeAll(declaredMethodSet); return ret; } public static Set<Method> getOverriddenMethods(final Class<?> clazz) { final Method[] methods = clazz.getDeclaredMethods(); final Set<Method> declaredMethodSet = CollectionUtils.arrayToSet(methods); final Set<Method> ret = new HashSet<Method>(); Class<?> currentClass = clazz.getSuperclass(); while (currentClass != null) { final Method[] superclassMethods = currentClass.getDeclaredMethods(); for (Method superclassMethod : superclassMethods) { final Method match = getMatch(declaredMethodSet, superclassMethod, true); if (match != null) { ret.add(match); } } currentClass = currentClass.getSuperclass(); } return ret; } public static boolean containField(final Set<Field> fields, final Field field) { for (final Field f : fields) { if (f.getName().equals(field.getName()) && f.getType().equals(field.getType())) { return true; } } return false; } public static boolean containMethod(final Set<Method> methods, final Method method) { for (final Method m : methods) { if (matchSignature(m, method) && matchModifier(m, method)) { return true; } } return false; } public static Field getMatch(final Set<Field> fields, final Field field) { for (final Field f : fields) { if (match(f, field)) { return f; } } return null; } public static Method getMatch(final Set<Method> methods, final Method maybeSuperclassMethod, final boolean matchInheritance) { for (final Method m : methods) { if (!matchInheritance) { if (matchModifier(m, maybeSuperclassMethod) && matchSignature(m, maybeSuperclassMethod)) { return m; } } else { if (matchInheritance(m, maybeSuperclassMethod)) { return m; } } } return null; } public static boolean match(final Field field1, final Field field2) { if (field1.getName().equals(field2.getName()) && field1.getType().equals(field2.getType())) { return true; } else { return false; } } public static boolean matchInheritance(final Field subclassField, final Field superclassField) { if (Modifier.isStatic(superclassField.getModifiers()) || subclassField.equals(superclassField)) { return false; } final Package subclassPackage = superclassField.getDeclaringClass().getPackage(); final Package superclassPackage = superclassField.getDeclaringClass().getPackage(); final int superFieldModifiers = superclassField.getModifiers(); return isAccessable(subclassPackage, superclassPackage, superFieldModifiers); } public static boolean matchInheritance(final Method subclassMethod, final Method superclassMethod) { if (Modifier.isStatic(superclassMethod.getModifiers()) || subclassMethod.equals(superclassMethod)) { return false; } if (matchSignature(subclassMethod, superclassMethod)) { final Package subclassPackage = subclassMethod.getDeclaringClass().getPackage(); final Package superclassPackage = superclassMethod.getDeclaringClass().getPackage(); final int superMethodModifiers = superclassMethod.getModifiers(); return isAccessable(subclassPackage, superclassPackage, superMethodModifiers); } else { return false; } } public static boolean matchModifier(final Method method1, final Method method2) { if (method1.getModifiers() == method2.getModifiers()) { return true; } else { return false; } } public static boolean matchSignature(final Method method1, final Method method2) { if (method1.getName().equals(method2.getName()) && method1.getReturnType().equals(method2.getReturnType()) && hasSameParameterTypes(method1, method2)) { return true; } else { return false; } } public static boolean hasSameParameterTypes(final Method method1, final Method method2) { final Class<?>[] parameterTypes1 = method1.getParameterTypes(); final Class<?>[] parameterTypes2 = method2.getParameterTypes(); if (parameterTypes1.length == parameterTypes2.length) { for (int i = 0; i < parameterTypes1.length; i++) { if (!parameterTypes1[i].equals(parameterTypes2[i])) { return false; } } return true; } else { return false; } } public static Field getHideField(final Field superclassField, final Class<?> subclass) { final Class<?> superclass = superclassField.getDeclaringClass(); Class<?> currentClass = subclass; if (!superclass.isAssignableFrom(subclass)) { throw new RuntimeException("Class[" + subclass + "] is not superclass" + " of class[" + subclass + "]"); } while (!currentClass.equals(superclass)) { try { final Field m = currentClass.getDeclaredField(superclassField.getName()); if (matchInheritance(m, superclassField)) { return m; } } catch (final Exception ex) {} currentClass = currentClass.getSuperclass(); } return superclassField; } public static Method getOverrideMethod(final Method superclassMethod, final Class<?> subclass) { final Class<?> superclass = superclassMethod.getDeclaringClass(); Class<?> currentClass = subclass; if (!superclass.isAssignableFrom(subclass)) { throw new RuntimeException("Class[" + subclass + "] is not superclass" + " of class[" + subclass + "]"); } while (!currentClass.equals(superclass)) { try { final Method m = currentClass.getDeclaredMethod(superclassMethod.getName(), superclassMethod.getParameterTypes()); if (matchInheritance(m, superclassMethod)) { return m; } } catch (final Exception ex) {} currentClass = currentClass.getSuperclass(); } return superclassMethod; } }