package com.nfwork.dbfound.model.reflector; /* * Copyright 2009-2012 The MyBatis Team * * 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. */ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ReflectPermission; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import com.nfwork.dbfound.exception.DBFoundPackageException; import com.nfwork.dbfound.util.LogUtil; /* * This class represents a cached set of class definition information that * allows for easy mapping between property names and getter/setter methods. */ public class Reflector { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private Class<?> type; private String[] readablePropertyNames = EMPTY_STRING_ARRAY; private String[] writeablePropertyNames = EMPTY_STRING_ARRAY; private Map<String, Invoker> setMethods = new HashMap<String, Invoker>(); private Map<String, Invoker> getMethods = new HashMap<String, Invoker>(); private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>(); private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>(); private Constructor<?> defaultConstructor; // 字段的别名-字段名 private Map<String, String> alias_name = new HashMap<String, String>(); // 字段名-字段的别名 private Map<String, String> name_alias = new HashMap<String, String>(); private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>(); private Reflector(Class<?> clazz) { type = clazz; addDefaultConstructor(clazz); addGetMethods(clazz); addSetMethods(clazz); addFields(clazz); readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writeablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } } private void addDefaultConstructor(Class<?> clazz) { Constructor<?>[] consts = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : consts) { if (constructor.getParameterTypes().length == 0) { if (canAccessPrivateMethods()) { try { constructor.setAccessible(true); } catch (Exception e) { // Ignored. This is only a final precaution, nothing we // can do. } } if (constructor.isAccessible()) { this.defaultConstructor = constructor; } } } } private void addGetMethods(Class<?> cls) { Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>(); Method[] methods = getClassMethods(cls); for (Method method : methods) { String name = method.getName(); if (name.startsWith("get") && name.length() > 3) { if (method.getParameterTypes().length == 0) { name = PropertyNamer.methodToProperty(name); addMethodConflict(conflictingGetters, name, method); } } else if (name.startsWith("is") && name.length() > 2) { if (method.getParameterTypes().length == 0) { name = PropertyNamer.methodToProperty(name); addMethodConflict(conflictingGetters, name, method); } } } resolveGetterConflicts(conflictingGetters); } private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) { for (String propName : conflictingGetters.keySet()) { List<Method> getters = conflictingGetters.get(propName); Iterator<Method> iterator = getters.iterator(); Method firstMethod = iterator.next(); if (getters.size() == 1) { addGetMethod(propName, firstMethod); } else { Method getter = firstMethod; Class<?> getterType = firstMethod.getReturnType(); while (iterator.hasNext()) { Method method = iterator.next(); Class<?> methodType = method.getReturnType(); if (methodType.equals(getterType)) { throw new ReflectionException( "Illegal overloaded getter method with ambiguous type for property " + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results."); } else if (methodType.isAssignableFrom(getterType)) { // OK getter type is descendant } else if (getterType.isAssignableFrom(methodType)) { getter = method; getterType = methodType; } else { throw new ReflectionException( "Illegal overloaded getter method with ambiguous type for property " + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results."); } } addGetMethod(propName, getter); } } } private void addGetMethod(String name, Method method) { if (isValidPropertyName(name)) { getMethods.put(name, new MethodInvoker(method)); getTypes.put(name, method.getReturnType()); } } private void addSetMethods(Class<?> cls) { Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>(); Method[] methods = getClassMethods(cls); for (Method method : methods) { String name = method.getName(); if (name.startsWith("set") && name.length() > 3) { if (method.getParameterTypes().length == 1) { name = PropertyNamer.methodToProperty(name); addMethodConflict(conflictingSetters, name, method); } } } resolveSetterConflicts(conflictingSetters); } private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) { List<Method> list = conflictingMethods.get(name); if (list == null) { list = new ArrayList<Method>(); conflictingMethods.put(name, list); } list.add(method); } private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) { for (String propName : conflictingSetters.keySet()) { List<Method> setters = conflictingSetters.get(propName); Method firstMethod = setters.get(0); if (setters.size() == 1) { addSetMethod(propName, firstMethod); } else { Class<?> expectedType = getTypes.get(propName); if (expectedType == null) { throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property " + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results."); } else { Iterator<Method> methods = setters.iterator(); Method setter = null; while (methods.hasNext()) { Method method = methods.next(); if (method.getParameterTypes().length == 1 && expectedType.equals(method.getParameterTypes()[0])) { setter = method; break; } } if (setter == null) { throw new ReflectionException( "Illegal overloaded setter method with ambiguous type for property " + propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results."); } addSetMethod(propName, setter); } } } } private void addSetMethod(String name, Method method) { if (isValidPropertyName(name)) { setMethods.put(name, new MethodInvoker(method)); setTypes.put(name, method.getParameterTypes()[0]); } } private void addFields(Class<?> clazz) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (canAccessPrivateMethods()) { try { field.setAccessible(true); } catch (Exception e) { // Ignored. This is only a final precaution, nothing we can // do. } } if (field.isAccessible()) { if (!setMethods.containsKey(field.getName())) { // issue 379 - removed the check for final because JDK 1.5 // allows // modification of final fields through reflection // (JSR-133). (JGB) addSetField(field); } if (!getMethods.containsKey(field.getName())) { addGetField(field); } } // 处理g3db_alias注解 Column alias = field.getAnnotation(Column.class); if (alias != null) { alias_name.put(alias.name(), field.getName()); name_alias.put(field.getName(), alias.name()); } } if (clazz.getSuperclass() != null) { addFields(clazz.getSuperclass()); } } private void addSetField(Field field) { if (isValidPropertyName(field.getName())) { setMethods.put(field.getName(), new SetFieldInvoker(field)); setTypes.put(field.getName(), field.getType()); } } private void addGetField(Field field) { if (isValidPropertyName(field.getName())) { getMethods.put(field.getName(), new GetFieldInvoker(field)); getTypes.put(field.getName(), field.getType()); } } private boolean isValidPropertyName(String name) { return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name)); } /* * This method returns an array containing all methods declared in this * class and any superclass. We use this method, instead of the simpler * Class.getMethods(), because we want to look for private methods as well. * * @param cls The class * * @return An array containing all methods in this class */ private Method[] getClassMethods(Class<?> cls) { HashMap<String, Method> uniqueMethods = new HashMap<String, Method>(); Class<?> currentClass = cls; while (currentClass != null) { addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); // we also need to look for interface methods - // because the class may be abstract Class<?>[] interfaces = currentClass.getInterfaces(); for (Class<?> anInterface : interfaces) { addUniqueMethods(uniqueMethods, anInterface.getMethods()); } currentClass = currentClass.getSuperclass(); } Collection<Method> methods = uniqueMethods.values(); return methods.toArray(new Method[methods.size()]); } private void addUniqueMethods(HashMap<String, Method> uniqueMethods, Method[] methods) { for (Method currentMethod : methods) { if (!currentMethod.isBridge()) { String signature = getSignature(currentMethod); // check to see if the method is already known // if it is known, then an extended class must have // overridden a method if (!uniqueMethods.containsKey(signature)) { if (canAccessPrivateMethods()) { try { currentMethod.setAccessible(true); } catch (Exception e) { // Ignored. This is only a final precaution, nothing // we can do. } } uniqueMethods.put(signature, currentMethod); } } } } private String getSignature(Method method) { StringBuilder sb = new StringBuilder(); Class<?> returnType = method.getReturnType(); if (returnType != null) { sb.append(returnType.getName()).append('#'); } sb.append(method.getName()); Class<?>[] parameters = method.getParameterTypes(); for (int i = 0; i < parameters.length; i++) { if (i == 0) { sb.append(':'); } else { sb.append(','); } sb.append(parameters[i].getName()); } return sb.toString(); } private static boolean canAccessPrivateMethods() { try { SecurityManager securityManager = System.getSecurityManager(); if (null != securityManager) { securityManager.checkPermission(new ReflectPermission("suppressAccessChecks")); } } catch (SecurityException e) { return false; } return true; } /* * Gets the name of the class the instance provides information for * * @return The class name */ public Class<?> getType() { return type; } public Constructor<?> getDefaultConstructor() { if (defaultConstructor != null) { return defaultConstructor; } else { throw new ReflectionException("There is no default constructor for " + type); } } public Invoker getSetInvoker(String propertyName) { Invoker method = setMethods.get(propertyName); if (method == null) { throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'"); } return method; } public Invoker getGetInvoker(String propertyName) { Invoker method = getMethods.get(propertyName); if (method == null) { throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'"); } return method; } /* * Gets the type for a property setter * * @param propertyName - the name of the property * * @return The Class of the propery setter */ public Class<?> getSetterType(String propertyName) { Class<?> clazz = setTypes.get(propertyName); if (clazz == null) { throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'"); } return clazz; } /* * Gets the type for a property getter * * @param propertyName - the name of the property * * @return The Class of the propery getter */ public Class<?> getGetterType(String propertyName) { Class<?> clazz = getTypes.get(propertyName); if (clazz == null) { throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'"); } return clazz; } /* * Gets an array of the readable properties for an object * * @return The array */ public String[] getGetablePropertyNames() { return readablePropertyNames; } /* * Gets an array of the writeable properties for an object * * @return The array */ public String[] getSetablePropertyNames() { return writeablePropertyNames; } /* * Check to see if a class has a writeable property by name * * @param propertyName - the name of the property to check * * @return True if the object has a writeable property by the name */ public boolean hasSetter(String propertyName) { return setMethods.keySet().contains(propertyName); } /* * Check to see if a class has a readable property by name * * @param propertyName - the name of the property to check * * @return True if the object has a readable property by the name */ public boolean hasGetter(String propertyName) { return getMethods.keySet().contains(propertyName); } public String findPropertyName(String name) { return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH)); } /* * Gets an instance of ClassInfo for the specified class. * * @param clazz The class for which to lookup the method cache. * * @return The method cache for the class */ private static final ConcurrentMap<Class<?>, Future<Reflector>> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Future<Reflector>>(); public static Reflector forClass(final Class<?> clazz) { Future<Reflector> future = REFLECTOR_MAP.get(clazz); if (future == null) { Callable<Reflector> callable = new Callable<Reflector>() { public Reflector call() throws Exception { Reflector cached = new Reflector(clazz); LogUtil.info(String.format("load class %s Constructor success", clazz)); return cached; } }; FutureTask<Reflector> task = new FutureTask<Reflector>(callable); future = REFLECTOR_MAP.putIfAbsent(clazz, task); if (future == null) { future = task; task.run(); } } try { return future.get(); } catch (Exception e) { REFLECTOR_MAP.remove(clazz); throw new DBFoundPackageException(e); } } public String getFieldName(String column) { String name = alias_name.get(column); if (name != null) { return name; } else { return column; } } public String getColumn(String name) { String column = name_alias.get(name); if (column != null) { return column; } else { return name; } } }