org.seasar.framework.beans.impl.BeanDescImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* 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.seasar.framework.beans.impl;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.ConstructorNotFoundRuntimeException;
import org.seasar.framework.beans.FieldNotFoundRuntimeException;
import org.seasar.framework.beans.IllegalDiiguRuntimeException;
import org.seasar.framework.beans.MethodNotFoundRuntimeException;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.PropertyNotFoundRuntimeException;
import org.seasar.framework.beans.factory.ParameterizedClassDescFactory;
import org.seasar.framework.conversion.DoubleConversionUtil;
import org.seasar.framework.conversion.FloatConversionUtil;
import org.seasar.framework.conversion.IntegerConversionUtil;
import org.seasar.framework.conversion.LongConversionUtil;
import org.seasar.framework.conversion.ShortConversionUtil;
import org.seasar.framework.exception.EmptyRuntimeException;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.ArrayMap;
import org.seasar.framework.util.CaseInsensitiveMap;
import org.seasar.framework.util.ClassPoolUtil;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ConstructorUtil;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.StringUtil;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
/**
* {@link BeanDesc}の実装クラスです。
*
* @author higa
*
*/
public class BeanDescImpl implements BeanDesc {
private static final Logger logger = Logger.getLogger(BeanDescImpl.class);
private static final Object[] EMPTY_ARGS = new Object[0];
private static final Class>[] EMPTY_PARAM_TYPES = new Class[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String PARAMETER_NAME_ANNOTATION = "org.seasar.framework.beans.annotation.ParameterName";
private Class> beanClass;
private Constructor>[] constructors;
private Map, Type> typeVariables;
private CaseInsensitiveMap propertyDescCache = new CaseInsensitiveMap<>();
private Map methodsCache = new HashMap<>();
private ArrayMap fieldCache = new ArrayMap<>();
private transient Set invalidPropertyNames = new HashSet<>();
private Map, String[]> constructorParameterNamesCache;
private Map methodParameterNamesCache;
/**
* {@link BeanDescImpl}を作成します。
*
* @param beanClass beanClass
* @throws EmptyRuntimeException {@link EmptyRuntimeException}
*/
public BeanDescImpl(Class> beanClass) throws EmptyRuntimeException {
if (beanClass == null) {
throw new EmptyRuntimeException("beanClass");
}
this.beanClass = beanClass;
constructors = beanClass.getConstructors();
typeVariables = ParameterizedClassDescFactory
.getTypeVariables(beanClass);
setupPropertyDescs();
setupMethods();
setupFields();
}
/**
* @see org.seasar.framework.beans.BeanDesc#getBeanClass()
*/
@Override
public Class> getBeanClass() {
return beanClass;
}
@Override
public boolean hasPropertyDesc(String propertyName) {
return propertyDescCache.get(propertyName) != null;
}
/**
* @see org.seasar.framework.beans.BeanDesc#getPropertyDesc(java.lang.String)
*/
@Override
public PropertyDesc getPropertyDesc(String propertyName)
throws PropertyNotFoundRuntimeException {
PropertyDesc pd = propertyDescCache.get(propertyName);
if (pd == null) {
throw new PropertyNotFoundRuntimeException(beanClass, propertyName);
}
return pd;
}
private PropertyDesc getPropertyDesc0(String propertyName) {
return propertyDescCache.get(propertyName);
}
/**
* @see org.seasar.framework.beans.BeanDesc#getPropertyDesc(int)
*/
@Override
public PropertyDesc getPropertyDesc(int index) {
return propertyDescCache.get(index);
}
/**
* @see org.seasar.framework.beans.BeanDesc#getPropertyDescSize()
*/
@Override
public int getPropertyDescSize() {
return propertyDescCache.size();
}
/**
* @see org.seasar.framework.beans.BeanDesc#hasField(java.lang.String)
*/
@Override
public boolean hasField(String fieldName) {
return fieldCache.get(fieldName) != null;
}
/**
* @see org.seasar.framework.beans.BeanDesc#getField(java.lang.String)
*/
@Override
public Field getField(String fieldName) {
Field field = fieldCache.get(fieldName);
if (field == null) {
throw new FieldNotFoundRuntimeException(beanClass, fieldName);
}
return field;
}
/**
* @see org.seasar.framework.beans.BeanDesc#getField(int)
*/
@Override
public Field getField(int index) {
return fieldCache.get(index);
}
/**
* @see org.seasar.framework.beans.BeanDesc#getFieldValue(java.lang.String,
* java.lang.Object)
*/
@Override
public Object getFieldValue(String fieldName, Object target)
throws FieldNotFoundRuntimeException {
Field field = getField(fieldName);
return FieldUtil.get(field, target);
}
/**
* @see org.seasar.framework.beans.BeanDesc#getFieldSize()
*/
@Override
public int getFieldSize() {
return fieldCache.size();
}
/**
* @see org.seasar.framework.beans.BeanDesc#newInstance(java.lang.Object[])
*/
@Override
public Object newInstance(Object[] args)
throws ConstructorNotFoundRuntimeException {
Constructor> constructor = getSuitableConstructor(args);
return ConstructorUtil.newInstance(constructor, args);
}
/**
* @see org.seasar.framework.beans.BeanDesc#invoke(java.lang.Object,
* java.lang.String, java.lang.Object[])
*/
@Override
public Object invoke(Object target, String methodName, Object[] args) {
Method method = getSuitableMethod(methodName, args);
return MethodUtil.invoke(method, target, args);
}
/**
* @see org.seasar.framework.beans.BeanDesc#getSuitableConstructor(java.lang.Object[])
*/
@Override
public Constructor> getSuitableConstructor(Object[] args)
throws ConstructorNotFoundRuntimeException {
if (args == null) {
args = EMPTY_ARGS;
}
Constructor> constructor = findSuitableConstructor(args);
if (constructor != null) {
return constructor;
}
constructor = findSuitableConstructorAdjustNumber(args);
if (constructor != null) {
return constructor;
}
throw new ConstructorNotFoundRuntimeException(beanClass, args);
}
@Override
public Constructor> getConstructor(final Class>[] paramTypes) {
for (int i = 0; i < constructors.length; ++i) {
if (Arrays.equals(paramTypes, constructors[i].getParameterTypes())) {
return constructors[i];
}
}
throw new ConstructorNotFoundRuntimeException(beanClass, paramTypes);
}
@Override
public Method getMethod(final String methodName) {
return getMethod(methodName, EMPTY_PARAM_TYPES);
}
@Override
public Method getMethodNoException(final String methodName) {
return getMethodNoException(methodName, EMPTY_PARAM_TYPES);
}
@Override
public Method getMethod(final String methodName, final Class>[] paramTypes) {
Method method = getMethodNoException(methodName, paramTypes);
if (method != null) {
return method;
}
throw new MethodNotFoundRuntimeException(beanClass, methodName,
paramTypes);
}
@Override
public Method getMethodNoException(final String methodName,
final Class>[] paramTypes) {
final Method[] methods = methodsCache.get(methodName);
if (methods == null) {
return null;
}
for (int i = 0; i < methods.length; ++i) {
if (Arrays.equals(paramTypes, methods[i].getParameterTypes())) {
return methods[i];
}
}
return null;
}
/**
* @see org.seasar.framework.beans.BeanDesc#getMethods(java.lang.String)
*/
@Override
public Method[] getMethods(String methodName)
throws MethodNotFoundRuntimeException {
Method[] methods = methodsCache.get(methodName);
if (methods == null) {
throw new MethodNotFoundRuntimeException(beanClass, methodName,
null);
}
return methods;
}
@Override
public boolean hasMethod(String methodName) {
return methodsCache.get(methodName) != null;
}
@Override
public String[] getMethodNames() {
return methodsCache.keySet().toArray(
new String[methodsCache.size()]);
}
@Override
public String[] getConstructorParameterNames(final Class>[] parameterTypes) {
return getConstructorParameterNames(getConstructor(parameterTypes));
}
@Override
public String[] getConstructorParameterNames(final Constructor> constructor) {
if (constructorParameterNamesCache == null) {
constructorParameterNamesCache = createConstructorParameterNamesCache();
}
if (!constructorParameterNamesCache.containsKey(constructor)) {
throw new ConstructorNotFoundRuntimeException(beanClass,
constructor.getParameterTypes());
}
return constructorParameterNamesCache.get(constructor);
}
@Override
public String[] getMethodParameterNamesNoException(final String methodName,
final Class>[] parameterTypes) {
return getMethodParameterNamesNoException(getMethod(methodName,
parameterTypes));
}
@Override
public String[] getMethodParameterNames(final String methodName,
final Class>[] parameterTypes) {
return getMethodParameterNames(getMethod(methodName, parameterTypes));
}
@Override
public String[] getMethodParameterNames(final Method method) {
String[] names = getMethodParameterNamesNoException(method);
if (names == null || names.length != method.getParameterTypes().length) {
throw new IllegalDiiguRuntimeException();
}
return names;
}
@Override
public String[] getMethodParameterNamesNoException(final Method method) {
if (methodParameterNamesCache == null) {
methodParameterNamesCache = createMethodParameterNamesCache();
}
if (!methodParameterNamesCache.containsKey(method)) {
throw new MethodNotFoundRuntimeException(beanClass, method
.getName(), method.getParameterTypes());
}
return methodParameterNamesCache.get(method);
}
private Map, String[]> createConstructorParameterNamesCache() {
final Map, String[]> map = new HashMap<>();
final ClassPool pool = ClassPoolUtil.getClassPool(beanClass);
for (int i = 0; i < constructors.length; ++i) {
final Constructor> constructor = constructors[i];
if (constructor.getParameterTypes().length == 0) {
map.put(constructor, EMPTY_STRING_ARRAY);
continue;
}
final CtClass clazz = ClassPoolUtil.toCtClass(pool, constructor
.getDeclaringClass());
final CtClass[] parameterTypes = ClassPoolUtil.toCtClassArray(pool,
constructor.getParameterTypes());
try {
final String[] names = getParameterNames(clazz
.getDeclaredConstructor(parameterTypes));
map.put(constructor, names);
} catch (final NotFoundException e) {
logger.log("WSSR0084", new Object[] { beanClass.getName(),
constructor });
}
}
return map;
}
private Map createMethodParameterNamesCache() {
final Map map = new HashMap<>();
final ClassPool pool = ClassPoolUtil.getClassPool(beanClass);
for (final Iterator it = methodsCache.values().iterator(); it.hasNext();) {
final Method[] methods = it.next();
for (int i = 0; i < methods.length; ++i) {
final Method method = methods[i];
if (method.getParameterTypes().length == 0) {
map.put(methods[i], EMPTY_STRING_ARRAY);
continue;
}
final CtClass clazz = ClassPoolUtil.toCtClass(pool, method
.getDeclaringClass());
final CtClass[] parameterTypes = ClassPoolUtil.toCtClassArray(
pool, method.getParameterTypes());
try {
final String[] names = getParameterNames(clazz
.getDeclaredMethod(method.getName(), parameterTypes));
map.put(methods[i], names);
} catch (final NotFoundException e) {
logger.log("WSSR0085", new Object[] { beanClass.getName(),
method });
}
}
}
return map;
}
private String[] getParameterNames(final CtBehavior behavior)
throws NotFoundException {
final MethodInfo methodInfo = behavior.getMethodInfo();
final ParameterAnnotationsAttribute attribute = (ParameterAnnotationsAttribute) methodInfo
.getAttribute(ParameterAnnotationsAttribute.visibleTag);
if (attribute == null) {
return null;
}
final int numParameters = behavior.getParameterTypes().length;
final String[] parameterNames = new String[numParameters];
final Annotation[][] annotationsArray = attribute.getAnnotations();
if (annotationsArray == null
|| annotationsArray.length != numParameters) {
return null;
}
for (int i = 0; i < numParameters; ++i) {
final String parameterName = getParameterName(annotationsArray[i]);
if (parameterName == null) {
return null;
}
parameterNames[i] = parameterName;
}
return parameterNames;
}
private String getParameterName(final Annotation[] annotations) {
Annotation nameAnnotation = null;
for (int i = 0; i < annotations.length; ++i) {
final Annotation annotation = annotations[i];
if (PARAMETER_NAME_ANNOTATION.equals(annotation.getTypeName())) {
nameAnnotation = annotation;
break;
}
}
if (nameAnnotation == null) {
return null;
}
return ((StringMemberValue) nameAnnotation.getMemberValue("value"))
.getValue();
}
private Constructor> findSuitableConstructor(Object[] args) {
outerLoop: for (int i = 0; i < constructors.length; ++i) {
Class>[] paramTypes = constructors[i].getParameterTypes();
if (paramTypes.length != args.length) {
continue;
}
for (int j = 0; j < args.length; ++j) {
if (args[j] == null
|| ClassUtil.isAssignableFrom(paramTypes[j], args[j]
.getClass())) {
continue;
}
continue outerLoop;
}
return constructors[i];
}
return null;
}
private Constructor> findSuitableConstructorAdjustNumber(Object[] args) {
outerLoop: for (int i = 0; i < constructors.length; ++i) {
Class>[] paramTypes = constructors[i].getParameterTypes();
if (paramTypes.length != args.length) {
continue;
}
for (int j = 0; j < args.length; ++j) {
if (args[j] == null
|| ClassUtil.isAssignableFrom(paramTypes[j], args[j]
.getClass())
|| adjustNumber(paramTypes, args, j)) {
continue;
}
continue outerLoop;
}
return constructors[i];
}
return null;
}
private static boolean adjustNumber(Class>[] paramTypes, Object[] args,
int index) {
if (paramTypes[index].isPrimitive()) {
if (paramTypes[index] == int.class) {
args[index] = IntegerConversionUtil.toInteger(args[index]);
return true;
} else if (paramTypes[index] == double.class) {
args[index] = DoubleConversionUtil.toDouble(args[index]);
return true;
} else if (paramTypes[index] == long.class) {
args[index] = LongConversionUtil.toLong(args[index]);
return true;
} else if (paramTypes[index] == short.class) {
args[index] = ShortConversionUtil.toShort(args[index]);
return true;
} else if (paramTypes[index] == float.class) {
args[index] = FloatConversionUtil.toFloat(args[index]);
return true;
}
} else {
if (paramTypes[index] == Integer.class) {
args[index] = IntegerConversionUtil.toInteger(args[index]);
return true;
} else if (paramTypes[index] == Double.class) {
args[index] = DoubleConversionUtil.toDouble(args[index]);
return true;
} else if (paramTypes[index] == Long.class) {
args[index] = LongConversionUtil.toLong(args[index]);
return true;
} else if (paramTypes[index] == Short.class) {
args[index] = ShortConversionUtil.toShort(args[index]);
return true;
} else if (paramTypes[index] == Float.class) {
args[index] = FloatConversionUtil.toFloat(args[index]);
return true;
}
}
return false;
}
private void setupPropertyDescs() {
Method[] methods = beanClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (MethodUtil.isBridgeMethod(m) || MethodUtil.isSyntheticMethod(m)) {
continue;
}
String methodName = m.getName();
if (methodName.startsWith("get")) {
if (m.getParameterTypes().length != 0
|| methodName.equals("getClass")
|| m.getReturnType() == void.class) {
continue;
}
String propertyName = decapitalizePropertyName(methodName
.substring(3));
setupReadMethod(m, propertyName);
} else if (methodName.startsWith("is")) {
if (m.getParameterTypes().length != 0
|| !m.getReturnType().equals(Boolean.TYPE)
&& !m.getReturnType().equals(Boolean.class)) {
continue;
}
String propertyName = decapitalizePropertyName(methodName
.substring(2));
setupReadMethod(m, propertyName);
} else if (methodName.startsWith("set")) {
if (m.getParameterTypes().length != 1
|| methodName.equals("setClass")
|| m.getReturnType() != void.class) {
continue;
}
String propertyName = decapitalizePropertyName(methodName
.substring(3));
setupWriteMethod(m, propertyName);
}
}
for (Iterator i = invalidPropertyNames.iterator(); i.hasNext();) {
propertyDescCache.remove(i.next());
}
invalidPropertyNames.clear();
}
private static String decapitalizePropertyName(String name) {
if (StringUtil.isEmpty(name)) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1))
&& Character.isUpperCase(name.charAt(0))) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
private void addPropertyDesc(PropertyDesc propertyDesc)
throws EmptyRuntimeException {
if (propertyDesc == null) {
throw new EmptyRuntimeException("propertyDesc");
}
propertyDescCache.put(propertyDesc.getPropertyName(), propertyDesc);
}
private void setupReadMethod(Method readMethod, String propertyName) {
Class> propertyType = readMethod.getReturnType();
PropertyDesc propDesc = getPropertyDesc0(propertyName);
if (propDesc != null) {
if (!propDesc.getPropertyType().equals(propertyType)) {
invalidPropertyNames.add(propertyName);
} else {
propDesc.setReadMethod(readMethod);
}
} else {
propDesc = new PropertyDescImpl(propertyName, propertyType,
readMethod, null, null, this);
addPropertyDesc(propDesc);
}
}
private void setupWriteMethod(Method writeMethod, String propertyName) {
Class> propertyType = writeMethod.getParameterTypes()[0];
PropertyDesc propDesc = getPropertyDesc0(propertyName);
if (propDesc != null) {
if (!propDesc.getPropertyType().equals(propertyType)) {
invalidPropertyNames.add(propertyName);
} else {
propDesc.setWriteMethod(writeMethod);
}
} else {
propDesc = new PropertyDescImpl(propertyName, propertyType, null,
writeMethod, null, this);
addPropertyDesc(propDesc);
}
}
private Method getSuitableMethod(String methodName, Object[] args)
throws MethodNotFoundRuntimeException {
if (args == null) {
args = EMPTY_ARGS;
}
Method[] methods = getMethods(methodName);
Method method = findSuitableMethod(methods, args);
if (method != null) {
return method;
}
method = findSuitableMethodAdjustNumber(methods, args);
if (method != null) {
return method;
}
throw new MethodNotFoundRuntimeException(beanClass, methodName, args);
}
private Method findSuitableMethod(Method[] methods, Object[] args) {
outerLoop: for (int i = 0; i < methods.length; ++i) {
Class>[] paramTypes = methods[i].getParameterTypes();
if (paramTypes.length != args.length) {
continue;
}
for (int j = 0; j < args.length; ++j) {
if (args[j] == null
|| ClassUtil.isAssignableFrom(paramTypes[j], args[j]
.getClass())) {
continue;
}
continue outerLoop;
}
return methods[i];
}
return null;
}
private Method findSuitableMethodAdjustNumber(Method[] methods,
Object[] args) {
outerLoop: for (int i = 0; i < methods.length; ++i) {
Class>[] paramTypes = methods[i].getParameterTypes();
if (paramTypes.length != args.length) {
continue;
}
for (int j = 0; j < args.length; ++j) {
if (args[j] == null
|| ClassUtil.isAssignableFrom(paramTypes[j], args[j]
.getClass())
|| adjustNumber(paramTypes, args, j)) {
continue;
}
continue outerLoop;
}
return methods[i];
}
return null;
}
private void setupMethods() {
ArrayMap> methodListMap = new ArrayMap<>();
Method[] methods = beanClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (MethodUtil.isBridgeMethod(method)
|| MethodUtil.isSyntheticMethod(method)) {
continue;
}
String methodName = method.getName();
List list = methodListMap.get(methodName);
if (list == null) {
list = new ArrayList<>();
methodListMap.put(methodName, list);
}
list.add(method);
}
for (int i = 0; i < methodListMap.size(); ++i) {
List methodList = methodListMap.get(i);
methodsCache.put(methodListMap.getKey(i), methodList
.toArray(new Method[methodList.size()]));
}
}
/*
* private void setupField() { for (Class clazz = beanClass_; clazz !=
* Object.class && clazz != null; clazz = clazz.getSuperclass()) {
*
* Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i <
* fields.length; ++i) { Field field = fields[i]; String fname =
* field.getName(); if (!fieldCache_.containsKey(fname)) {
* fieldCache_.put(fname, field); } } } }
*/
private void setupFields() {
setupFields(beanClass);
}
private void setupFields(Class> targetClass) {
if (targetClass.isInterface()) {
setupFieldsByInterface(targetClass);
} else {
setupFieldsByClass(targetClass);
}
}
private void setupFieldsByInterface(Class> interfaceClass) {
addFields(interfaceClass);
Class>[] interfaces = interfaceClass.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) {
setupFieldsByInterface(interfaces[i]);
}
}
private void addFields(Class> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; ++i) {
Field field = fields[i];
String fname = field.getName();
if (!fieldCache.containsKey(fname)) {
field.setAccessible(true);
fieldCache.put(fname, field);
if (FieldUtil.isInstanceField(field)) {
if (hasPropertyDesc(fname)) {
PropertyDesc pd = getPropertyDesc(field.getName());
pd.setField(field);
} else if (FieldUtil.isPublicField(field)) {
PropertyDesc pd = new PropertyDescImpl(field.getName(),
field.getType(), null, null, field, this);
propertyDescCache.put(fname, pd);
}
}
}
}
}
private void setupFieldsByClass(Class> targetClass) {
addFields(targetClass);
Class>[] interfaces = targetClass.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) {
setupFieldsByInterface(interfaces[i]);
}
Class> superClass = targetClass.getSuperclass();
if (superClass != Object.class && superClass != null) {
setupFieldsByClass(superClass);
}
}
/*
* private void setupField() { Field[] fields = beanClass_.getFields(); for
* (int i = 0; i < fields.length; i++) { if
* (Modifier.isStatic(fields[i].getModifiers())) {
* fieldCache_.put(fields[i].getName(), fields[i]); } } }
*/
Map, Type> getTypeVariables() {
return typeVariables;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy