org.apache.ibatis.reflection.Reflector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mybatis Show documentation
Show all versions of mybatis Show documentation
The MyBatis SQL mapper framework makes it easier to use a relational database with object-oriented
applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or
annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping
tools.
/*
* Copyright 2009-2012 the original author or authors.
*
* 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.apache.ibatis.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
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.ConcurrentHashMap;
import org.apache.ibatis.reflection.invoker.GetFieldInvoker;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.reflection.invoker.MethodInvoker;
import org.apache.ibatis.reflection.invoker.SetFieldInvoker;
import org.apache.ibatis.reflection.property.PropertyNamer;
/*
* This class represents a cached set of class definition information that
* allows for easy mapping between property names and getter/setter methods.
*/
/**
* @author Clinton Begin
*/
public class Reflector {
private static boolean classCacheEnabled = true;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Map, Reflector> REFLECTOR_MAP = new ConcurrentHashMap, Reflector>();
private Class> type;
private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
private Map setMethods = new HashMap();
private Map getMethods = new HashMap();
private Map> setTypes = new HashMap>();
private Map> getTypes = new HashMap>();
private Constructor> defaultConstructor;
private Map caseInsensitivePropertyMap = new HashMap();
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> conflictingGetters = new HashMap>();
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> conflictingGetters) {
for (String propName : conflictingGetters.keySet()) {
List getters = conflictingGetters.get(propName);
Iterator 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> conflictingSetters = new HashMap>();
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> conflictingMethods, String name, Method method) {
List list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList();
conflictingMethods.put(name, list);
}
list.add(method);
}
private void resolveSetterConflicts(Map> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List 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 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)
// pr #16 - final static can only be set by the classloader
int modifiers = field.getModifiers();
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
addSetField(field);
}
}
if (!getMethods.containsKey(field.getName())) {
addGetField(field);
}
}
}
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 uniqueMethods = new HashMap();
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 methods = uniqueMethods.values();
return methods.toArray(new Method[methods.size()]);
}
private void addUniqueMethods(HashMap 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
*/
public static Reflector forClass(Class> clazz) {
if (classCacheEnabled) {
// synchronized (clazz) removed see issue #461
Reflector cached = REFLECTOR_MAP.get(clazz);
if (cached == null) {
cached = new Reflector(clazz);
REFLECTOR_MAP.put(clazz, cached);
}
return cached;
} else {
return new Reflector(clazz);
}
}
public static void setClassCacheEnabled(boolean classCacheEnabled) {
Reflector.classCacheEnabled = classCacheEnabled;
}
public static boolean isClassCacheEnabled() {
return classCacheEnabled;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy