/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.beans;

import java.util.*;
import java.beans.*;
import java.lang.reflect.*;
import java.math.*;
import java.io.*;

import jp.ossc.nimbus.beans.dataset.*;

/**
 * 単純プロパティ。<p>
 * 任意のBeanの、ある単純なプロパティ名のプロパティにアクセスするための{@link Property}。<br>
 * <p>
 * 以下のような単純プロパティにアクセスするタイプセーフなコードがある。<br>
 * <pre>
 *   Hoge propValue = obj.getHoge();
 *   obj.setHoge(propValue);
 * </pre>
 * 単純プロパティを使う事で、このコードを<br>
 * <pre>
 *   SimpleProperty prop = new SimpleProperty();
 *   prop.parse("hoge");
 *   Object propValue = prop.getProperty(obj);
 *   prop.setProperty(obj, propValue);
 * </pre>
 * というコードに置き換える事ができる。<br>
 * このコードは、冗長になっているが、対象となるBeanの型やメソッドをタイプセーフに書かない動的なコードになっている。<br>
 * <p>
 * この単純プロパティでは、以下のようなBeanのプロパティに対するアクセス方法が用意されている。<br>
 * <table border="1">
 *   <tr bgcolor="#CCCCFF"><th rowspan="3">アクセス方法</th><th>Java表現</th><th rowspan="3">プロパティ文字列表現</th></tr>
 *   <tr bgcolor="#CCCCFF"><th>プロパティ取得</th></tr>
 *   <tr bgcolor="#CCCCFF"><th>プロパティ設定</th></tr>
 *   <tr><td rowspan="2">Java Beansプロパティ</td><td>bean.getHoge()</td><td rowspan="2">hoge</td></tr>
 *   <tr><td>bean.setHoge(value)</td></tr>
 *   <tr><td rowspan="2">プロパティメソッド</td><td>bean.length()</td><td rowspan="2">length</td></tr>
 *   <tr><td>bean.length(value)</td></tr>
 *   <tr><td rowspan="2">java.util.Mapプロパティ</td><td>((java.util.Map)bean).get("hoge")</td><td rowspan="2">hoge</td></tr>
 *   <tr><td>((java.util.Map)bean).put("hoge", value)</td></tr>
 * </table>
 *
 * @author M.Takata
 */
public class SimpleProperty implements Property, Serializable, Comparable<Property>{
    
    private static final long serialVersionUID = 5346194284290420718L;
    
    // エラーメッセージ定義
    private static final String MSG_00001
        = "Length of property literal must be more than 1.";
    protected static final String GET_METHOD_PREFIX = "get";
    protected static final String SET_METHOD_PREFIX = "set";
    protected static final String MAP_GET_METHOD_NAME = "get";
    protected static final Class<?>[] MAP_GET_METHOD_ARGS
         = new Class[]{Object.class};
    protected static final String MAP_SET_METHOD_NAME = "put";
    protected static final Class<?>[] MAP_SET_METHOD_ARGS
         = new Class[]{Object.class, Object.class};
    protected static final String ARRAY_LENGTH_METHOD_NAME = "length";
    
    /**
     * プロパティ名。<p>
     */
    protected String property;
    
    /**
     * GETメソッドオブジェクトのキャッシュ。<p>
     */
    protected transient Map<Class<?>, Method> getMethodCache = new HashMap<Class<?>, Method>();
    
    /**
     * SETメソッドオブジェクトのキャッシュ。<p>
     */
    protected transient Map<Class<?>, Object> setMethodCache = new HashMap<Class<?>, Object>();
    
    /**
     * null参照のプロパティを取得使用とした場合に、例外をthrowするかどうかのフラグ。<p>
     * trueの場合は、例外をthrowしない。デフォルトは、false。<br>
     */
    protected boolean isIgnoreNullProperty;
    
    /**
     * プロパティ名を持たないインスタンスを生成する。<p>
     * {@link #setPropertyName(String)}で、有効なプロパティ名を指定しなければ、無効なインスタンスです。<br>
     */
    public SimpleProperty(){
    }
    
    /**
     * 指定されたプロパティ名のプロパティにアクセスするインスタンスを生成する。<p>
     * @param prop プロパティ名
     * @exception IllegalArgumentException 指定されたプロパティ名がnullまたは、長さ０の場合
     */
    public SimpleProperty(String prop) throws IllegalArgumentException{
        setPropertyName(prop);
    }
    
    /**
     * 指定したプロパティ文字列を解析する。<p>
     * ここで指定可能な文字列は、<br>
     * &nbsp;プロパティ名<br>
     * である。<br>
     * 但し、プロパティ名は省略可。<br>
     *
     * @param prop プロパティ文字列
     * @exception IllegalArgumentException 指定されたプロパティ文字列をこのプロパティオブジェクトが解析できない場合
     */
    @Override
    public void parse(String prop) throws IllegalArgumentException{
        setPropertyName(prop);
    }
    
    // PropertyインタフェースのJavaDoc
    @Override
    public String getPropertyName(){
        return property;
    }
    
    /**
     * プロパティ名を設定する。<p>
     *
     * @param prop プロパティ名
     * @exception IllegalArgumentException 指定されたプロパティ名がnullまたは、長さ０の場合
     */
    protected void setPropertyName(String prop)
     throws IllegalArgumentException{
        if(prop == null || prop.length() == 0){
            throw new IllegalArgumentException(MSG_00001);
        }
        property = prop;
    }
    
    @Override
    public Class<?> getPropertyType(Object obj) throws NoSuchPropertyException{
        if(obj instanceof Record){
            final Record record = (Record)obj;
            final RecordSchema recSchema = record.getRecordSchema();
            if(recSchema != null){
                final PropertySchema propSchema = recSchema.getPropertySchema(property);
                if(propSchema != null){
                    final Class<?> type = propSchema.getType();
                    if(type != null){
                        return type;
                    }
                }
            }
        }
        return getPropertyType(obj.getClass());
    }
    
    public Class<?> getPropertyType(Class<?> clazz)
     throws NoSuchPropertyException{
        return (Class<?>)getPropertyType(clazz, false);
    }
    
    @Override
    public Type getPropertyGenericType(Object obj)
     throws NoSuchPropertyException{
        if(obj instanceof Record){
            final Record record = (Record)obj;
            final RecordSchema recSchema = record.getRecordSchema();
            if(recSchema != null){
                final PropertySchema propSchema = recSchema.getPropertySchema(property);
                if(propSchema != null){
                    final Class<?> type = propSchema.getType();
                    if(type != null){
                        return type;
                    }
                }
            }
        }
        return getPropertyGenericType(obj.getClass());
    }
    
    public Type getPropertyGenericType(Class<?> clazz)
     throws NoSuchPropertyException{
        return getPropertyType(clazz, true);
    }
    
    protected Type getPropertyType(Class<?> clazz, boolean isGeneric)
     throws NoSuchPropertyException{
        if(property == null){
            throw new NoSuchPropertyException(clazz, property);
        }
        try{
            Method readMethod = getReadMethod(clazz);
            return isGeneric ? readMethod.getGenericReturnType() : readMethod.getReturnType();
        }catch(InvocationTargetException e){
            throw new NoSuchPropertyException(clazz, property, e);
        }catch(NoSuchPropertyException e){
            try{
                Method writeMethod = getWriteMethod(clazz);
                if(setMethodCache.containsKey(clazz)){
                    final Object methodObj = setMethodCache.get(clazz);
                    if(!(methodObj instanceof Method)){
                        return null;
                    }
                }
                return isGeneric ? writeMethod.getGenericParameterTypes()[0] : writeMethod.getParameterTypes()[0];
            }catch(InvocationTargetException e2){
                throw e;
            }catch(NoSuchPropertyException e2){
                throw e2;
            }
        }
    }
    
    private String createGetterName(){
        StringBuilder result = new StringBuilder(property);
        final int len = result.length();
        if(len != 0 && !Character.isUpperCase(result.charAt(0))){
            char capital = Character.toUpperCase(result.charAt(0));
            result.deleteCharAt(0).insert(0, capital);
        }
        return result.insert(0, GET_METHOD_PREFIX).toString();
    }
    
    private String createSetterName(){
        StringBuilder result = new StringBuilder(property);
        final int len = result.length();
        if(len != 0 && !Character.isUpperCase(result.charAt(0))){
            char capital = Character.toUpperCase(result.charAt(0));
            result.deleteCharAt(0).insert(0, capital);
        }
        return result.insert(0, SET_METHOD_PREFIX).toString();
    }
    
    @Override
    public boolean isReadable(Object obj){
        if(obj instanceof Record){
            Record record = (Record)obj;
            final RecordSchema recSchema = record.getRecordSchema();
            if(recSchema != null){
                final PropertySchema propSchema = recSchema.getPropertySchema(property);
                if(propSchema != null){
                    return true;
                }
                try{
                    getReadMethod(record.getClass());
                    return true;
                }catch(NoSuchPropertyException e){
                    return false;
                }catch(InvocationTargetException e){
                    return false;
                }
            }
        }
        return isReadable(obj.getClass());
    }
    
    public boolean isReadable(Class<?> clazz){
        if(property == null){
            return false;
        }
        if(Map.class.isAssignableFrom(clazz)){
            return true;
        }else{
            try{
                getReadMethod(clazz);
                return true;
            }catch(NoSuchPropertyException e){
                return false;
            }catch(InvocationTargetException e){
                return false;
            }
        }
    }
    
    public boolean isWritable(Object obj, Object value){
        if(value == null){
            return isWritable(obj.getClass());
        }else{
            return isWritable(obj, value.getClass());
        }
    }
    
    @Override
    public boolean isWritable(Object obj, Class<?> clazz){
        if(obj instanceof Record){
            Record record = (Record)obj;
            final RecordSchema recSchema = record.getRecordSchema();
            if(recSchema != null){
                final PropertySchema propSchema = recSchema.getPropertySchema(property);
                if(propSchema != null){
                    return propSchema.getType() == null || propSchema.getType().isAssignableFrom(clazz);
                }
                try{
                    getWriteMethod(record.getClass());
                    return true;
                }catch(NoSuchPropertyException e){
                    return false;
                }catch(InvocationTargetException e){
                    return false;
                }
            }
        }
        return isWritable(obj.getClass());
    }
    
    public boolean isWritable(Class<?> clazz){
        if(property == null){
            return false;
        }
        if(Map.class.isAssignableFrom(clazz)){
            return true;
        }else{
            try{
                getWriteMethod(clazz);
                return true;
            }catch(NoSuchPropertyException e){
                return false;
            }catch(InvocationTargetException e){
                return false;
            }
        }
    }
    
    /**
     * 指定したオブジェクトから、このプロパティが持つプロパティ名のアクセサを呼び出してプロパティ値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @return プロパティ値
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object getProperty(Object obj)
     throws NoSuchPropertyException, InvocationTargetException{
        if(property == null){
            throw new NoSuchPropertyException(obj.getClass(), property);
        }
        if(obj == null && isIgnoreNullProperty){
            return null;
        }
        try{
            final Class<?> clazz = obj.getClass();
            if(clazz.isArray() && ARRAY_LENGTH_METHOD_NAME.equals(property)){
                return new Integer(Array.getLength(obj));
            }
            Method readMethod = getReadMethod(clazz);
            try{
                if(readMethod.getParameterTypes().length == 0){
                    return readMethod.invoke(obj);
                }else{
                    return readMethod.invoke(obj, property);
                }
            }catch(IllegalAccessException e){
                // 起こらないはず
                throw new NoSuchPropertyException(
                    clazz,
                    property,
                    e
                );
            }catch(IllegalArgumentException e){
                // 起こらないはず
                throw new NoSuchPropertyException(
                    clazz,
                    property,
                    e
                );
            }
        }catch(NoSuchPropertyException e){
            if(obj instanceof Map){
                return ((Map<String,Object>)obj).get(property);
            }
            throw e;
        }
    }
    
    /**
     * 指定したオブジェクトに、このプロパティが表すプロパティ値を設定する。<p>
     *
     * @param obj 対象となるBean
     * @param value 設定するプロパティ値
     * @exception NoSuchWritablePropertyException 指定されたプロパティのSetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    @Override
    public void setProperty(Object obj, Object value)
     throws NoSuchPropertyException, InvocationTargetException{
        setProperty(obj, value == null ? null : value.getClass(), value);
    }
    
    /**
     * 指定したオブジェクトに、このプロパティが表すプロパティ値を設定する。<p>
     *
     * @param obj 対象となるBean
     * @param type プロパティの型
     * @param value 設定するプロパティ値
     * @exception NoSuchWritablePropertyException 指定されたプロパティのSetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    @Override
    public void setProperty(Object obj, Class<?> type, Object value)
     throws NoSuchPropertyException, InvocationTargetException{
        if(property == null){
            throw new NoSuchPropertyException(obj.getClass(), property);
        }
        final Class<?> clazz = obj.getClass();
        Method writeMethod = null;
        if(type == null && value != null){
            type = value.getClass();
        }
        if(type == null){
            writeMethod = getWriteMethod(clazz);
        }else{
            writeMethod = getWriteMethod(clazz, type);
        }
        try{
            if(writeMethod.getParameterTypes().length == 1){
                final Class<?> paramType = writeMethod.getParameterTypes()[0];
                if(value instanceof Number
                     && !paramType.isPrimitive()
                     && !paramType.equals(value.getClass())
                ){
                    value = castPrimitiveWrapper(paramType, (Number)value);
                }
                writeMethod.invoke(obj, value);
            }else{
                writeMethod.invoke(obj, property, value);
            }
        }catch(IllegalAccessException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property,
                e
            );
        }catch(IllegalArgumentException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property,
                e
            );
        }
    }
    
    /**
     * 指定したオブジェクトから、このプロパティが持つプロパティ名のGetterメソッドを取得する。<p>
     *
     * @param obj 対象となるBean
     * @return このプロパティが持つプロパティ名のGetterメソッド
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    public Method getReadMethod(Object obj)
     throws NoSuchPropertyException, InvocationTargetException{
        return getReadMethod(obj.getClass());
    }
    
    /**
     * 指定したクラスから、このプロパティが持つプロパティ名のGetterメソッドを取得する。<p>
     *
     * @param clazz 対象となるクラス
     * @return このプロパティが持つプロパティ名のGetterメソッド
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    public Method getReadMethod(Class<?> clazz)
     throws NoSuchPropertyException, InvocationTargetException{
        if(property == null){
            throw new NoSuchPropertyException(clazz, property);
        }
        Method readMethod = null;
        if(getMethodCache.containsKey(clazz)){
            readMethod = getMethodCache.get(clazz);
            if(readMethod == null){
                throw new NoSuchReadablePropertyException(
                    clazz,
                    property
                );
            }
        }else{
            if(!isAccessableClass(clazz)){
                final Class<?>[] interfaces = clazz.getInterfaces();
                for(Class<?> ifc : interfaces){
                    try{
                        return getReadMethod(ifc);
                    }catch(NoSuchPropertyException e){
                    }
                }
                final Class<?> superClass = clazz.getSuperclass();
                if(superClass != null){
                    return getReadMethod(superClass);
                }
                throw new NoSuchReadablePropertyException(clazz, property);
            }
            PropertyDescriptor descriptor = null;
            try{
                descriptor = getPropertyDescriptor(clazz);
                readMethod = descriptor.getReadMethod();
                if(readMethod != null
                     && (readMethod.getParameterTypes().length != 0 || Modifier.isNative(readMethod.getModifiers()))){
                    readMethod = null;
                }
            }catch(NoSuchPropertyException e){
            }
            if(readMethod == null){
                try{
                    readMethod = clazz.getMethod(
                        createGetterName()
                    );
                    if(Void.TYPE.equals(readMethod.getReturnType())
                        || Modifier.isNative(readMethod.getModifiers())){
                        readMethod = null;
                    }
                }catch(NoSuchMethodException e){
                }
            }
            if(readMethod == null){
                try{
                    readMethod = clazz.getMethod(
                        property
                    );
                }catch(NoSuchMethodException e){
                    if(property != null
                        && property.length() != 0
                        && Character.isUpperCase(property.charAt(0))){
                        StringBuilder methodName
                             = new StringBuilder(property);
                        char capital = Character.toLowerCase(methodName.charAt(0));
                        methodName.deleteCharAt(0).insert(0, capital);
                        try{
                            readMethod = clazz.getMethod(
                                methodName.toString()
                            );
                        }catch(NoSuchMethodException e2){
                        }
                    }
                }
                if(readMethod != null
                    && (Void.TYPE.equals(readMethod.getReturnType()) || Modifier.isNative(readMethod.getModifiers()))){
                    readMethod = null;
                }
            }
            if(readMethod == null){
                if(Map.class.isAssignableFrom(clazz)){
                    try{
                        readMethod = Map.class.getMethod(
                            MAP_GET_METHOD_NAME,
                            MAP_GET_METHOD_ARGS
                        );
                    }catch(NoSuchMethodException e){
                    }
                }
            }
            if(readMethod == null){
                getMethodCache.put(clazz, null);
                throw new NoSuchReadablePropertyException(
                    clazz,
                    property
                );
            }else{
                getMethodCache.put(clazz, readMethod);
            }
        }
        return readMethod;
    }
    
    /**
     * 指定したオブジェクトから、このプロパティが持つプロパティ名のSetterメソッドを取得する。<p>
     *
     * @param obj 対象となるBean
     * @return このプロパティが持つプロパティ名のSetterメソッド
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    public Method getWriteMethod(Object obj)
     throws NoSuchPropertyException, InvocationTargetException{
        return getWriteMethod(obj.getClass());
    }
    
    /**
     * 指定したオブジェクトから、このプロパティが持つプロパティ名のSetterメソッドを取得する。<p>
     *
     * @param clazz 対象となるBean
     * @return このプロパティが持つプロパティ名のSetterメソッド
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    public Method getWriteMethod(Class<?> clazz)
     throws NoSuchPropertyException, InvocationTargetException{
        return getWriteMethod(clazz, null);
    }
    
    @SuppressWarnings("unchecked")
    public Method getWriteMethod(Class<?> clazz, Class<?> valClazz)
     throws NoSuchPropertyException, InvocationTargetException{
        if(property == null){
            throw new NoSuchWritablePropertyException(clazz, property);
        }
        if(setMethodCache.containsKey(clazz)){
            final Object methodObj = setMethodCache.get(clazz);
            if(methodObj instanceof Method){
                return (Method)methodObj;
            }
            if(valClazz == null){
                throw new NoSuchWritablePropertyException(
                    clazz,
                    property,
                    "The method cannot be specified, because the method of the overload exists."
                );
            }
            final Map<Class<?>, Method> overloadMap = (Map<Class<?>, Method>)methodObj;
            if(overloadMap.containsKey(valClazz)){
                return overloadMap.get(valClazz);
            }
        }
        if(!isAccessableClass(clazz)){
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                try{
                    return getWriteMethod(
                        itf,
                        valClazz
                    );
                }catch(NoSuchPropertyException e){
                }
            }
            final Class<?> superClass = clazz.getSuperclass();
            if(superClass != null){
                return getWriteMethod(
                    superClass,
                    valClazz
                );
            }
            throw new NoSuchWritablePropertyException(clazz, property);
        }
        Method writeMethod = null;
        final String setterName = createSetterName();
        final Class<?> primitiveClazz = toPrimitive(valClazz);
        String lowerCaseProperty = null;
        if(property != null
            && property.length() != 0
            && Character.isUpperCase(property.charAt(0))){
            StringBuilder methodName
                 = new StringBuilder(property);
            char capital = Character.toLowerCase(methodName.charAt(0));
            methodName.deleteCharAt(0).insert(0, capital);
            lowerCaseProperty = methodName.toString();
        }
        final Method[] methods = clazz.getMethods();
        final Map<Class<?>, Method> overloadMap = new HashMap<Class<?>, Method>();
        boolean isMatch = false;
        for(Method method : methods){
            if(Modifier.isNative(method.getModifiers())){
                continue;
            }
            final Class<?>[] paramTypes = method.getParameterTypes();
            if(paramTypes.length != 1){
                continue;
            }
            if(!setterName.equals(method.getName())
                && !property.equals(method.getName())
                && !method.getName().equals(lowerCaseProperty)){
                continue;
            }
            if(overloadMap.containsKey(paramTypes[0])
                && setterName.equals(
                    overloadMap.get(paramTypes[0]).getName())
            ){
                continue;
            }else{
                overloadMap.put(paramTypes[0], method);
            }
            if(isMatch){
                continue;
            }
            if(writeMethod == null){
                if(valClazz == null){
                    writeMethod = method;
                    continue;
                }
                if(!isAssignableFrom(paramTypes[0], valClazz)
                    && !paramTypes[0].equals(primitiveClazz)){
                    continue;
                }
                writeMethod = method;
                if(valClazz.equals(paramTypes[0])
                    || paramTypes[0].equals(primitiveClazz)){
                    isMatch = true;
                }
                continue;
            }
            if(valClazz == null){
                continue;
            }
            if(!isAssignableFrom(paramTypes[0], valClazz)
                && !paramTypes[0].equals(primitiveClazz)){
                continue;
            }
            if(valClazz.equals(paramTypes[0])
                || paramTypes[0].equals(primitiveClazz)){
                writeMethod = method;
                isMatch = true;
                continue;
            }
            if(isAssignableFrom(writeMethod.getParameterTypes()[0], paramTypes[0])){
                writeMethod = method;
            }
        }
        if(writeMethod != null){
            if(overloadMap.size() > 1){
                if(valClazz == null){
                    throw new NoSuchWritablePropertyException(
                        clazz,
                        property,
                        "The method cannot be specified, because the method of the overload exists."
                    );
                }
                setMethodCache.put(clazz, overloadMap);
            }else{
                setMethodCache.put(clazz, writeMethod);
            }
            return writeMethod;
        }
        if(Map.class.isAssignableFrom(clazz)){
            try{
                writeMethod = Map.class.getMethod(
                    MAP_SET_METHOD_NAME,
                    MAP_SET_METHOD_ARGS
                );
                setMethodCache.put(clazz, writeMethod);
                return writeMethod;
            }catch(NoSuchMethodException e){
            }
        }
        throw new NoSuchWritablePropertyException(
            clazz,
            property
        );
    }
    
    protected boolean isAssignableFrom(Class<?> thisClass, Class<?> thatClass){
        if(thatClass == null){
            return !thisClass.isPrimitive();
        }
        if(isNumber(thisClass) && isNumber(thatClass)){
            if(Byte.TYPE.equals(thisClass)
                || Byte.class.equals(thisClass)){
                return Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(Short.TYPE.equals(thisClass)
                || Short.class.equals(thisClass)){
                return Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(Integer.TYPE.equals(thisClass)
                || Integer.class.equals(thisClass)){
                return Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(Long.TYPE.equals(thisClass)
                || Long.class.equals(thisClass)){
                return Long.TYPE.equals(thatClass)
                    || Long.class.equals(thatClass)
                    || Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(BigInteger.class.equals(thisClass)){
                return BigInteger.class.equals(thatClass)
                    || Long.TYPE.equals(thatClass)
                    || Long.class.equals(thatClass)
                    || Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(Float.TYPE.equals(thisClass)
                || Float.class.equals(thisClass)){
                return Float.TYPE.equals(thatClass)
                    || Float.class.equals(thatClass)
                    || Long.TYPE.equals(thatClass)
                    || Long.class.equals(thatClass)
                    || Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(Double.TYPE.equals(thisClass)
                || Double.class.equals(thisClass)){
                return Double.TYPE.equals(thatClass)
                    || Double.class.equals(thatClass)
                    || Float.TYPE.equals(thatClass)
                    || Float.class.equals(thatClass)
                    || Long.TYPE.equals(thatClass)
                    || Long.class.equals(thatClass)
                    || Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }else if(BigDecimal.class.equals(thisClass)){
                return BigDecimal.class.equals(thatClass)
                    || Double.TYPE.equals(thatClass)
                    || Double.class.equals(thatClass)
                    || Float.TYPE.equals(thatClass)
                    || Float.class.equals(thatClass)
                    || BigInteger.class.equals(thatClass)
                    || Long.TYPE.equals(thatClass)
                    || Long.class.equals(thatClass)
                    || Integer.TYPE.equals(thatClass)
                    || Integer.class.equals(thatClass)
                    || Short.TYPE.equals(thatClass)
                    || Short.class.equals(thatClass)
                    || Byte.TYPE.equals(thatClass)
                    || Byte.class.equals(thatClass);
            }
            return true;
        }else{
            return thisClass.isAssignableFrom(thatClass);
        }
    }
    
    protected boolean isNumber(Class<?> clazz){
        if(clazz == null){
            return false;
        }
        if(clazz.isPrimitive()){
            if(Byte.TYPE.equals(clazz)
                || Short.TYPE.equals(clazz)
                || Integer.TYPE.equals(clazz)
                || Long.TYPE.equals(clazz)
                || Float.TYPE.equals(clazz)
                || Double.TYPE.equals(clazz)){
                return true;
            }else{
                return false;
            }
        }else if(Number.class.isAssignableFrom(clazz)){
            return true;
        }else{
            return false;
        }
    }
    
    protected Class<?> toPrimitive(Class<?> clazz){
        if(clazz == null){
            return null;
        }
        if(Boolean.class.equals(clazz)){
            return Boolean.TYPE;
        }else if(Byte.class.equals(clazz)){
            return Byte.TYPE;
        }else if(Short.class.equals(clazz)){
            return Short.TYPE;
        }else if(Character.class.equals(clazz)){
            return Character.TYPE;
        }else if(Integer.class.equals(clazz)){
            return Integer.TYPE;
        }else if(Long.class.equals(clazz)){
            return Long.TYPE;
        }else if(Float.class.equals(clazz)){
            return Float.TYPE;
        }else if(Double.class.equals(clazz)){
            return Double.TYPE;
        }else{
            return null;
        }
    }
    
    protected Number castPrimitiveWrapper(Class<?> clazz, Number val){
        if(Byte.class.equals(clazz)){
            return new Byte(val.byteValue());
        }else if(Short.class.equals(clazz)){
            return new Short(val.shortValue());
        }else if(Integer.class.equals(clazz)){
            return new Integer(val.intValue());
        }else if(Long.class.equals(clazz)){
            return new Long(val.longValue());
        }else if(BigInteger.class.equals(clazz)){
            return BigInteger.valueOf(val.longValue());
        }else if(Float.class.equals(clazz)){
            return new Float(val.floatValue());
        }else if(Double.class.equals(clazz)){
            return new Double(val.doubleValue());
        }else if(BigDecimal.class.equals(clazz)){
            if(val instanceof BigInteger){
                return new BigDecimal((BigInteger)val);
            }else{
                return new BigDecimal(val.doubleValue());
            }
        }else{
            return val;
        }
    }
    
    /**
     * 指定されたBeanの、全ての単純プロパティを取得する。<p>
     *
     * @param bean 対象となるBean
     * @return 指定されたBeanの、全ての単純プロパティを取得する。
     */
    public static SimpleProperty[] getProperties(Object bean){
        return getProperties(bean.getClass());
    }
    
    /**
     * 指定されたクラスの、全ての単純プロパティを取得する。<p>
     *
     * @param clazz 対象となるクラス
     * @return 指定されたBeanの、全ての単純プロパティを取得する。
     */
    public static SimpleProperty[] getProperties(Class<?> clazz){
        Set<SimpleProperty> props = new HashSet<SimpleProperty>();
        if(isAccessableClass(clazz)){
            props = getProperties(clazz, props);
        }else{
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                if(isAccessableClass(itf)){
                    props = getProperties(itf, props);
                    break;
                }
            }
        }
        SimpleProperty[] result = props.toArray(new SimpleProperty[props.size()]);
        Arrays.sort(result);
        return result;
    }
    
    /**
     * 指定されたBeanの、全ての単純プロパティ名を取得する。<p>
     *
     * @param bean 対象となるBean
     * @return 指定されたBeanの、全ての単純プロパティ名を取得する。
     */
    public static Set<String> getPropertyNames(Object bean){
        return getPropertyNames(bean.getClass());
    }
    
    /**
     * 指定されたクラスの、全ての単純プロパティ名を取得する。<p>
     *
     * @param clazz 対象となるクラス
     * @return 指定されたBeanの、全ての単純プロパティ名を取得する。
     */
    public static Set<String> getPropertyNames(Class<?> clazz){
        Set<String> props = new HashSet<String>();
        if(isAccessableClass(clazz)){
            props = getPropertyNames(clazz, props);
        }else{
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                if(isAccessableClass(itf)){
                    props = getPropertyNames(itf, props);
                    break;
                }
            }
        }
        return props;
    }
    
    private static Set<String> getPropertyNames(Class<?> clazz, Set<String> props){
        BeanInfo beanInfo = null;
        try{
            beanInfo = Introspector.getBeanInfo(clazz);
        }catch(IntrospectionException e){
            return props;
        }
        final PropertyDescriptor[] descriptors
             = beanInfo.getPropertyDescriptors();
        if(descriptors == null){
            return props;
        }
        for(PropertyDescriptor descriptor : descriptors){
            props.add(descriptor.getName());
        }
        return props;
    }
    
    private static Set<SimpleProperty> getProperties(Class<?> clazz, Set<SimpleProperty> props){
        BeanInfo beanInfo = null;
        try{
            beanInfo = Introspector.getBeanInfo(clazz);
        }catch(IntrospectionException e){
            return props;
        }
        final PropertyDescriptor[] descriptors
             = beanInfo.getPropertyDescriptors();
        if(descriptors == null){
            return props;
        }
        for(PropertyDescriptor descriptor : descriptors){
            props.add(new IntrospectSimpleProperty(clazz, descriptor));
        }
        return props;
    }
    
    /**
     * 指定されたBeanの、このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子を取得する。<p>
     *
     * @param obj 対象となるBean
     * @return このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子
     * @exception NoSuchPropertyException このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子が見つからない場合
     */
    protected PropertyDescriptor getPropertyDescriptor(Object obj)
     throws NoSuchPropertyException{
        return getPropertyDescriptor(obj.getClass());
    }
    
    /**
     * 指定されたクラスがアクセス可能かどうかを調べる。<p>
     *
     * @param clazz 対象となるクラスオブジェクト
     * @return アクセス可能な場合true
     */
    protected static boolean isAccessableClass(Class<?> clazz){
        final int modifier = clazz.getModifiers();
        return Modifier.isPublic(modifier)
            || ((Modifier.isProtected(modifier)
                || (!Modifier.isPublic(modifier)
                    && !Modifier.isProtected(modifier)
                    && !Modifier.isPrivate(modifier)))
                && SimpleProperty.class.getPackage().equals(clazz.getPackage()));
    }
    
    /**
     * 指定されたクラスの、このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子を取得する。<p>
     *
     * @param clazz 対象となるクラスオブジェクト
     * @return このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子
     * @exception NoSuchPropertyException このプロパティが持つプロパティ名に該当するアクセス可能なプロパティ記述子が見つからない場合
     */
    protected PropertyDescriptor getPropertyDescriptor(Class<?> clazz)
     throws NoSuchPropertyException{
        if(!isAccessableClass(clazz)){
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                if(isAccessableClass(itf)){
                    try{
                        return getPropertyDescriptor(itf);
                    }catch(NoSuchPropertyException e){
                    }
                }
            }
            final Class<?> superClass = clazz.getSuperclass();
            if(superClass != null){
                return getPropertyDescriptor(superClass);
            }
            throw new NoSuchPropertyException(clazz, property);
        }
        BeanInfo beanInfo = null;
        try{
            beanInfo = Introspector.getBeanInfo(clazz);
        }catch(IntrospectionException e){
            throw new NoSuchPropertyException(clazz, property, e);
        }
        final PropertyDescriptor[] descriptors
            = beanInfo.getPropertyDescriptors();
        if(descriptors == null){
            throw new NoSuchPropertyException(clazz, property);
        }
        String prop = property;
        final int len = prop.length();
        if(len != 0 && Character.isUpperCase(prop.charAt(0))){
            if(len > 1){
                prop = Character.toLowerCase(prop.charAt(0))
                    + prop.substring(1);
            }else{
                prop = prop.toLowerCase();
            }
        }
        for(PropertyDescriptor descriptor : descriptors){
            if(prop.equals(descriptor.getName())
                || property.equals(descriptor.getName())){
                return descriptor;
            }
        }
        throw new NoSuchPropertyException(clazz, property);
    }
    
    @Override
    public void setIgnoreNullProperty(boolean isIgnore){
        isIgnoreNullProperty = isIgnore;
    }
    
    @Override
    public boolean isIgnoreNullProperty(){
        return isIgnoreNullProperty;
    }
    
    /**
     * このインデックスプロパティの文字列表現を取得する。<p>
     *
     * @return SimpleProperty{プロパティ名}
     */
    @Override
    public String toString(){
        return "SimpleProperty{" + property + "}";
    }
    
    /**
     * このオブジェクトと他のオブジェクトが等しいかどうかを示します。 <p>
     *
     * @param obj 比較対象のオブジェクト
     * @return 引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false。
     */
    @Override
    public boolean equals(Object obj){
        if(obj == null){
            return false;
        }
        if(!(obj instanceof SimpleProperty)){
            return false;
        }
        final SimpleProperty comp = (SimpleProperty)obj;
        if(property == null && comp.property == null){
            return true;
        }else if(property == null){
            return false;
        }else{
            return property.equals(comp.property);
        }
    }
    
    /**
     * ハッシュ値を取得する。<p>
     *
     * @return ハッシュ値
     */
    @Override
    public int hashCode(){
        return property == null ? 0 : property.hashCode();
    }
    
    /**
     * このオブジェクトと指定されたオブジェクトの順序を比較する。<p>
     *
     * @param obj 比較対象のオブジェクト
     * @return このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
     */
    @Override
    public int compareTo(Property obj){
        if(obj == null){
            return 1;
        }
        if(!(obj instanceof SimpleProperty)){
            return 1;
        }
        final SimpleProperty comp = (SimpleProperty)obj;
        if(property == null && comp.property == null){
            return 0;
        }else if(property == null){
            return -1;
        }else{
            return property.compareTo(comp.property);
        }
    }
    
    /**
     * デシリアライズ処理を行う。<p>
     *
     * @param in 入力ストリーム
     * @exception IOException デシリアライズ処理中にI/O例外が発生した場合
     * @exception ClassNotFoundException デシリアライズ処理中にデシリアライズしたオブジェクトのクラスが見つからない場合
     */
    private void readObject(ObjectInputStream in)
     throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        getMethodCache = new HashMap<Class<?>, Method>();
        setMethodCache = new HashMap<Class<?>, Object>();
    }
    
    private static class IntrospectSimpleProperty extends SimpleProperty{
        
        private static final long serialVersionUID = 883129830207417832L;
        private Class<?> target;
        private Class<?> propertyType;
        private Method readMethod;
        private Method writeMethod;
        public IntrospectSimpleProperty(Class<?> target, PropertyDescriptor desc) throws IllegalArgumentException{
            super(desc.getName());
            this.target = target;
            propertyType = desc.getPropertyType();
            readMethod = desc.getReadMethod();
            writeMethod = desc.getWriteMethod();
        }
        
        public Class<?> getPropertyType(Object obj) throws NoSuchPropertyException{
            if(target.equals(obj.getClass())){
                return propertyType;
            }
            return super.getPropertyType(obj);
        }
        
        public Class<?> getPropertyType(Class<?> clazz) throws NoSuchPropertyException{
            if(target.equals(clazz)){
                return propertyType;
            }
            return super.getPropertyType(clazz);
        }
        
        public boolean isReadable(Object obj){
            if(target.equals(obj.getClass())){
                return readMethod != null;
            }
            return super.isReadable(obj);
        }
        
        public boolean isReadable(Class<?> clazz){
            if(target.equals(clazz)){
                return readMethod != null;
            }
            return super.isReadable(clazz);
        }
        
        public boolean isWritable(Object obj, Object value){
            if(target.equals(obj.getClass())){
                if(value == null){
                    return writeMethod != null;
                }else{
                    return writeMethod != null && writeMethod.getParameterTypes()[0].isAssignableFrom(value.getClass());
                }
            }
            return super.isWritable(obj, value);
        }
        
        public boolean isWritable(Object obj, Class<?> clazz){
            if(target.equals(obj.getClass())){
                return writeMethod != null && writeMethod.getParameterTypes()[0].isAssignableFrom(clazz);
            }
            return super.isWritable(obj, clazz);
        }
        
        public boolean isWritable(Class<?> clazz){
            if(target.equals(clazz)){
                return writeMethod != null;
            }
            return super.isWritable(clazz);
        }
        
        public Object getProperty(Object obj)
         throws NoSuchPropertyException, InvocationTargetException{
            if(target.equals(obj.getClass())){
                if(readMethod == null){
                    throw new NoSuchPropertyException(obj.getClass(), property);
                }
                try{
                    return readMethod.invoke(obj);
                }catch(IllegalAccessException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        obj.getClass(),
                        property,
                        e
                    );
                }catch(IllegalArgumentException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        obj.getClass(),
                        property,
                        e
                    );
                }
            }
            return super.getProperty(obj);
        }
        
        public void setProperty(Object obj, Class<?> type, Object value)
         throws NoSuchPropertyException, InvocationTargetException{
            if(target.equals(obj.getClass())){
                if(writeMethod == null){
                    throw new NoSuchPropertyException(obj.getClass(), property);
                }
                try{
                    final Class<?> paramType = writeMethod.getParameterTypes()[0];
                    if(value instanceof Number
                         && !paramType.isPrimitive()
                         && !paramType.equals(value.getClass())
                    ){
                        value = castPrimitiveWrapper(paramType, (Number)value);
                    }
                    writeMethod.invoke(obj, value);
                }catch(IllegalAccessException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        obj.getClass(),
                        property,
                        e
                    );
                }catch(IllegalArgumentException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        obj.getClass(),
                        property,
                        e
                    );
                }
            }
            super.setProperty(obj, type, value);
        }
        
        public Method getReadMethod(Class<?> clazz)
         throws NoSuchPropertyException, InvocationTargetException{
            if(target.equals(clazz)){
                return readMethod;
            }
            return super.getReadMethod(clazz);
        }
        
        public Method getWriteMethod(Class<?> clazz, Class<?> valClazz)
         throws NoSuchPropertyException, InvocationTargetException{
            if(target.equals(clazz)){
                if(writeMethod == null){
                    throw new NoSuchPropertyException(clazz, property);
                }else if(valClazz == null){
                    return writeMethod;
                }else if(writeMethod.getParameterTypes()[0].isAssignableFrom(valClazz)){
                    return writeMethod;
                }else{
                    throw new NoSuchPropertyException(clazz, property);
                }
            }
            return super.getWriteMethod(clazz, valClazz);
        }
    }
}
