/*
* Copyright 2004-2012 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.util.beans.impl;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.seasar.util.beans.BeanDesc;
import org.seasar.util.beans.ConstructorDesc;
import org.seasar.util.beans.FieldDesc;
import org.seasar.util.beans.MethodDesc;
import org.seasar.util.beans.ParameterizedClassDesc;
import org.seasar.util.beans.PropertyDesc;
import org.seasar.util.collection.ArrayMap;
import org.seasar.util.collection.CaseInsensitiveMap;
import org.seasar.util.convert.ByteConversionUtil;
import org.seasar.util.convert.DoubleConversionUtil;
import org.seasar.util.convert.FloatConversionUtil;
import org.seasar.util.convert.IntegerConversionUtil;
import org.seasar.util.convert.LongConversionUtil;
import org.seasar.util.convert.ShortConversionUtil;
import org.seasar.util.exception.ConstructorNotFoundRuntimeException;
import org.seasar.util.exception.FieldNotFoundRuntimeException;
import org.seasar.util.exception.MethodNotFoundRuntimeException;
import org.seasar.util.exception.PropertyNotFoundRuntimeException;
import org.seasar.util.lang.ClassUtil;
import org.seasar.util.lang.FieldUtil;
import org.seasar.util.lang.StringUtil;
import static java.util.Collections.*;
import static org.seasar.util.collection.CollectionsUtil.*;
import static org.seasar.util.lang.GenericsUtil.*;
import static org.seasar.util.misc.AssertionUtil.*;
/**
* {@link BeanDesc}の実装クラスです。
*
* @author higa
*/
public class BeanDescImpl implements BeanDesc {
/** 空のオブジェクト配列 */
protected static final Object[] EMPTY_ARGS = new Object[0];
/** 空のクラス配列 */
protected static final Class<?>[] EMPTY_PARAM_TYPES = new Class<?>[0];
/** Beanのクラス */
protected final Class<?> beanClass;
/** 型引数と型変数のマップ */
protected final Map<TypeVariable<?>, Type> typeVariables;
/** プロパティ名から{@link PropertyDesc}へのマップ */
protected final CaseInsensitiveMap<PropertyDesc> propertyDescCache =
new CaseInsensitiveMap<PropertyDesc>();
/** フィールド名から{@link FieldDescImpl}へのマップ */
protected final ArrayMap<String, FieldDesc> fieldDescCache =
new ArrayMap<String, FieldDesc>();
/** {@link ConstructorDesc}の配列 */
protected final List<ConstructorDesc> constructorDescs = newArrayList();
/** メソッド名から{@link MethodDesc}配列へのマップ */
protected final Map<String, MethodDesc[]> methodDescsCache = newHashMap();
/** 不正なプロパティ名のセット */
protected final Set<String> invalidPropertyNames = newHashSet();
/**
* {@link BeanDescImpl}を作成します。
*
* @param beanClass
* ビーンのクラス。{@literal null}であってはいけません
*/
public BeanDescImpl(final Class<?> beanClass) {
assertArgumentNotNull("beanClass", beanClass);
this.beanClass = beanClass;
typeVariables = getTypeVariableMap(beanClass);
setupConstructorDescs();
setupPropertyDescs();
setupMethodDescs();
setupFieldDescs();
}
@SuppressWarnings("unchecked")
@Override
public <T> Class<T> getBeanClass() {
return (Class<T>) beanClass;
}
@Override
public Map<TypeVariable<?>, Type> getTypeVariables() {
return typeVariables;
}
@Override
public boolean hasPropertyDesc(final String propertyName) {
assertArgumentNotEmpty("propertyName", propertyName);
return propertyDescCache.get(propertyName) != null;
}
@Override
public PropertyDesc getPropertyDesc(final String propertyName)
throws PropertyNotFoundRuntimeException {
assertArgumentNotEmpty("propertyName", propertyName);
final PropertyDesc pd = propertyDescCache.get(propertyName);
if (pd == null) {
throw new PropertyNotFoundRuntimeException(beanClass, propertyName);
}
return pd;
}
@Override
public PropertyDesc getPropertyDesc(final int index) {
assertArgumentArrayIndex("index", index, getPropertyDescSize());
return propertyDescCache.getAt(index);
}
@Override
public int getPropertyDescSize() {
return propertyDescCache.size();
}
@Override
public Iterable<PropertyDesc> getPropertyDescs() {
return unmodifiableCollection(propertyDescCache.values());
}
@Override
public boolean hasFieldDesc(final String fieldName) {
assertArgumentNotEmpty("fieldName", fieldName);
return fieldDescCache.containsKey(fieldName);
}
@Override
public FieldDesc getFieldDesc(final String fieldName) {
assertArgumentNotEmpty("fieldName", fieldName);
final FieldDesc fieldDesc = fieldDescCache.get(fieldName);
if (fieldDesc == null) {
throw new FieldNotFoundRuntimeException(beanClass, fieldName);
}
return fieldDesc;
}
@Override
public FieldDesc getFieldDesc(final int index) {
assertArgumentArrayIndex("index", index, getFieldDescSize());
return fieldDescCache.getAt(index);
}
@Override
public int getFieldDescSize() {
return fieldDescCache.size();
}
@Override
public Iterable<FieldDesc> getFieldDescs() {
return unmodifiableCollection(fieldDescCache.values());
}
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(final Object... args) {
final ConstructorDesc constructorDesc =
getSuitableConstructorDesc(args);
return (T) constructorDesc.newInstance(args);
}
@Override
public ConstructorDesc getConstructorDesc(final Class<?>... paramTypes) {
for (final ConstructorDesc constructorDesc : constructorDescs) {
if (Arrays.equals(paramTypes, constructorDesc.getParameterTypes())) {
return constructorDesc;
}
}
throw new ConstructorNotFoundRuntimeException(beanClass, paramTypes);
}
@Override
public ConstructorDesc getSuitableConstructorDesc(final Object... args) {
ConstructorDesc constructorDesc = findSuitableConstructorDesc(args);
if (constructorDesc != null) {
return constructorDesc;
}
constructorDesc = findSuitableConstructorDescAdjustNumber(args);
if (constructorDesc != null) {
return constructorDesc;
}
throw new ConstructorNotFoundRuntimeException(beanClass, args);
}
@Override
public ConstructorDesc getConstructorDesc(int index) {
return constructorDescs.get(index);
}
@Override
public int getConstructorDescSize() {
return constructorDescs.size();
}
@Override
public Iterable<ConstructorDesc> getConstructorDescs() {
return unmodifiableCollection(constructorDescs);
}
@Override
public MethodDesc getMethodDesc(final String methodName,
final Class<?>... paramTypes) {
assertArgumentNotEmpty("methodName", methodName);
final MethodDesc methodDesc =
getMethodDescNoException(methodName, paramTypes);
if (methodDesc != null) {
return methodDesc;
}
throw new MethodNotFoundRuntimeException(
beanClass,
methodName,
paramTypes);
}
@Override
public MethodDesc getMethodDescNoException(final String methodName,
final Class<?>... paramTypes) {
assertArgumentNotEmpty("methodName", methodName);
final MethodDesc[] methodDescs = methodDescsCache.get(methodName);
if (methodDescs == null) {
return null;
}
for (final MethodDesc methodDesc : methodDescs) {
if (Arrays.equals(paramTypes, methodDesc.getParameterTypes())) {
return methodDesc;
}
}
return null;
}
@Override
public MethodDesc getSuitableMethodDesc(final String methodName,
final Object... args) {
assertArgumentNotEmpty("methodName", methodName);
final MethodDesc[] methodDescs = getMethodDescs(methodName);
MethodDesc methodDesc = findSuitableMethod(methodDescs, args);
if (methodDesc != null) {
return methodDesc;
}
methodDesc = findSuitableMethodDescAdjustNumber(methodDescs, args);
if (methodDesc != null) {
return methodDesc;
}
throw new MethodNotFoundRuntimeException(beanClass, methodName, args);
}
@Override
public MethodDesc[] getMethodDescs(final String methodName) {
assertArgumentNotEmpty("methodName", methodName);
final MethodDesc[] methodDescs = methodDescsCache.get(methodName);
if (methodDescs == null) {
throw new MethodNotFoundRuntimeException(
beanClass,
methodName,
null);
}
return methodDescs;
}
@Override
public boolean hasMethodDesc(final String methodName) {
assertArgumentNotEmpty("methodName", methodName);
return methodDescsCache.containsKey(methodName);
}
@Override
public String[] getMethodNames() {
return methodDescsCache.keySet().toArray(
new String[methodDescsCache.size()]);
}
/**
* {@link PropertyDesc}を返します。
*
* @param propertyName
* プロパティ名
* @return {@link PropertyDesc}。プロパティが存在しない場合は{@literal null}
*/
protected PropertyDesc getPropertyDescNoException(final String propertyName) {
return propertyDescCache.get(propertyName);
}
/**
* 引数に適合する{@link ConstructorDesc}を返します。
*
* @param args
* コンストラクタ引数の並び
* @return 引数に適合する{@link ConstructorDesc}。存在しない場合は{@literal null}
*/
protected ConstructorDesc findSuitableConstructorDesc(final Object... args) {
for (final ConstructorDesc constructorDesc : constructorDescs) {
if (isSuitable(constructorDesc.getParameterTypes(), args, false)) {
return constructorDesc;
}
}
return null;
}
/**
* 引数に適合する{@link ConstructorDesc}を返します。
* <p>
* 引数の型が数値の場合、引数を数値に変換することが出来れば適合するとみなします。
* </p>
*
* @param args
* コンストラクタ引数の並び
* @return 引数に適合する{@link ConstructorDesc}。存在しない場合は{@literal null}
*/
protected ConstructorDesc findSuitableConstructorDescAdjustNumber(
final Object... args) {
for (final ConstructorDesc constructorDesc : constructorDescs) {
if (isSuitable(constructorDesc.getParameterTypes(), args, true)) {
return constructorDesc;
}
}
return null;
}
/**
* 引数に適合する{@link MethodDesc}を返します。
*
* @param methodDescs
* メソッドの配列
* @param args
* メソッド引数の並び
* @return 引数に適合する{@link MethodDesc}。存在しない場合は{@literal null}
*/
protected MethodDesc findSuitableMethod(final MethodDesc[] methodDescs,
final Object[] args) {
for (final MethodDesc methodDesc : methodDescs) {
if (isSuitable(methodDesc.getParameterTypes(), args, false)) {
return methodDesc;
}
}
return null;
}
/**
* 引数に適合する{@link MethodDesc}を返します。
* <p>
* 引数の型が数値の場合、引数を数値に変換することが出来れば適合するとみなします。
* </p>
*
* @param methodDescs
* メソッドの配列
* @param args
* メソッド引数の並び
* @return 引数に適合する{@link MethodDesc}。存在しない場合は{@literal null}
*/
protected MethodDesc findSuitableMethodDescAdjustNumber(
final MethodDesc[] methodDescs, final Object[] args) {
for (final MethodDesc methodDesc : methodDescs) {
if (isSuitable(methodDesc.getParameterTypes(), args, true)) {
return methodDesc;
}
}
return null;
}
/**
* 引数が引数型に適合するかチェックします。
*
* @param paramTypes
* 引数型の並び
* @param args
* 引数の並び
* @param adjustNumber
* 引数型が数値型の場合に引数を適合するように変換する場合は{@literal true}
* @return 引数が引数型に適合する場合は{@literal true}
*/
protected boolean isSuitable(final Class<?>[] paramTypes,
final Object[] args, final boolean adjustNumber) {
if (args == null) {
return paramTypes.length == 0;
}
if (paramTypes.length != args.length) {
return false;
}
for (int i = 0; i < args.length; ++i) {
if (args[i] == null) {
continue;
}
if (ClassUtil.isAssignableFrom(paramTypes[i], args[i].getClass())) {
continue;
}
if (adjustNumber && adjustNumber(paramTypes, args, i)) {
continue;
}
return false;
}
return true;
}
/**
* 指定された位置の引数型が数値の場合、引数を適合するように変換します。
*
* @param paramTypes
* 引数型の並び
* @param args
* 引数の並び
* @param index
* 操作対象となる引数のインデックス
* @return 引数を適合するように変換した場合は{@literal true}
*/
protected static boolean adjustNumber(final Class<?>[] paramTypes,
final Object[] args, final int index) {
if (paramTypes[index].isPrimitive()) {
if (paramTypes[index] == byte.class) {
args[index] = ByteConversionUtil.toByte(args[index]);
return true;
} else if (paramTypes[index] == short.class) {
args[index] = ShortConversionUtil.toShort(args[index]);
return true;
} else if (paramTypes[index] == int.class) {
args[index] = IntegerConversionUtil.toInteger(args[index]);
return true;
} else if (paramTypes[index] == long.class) {
args[index] = LongConversionUtil.toLong(args[index]);
return true;
} else if (paramTypes[index] == float.class) {
args[index] = FloatConversionUtil.toFloat(args[index]);
return true;
} else if (paramTypes[index] == double.class) {
args[index] = DoubleConversionUtil.toDouble(args[index]);
return true;
}
} else {
if (paramTypes[index] == Byte.class) {
args[index] = ByteConversionUtil.toByte(args[index]);
return true;
} else if (paramTypes[index] == Short.class) {
args[index] = ShortConversionUtil.toShort(args[index]);
return true;
} else if (paramTypes[index] == Integer.class) {
args[index] = IntegerConversionUtil.toInteger(args[index]);
return true;
} else if (paramTypes[index] == Long.class) {
args[index] = LongConversionUtil.toLong(args[index]);
return true;
} else if (paramTypes[index] == Float.class) {
args[index] = FloatConversionUtil.toFloat(args[index]);
return true;
} else if (paramTypes[index] == Double.class) {
args[index] = DoubleConversionUtil.toDouble(args[index]);
return true;
}
}
return false;
}
/**
* {@link PropertyDesc}を準備します。
*/
protected void setupPropertyDescs() {
for (final Method m : beanClass.getMethods()) {
if (m.isBridge() || m.isSynthetic()) {
continue;
}
final String methodName = m.getName();
if (methodName.startsWith("get")) {
if (m.getParameterTypes().length != 0
|| methodName.equals("getClass")
|| m.getReturnType() == void.class) {
continue;
}
final String propertyName =
StringUtil.decapitalize(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;
}
final String propertyName =
StringUtil.decapitalize(methodName.substring(2));
setupReadMethod(m, propertyName);
} else if (methodName.startsWith("set")) {
if (m.getParameterTypes().length != 1
|| methodName.equals("setClass")
|| m.getReturnType() != void.class) {
continue;
}
final String propertyName =
StringUtil.decapitalize(methodName.substring(3));
setupWriteMethod(m, propertyName);
}
}
for (final String name : invalidPropertyNames) {
propertyDescCache.remove(name);
}
invalidPropertyNames.clear();
}
/**
* getterメソッドを準備します。
*
* @param readMethod
* getterメソッド
* @param propertyName
* プロパティ名
*/
protected void setupReadMethod(final Method readMethod,
final String propertyName) {
final Class<?> propertyType = readMethod.getReturnType();
PropertyDescImpl propDesc =
(PropertyDescImpl) propertyDescCache.get(propertyName);
if (propDesc == null) {
propDesc =
new PropertyDescImpl(
propertyName,
propertyType,
readMethod,
null,
null,
this);
addPropertyDesc(propDesc);
} else if (propDesc.getPropertyType() != propertyType) {
invalidPropertyNames.add(propertyName);
} else {
propDesc.setReadMethod(readMethod);
}
}
/**
* setterメソッドを準備します。
*
* @param writeMethod
* setterメソッド
* @param propertyName
* プロパティ名
*/
protected void setupWriteMethod(final Method writeMethod,
final String propertyName) {
final Class<?> propertyType = writeMethod.getParameterTypes()[0];
PropertyDescImpl propDesc =
(PropertyDescImpl) propertyDescCache.get(propertyName);
if (propDesc == null) {
propDesc =
new PropertyDescImpl(
propertyName,
propertyType,
null,
writeMethod,
null,
this);
addPropertyDesc(propDesc);
} else if (propDesc.getPropertyType() != propertyType) {
invalidPropertyNames.add(propertyName);
} else {
propDesc.setWriteMethod(writeMethod);
}
}
/**
* {@link PropertyDesc}を追加します.
*
* @param propertyDesc
* {@link PropertyDesc}
*/
protected void addPropertyDesc(final PropertyDescImpl propertyDesc) {
assertArgumentNotNull("propertyDesc", propertyDesc);
propertyDescCache.put(propertyDesc.getPropertyName(), propertyDesc);
}
/**
* コンストラクタを準備します。
*/
protected void setupConstructorDescs() {
for (final Constructor<?> constructor : beanClass.getConstructors()) {
constructorDescs.add(new ConstructorDescImpl(this, constructor));
}
}
/**
* メソッドを準備します。
*/
protected void setupMethodDescs() {
final ArrayMap<String, List<MethodDesc>> methodDescListMap =
new ArrayMap<String, List<MethodDesc>>();
for (final Method method : beanClass.getMethods()) {
if (method.isBridge() || method.isSynthetic()) {
continue;
}
final String methodName = method.getName();
List<MethodDesc> list = methodDescListMap.get(methodName);
if (list == null) {
list = newArrayList();
methodDescListMap.put(methodName, list);
}
list.add(new MethodDescImpl(this, method));
}
for (int i = 0; i < methodDescListMap.size(); ++i) {
final List<MethodDesc> methodDescList = methodDescListMap.getAt(i);
methodDescsCache.put(
methodDescListMap.getKeyAt(i),
methodDescList.toArray(new MethodDesc[methodDescList.size()]));
}
}
/**
* フィールドを準備します。
*/
protected void setupFieldDescs() {
if (beanClass.isInterface()) {
setupFieldDescsByInterface(beanClass);
} else {
setupFieldDescsByClass(beanClass);
}
}
/**
* インターフェースに定義されたフィールドを準備します。
*
* @param interfaceClass
* 対象のインターフェース
*/
protected void setupFieldDescsByInterface(final Class<?> interfaceClass) {
addFieldDescs(interfaceClass);
final Class<?>[] interfaces = interfaceClass.getInterfaces();
for (final Class<?> intf : interfaces) {
setupFieldDescsByInterface(intf);
}
}
/**
* クラスに定義されたフィールドを準備します。
*
* @param targetClass
* 対象のクラス
*/
private void setupFieldDescsByClass(final Class<?> targetClass) {
addFieldDescs(targetClass);
for (final Class<?> intf : targetClass.getInterfaces()) {
setupFieldDescsByInterface(intf);
}
final Class<?> superClass = targetClass.getSuperclass();
if (superClass != Object.class && superClass != null) {
setupFieldDescsByClass(superClass);
}
}
/**
* クラスまたはインターフェースに定義されたフィールドを追加します。
*
* @param clazz
* 対象のクラスまたはインターフェース
*/
protected void addFieldDescs(final Class<?> clazz) {
for (final Field field : clazz.getDeclaredFields()) {
final String fname = field.getName();
if (fieldDescCache.containsKey(fname)) {
continue;
}
field.setAccessible(true);
final FieldDescImpl fieldDesc = new FieldDescImpl(this, field);
fieldDescCache.put(fname, fieldDesc);
if (!FieldUtil.isInstanceField(field)) {
continue;
}
if (hasPropertyDesc(fname)) {
final PropertyDescImpl pd =
(PropertyDescImpl) propertyDescCache.get(field.getName());
pd.setField(field);
continue;
}
if (FieldUtil.isPublicField(field)) {
final PropertyDescImpl pd =
new PropertyDescImpl(
field.getName(),
field.getType(),
null,
null,
field,
this);
propertyDescCache.put(fname, pd);
}
}
}
/**
* {@link Type}を表現する{@link ParameterizedClassDesc}を作成して返します。
*
* @param type
* 型
* @param map
* パラメータ化された型が持つ型変数をキー、型引数を値とする{@link Map}
* @return 型を表現する{@link ParameterizedClassDesc}
*/
protected static ParameterizedClassDesc createParameterizedClassDesc(
final Type type, final Map<TypeVariable<?>, Type> map) {
final Class<?> rowClass = getActualClass(type, map);
if (rowClass == null) {
return null;
}
final ParameterizedClassDescImpl desc =
new ParameterizedClassDescImpl(rowClass);
final Type[] parameterTypes = getGenericParameters(type);
if (parameterTypes == null) {
return desc;
}
final ParameterizedClassDesc[] parameterDescs =
new ParameterizedClassDesc[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; ++i) {
parameterDescs[i] =
createParameterizedClassDesc(parameterTypes[i], map);
}
desc.setArguments(parameterDescs);
return desc;
}
}