/*
 * 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.lang.reflect.*;
import java.io.*;

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

/**
 * インデックスプロパティ。<p>
 * 任意のBeanの、あるプロパティ名のインデックス付きプロパティにアクセスするための{@link Property}。<br>
 * <p>
 * 以下のようなインデックス付きプロパティにアクセスするタイプセーフなコードがある。<br>
 * <pre>
 *   Object propValue = obj.getHoge(0);
 *   obj.setHoge(0, propValue);
 * </pre>
 * インデックスプロパティを使う事で、このコードを<br>
 * <pre>
 *   IndexedProperty prop = new IndexedProperty();
 *   prop.parse("hoge[0]");
 *   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">インデックス付きプロパティ</td><td>bean.getHoge(0)</td><td rowspan="2">hoge[0]</td></tr>
 *   <tr><td>bean.setHoge(0, value)</td></tr>
 *   <tr><td rowspan="2">インデックス付き戻り値（配列型）の単純プロパティ</td><td>((Object[])bean.getHoge())[0]</td><td rowspan="2">hoge[0]</td></tr>
 *   <tr><td>((Object[])bean.getHoge())[0] = value</td></tr>
 *   <tr><td rowspan="2">インデックス付き戻り値（java.util.List）の単純プロパティ</td><td>((java.util.List)bean.getHoge()).get(0)</td><td rowspan="2">hoge[0]</td></tr>
 *   <tr><td>((java.util.List)bean.getHoge()).set(0, value)</td></tr>
 *   <tr><td rowspan="2">インデックス付き戻り値（get(int)、set(int, 適切な型)メソッドを持つ任意のクラス）の単純プロパティ</td><td>bean.getHoge().get(0)</td><td rowspan="2">hoge[0]</td></tr>
 *   <tr><td>bean.getHoge().set(0, value)</td></tr>
 *   <tr><td rowspan="2">配列の要素</td><td>array[0]</td><td rowspan="2">[0]</td></tr>
 *   <tr><td>array[0] = value</td></tr>
 *   <tr><td rowspan="2">java.util.Listの要素</td><td>bean.get(0)</td><td rowspan="2">[0]</td></tr>
 *   <tr><td>bean.set(0, value)</td></tr>
 * </table>
 * 
 * @author M.Takata
 */
public class IndexedProperty extends SimpleProperty implements Serializable{
    
    private static final long serialVersionUID = -3949215311238233792L;
    
    // エラーメッセージ定義
    private static final String MSG_00001 = "Illegal IndexedProperty : ";
    private static final String MSG_00002
        = "Length of property literal must be not null.";
    private static final String RECORD_PROP_NAME = "Property";
    
    /**
     * インデックスプロパティのGetterメソッド名。<p>
     */
    protected static final String GET_METHOD_NAME = "get";
    
    /**
     * インデックスプロパティのGetterメソッドの引数型配列。<p>
     */
    protected static final Class<?>[] GET_METHOD_ARGS = new Class[]{int.class};
    
    /**
     * インデックスプロパティのSetterメソッド名。<p>
     */
    protected static final String SET_METHOD_NAME = "set";
    
    /**
     * インデックス。<p>
     */
    protected int index;
    
    /**
     * インデックスプロパティのGetterメソッドキャッシュ。<p>
     */
    protected transient Map<Class<?>, Method> indexedReadMethodCache = new HashMap<Class<?>, Method>();
    
    /**
     * インデックスプロパティのSetterメソッドキャッシュ。<p>
     */
    protected transient Map<Class<?>, Object> indexedWriteMethodCache = new HashMap<Class<?>, Object>();
    
    /**
     * 空のインデックスプロパティを生成する。<p>
     */
    public IndexedProperty(){
        super();
    }
    
    /**
     * 指定したプロパティ名で、インデックスが0のインデックスプロパティを生成する。<p>
     *
     * @param name プロパティ名
     * @exception IllegalArgumentException 引数にnullを指定した場合
     */
    public IndexedProperty(String name) throws IllegalArgumentException{
        super(name);
    }
    
    /**
     * 指定したプロパティ名とインデックスのインデックスプロパティを生成する。<p>
     *
     * @param name プロパティ名
     * @param index インデックス
     * @exception IllegalArgumentException 引数nameにnullを指定した場合
     */
    public IndexedProperty(String name, int index)
     throws IllegalArgumentException{
        super(name);
        this.index = index;
    }
    
    /**
     * このプロパティが表すプロパティ名を取得する。<p>
     *
     * @return プロパティ名[インデックス]
     */
    @Override
    public String getPropertyName(){
        return super.getPropertyName() + '[' + index + ']';
    }
    
    /**
     * プロパティ名を設定する。<p>
     *
     * @param prop プロパティ名
     * @exception IllegalArgumentException 指定されたプロパティ名がnullの場合
     */
    @Override
    protected void setPropertyName(String prop)
     throws IllegalArgumentException{
        if(prop == null){
            throw new IllegalArgumentException(MSG_00002);
        }
        property = prop;
    }
    
    /**
     * 指定したプロパティ文字列を解析する。<p>
     * ここで指定可能な文字列は、<br>
     * &nbsp;プロパティ名[インデックス]<br>
     * である。<br>
     * 但し、プロパティ名は省略可。また、インデックスはint値でなければならない。<br>
     *
     * @param prop プロパティ文字列
     * @exception IllegalArgumentException 指定されたプロパティ文字列をこのプロパティオブジェクトが解析できない場合
     */
    @Override
    public void parse(String prop) throws IllegalArgumentException{
        final int startIndexedDelim = prop.indexOf('[');
        final int endIndexedDelim = prop.indexOf(']');
        if(startIndexedDelim == -1 || endIndexedDelim == -1
            || endIndexedDelim - startIndexedDelim <= 1
            || endIndexedDelim != prop.length() - 1){
            throw new IllegalArgumentException(MSG_00001 + prop);
        }else{
            final String indexStr = prop.substring(
                startIndexedDelim + 1,
                endIndexedDelim
            );
            try{
                index = Integer.parseInt(indexStr);
            }catch(NumberFormatException e){
                throw new IllegalArgumentException(MSG_00001 + prop);
            }
            setPropertyName(prop.substring(0, startIndexedDelim));
        }
    }
    
    /**
     * インデックスを取得する。<p>
     *
     * @return インデックス
     */
    public int getIndex(){
        return index;
    }
    
    /**
     * インデックスを設定する。<p>
     *
     * @param index インデックス
     */
    public void setIndex(int index){
        this.index = index;
    }
    
    @Override
    public Class<?> getPropertyType(Object obj) throws NoSuchPropertyException{
        return (Class<?>)getPropertyType(obj, false);
    }
    
    @Override
    public Type getPropertyGenericType(Object obj) throws NoSuchPropertyException{
        return getPropertyType(obj, true);
    }
        
    @SuppressWarnings("unchecked")
    protected Type getPropertyType(Object obj, boolean isGeneric) throws NoSuchPropertyException{
        if(obj == null){
            return java.lang.Object.class;
        }
        if(obj instanceof Record
            && RECORD_PROP_NAME.equalsIgnoreCase(super.getPropertyName())){
            final Record record = (Record)obj;
            final RecordSchema recSchema = record.getRecordSchema();
            if(recSchema != null){
                final PropertySchema propSchema = recSchema.getPropertySchema(getIndex());
                if(propSchema != null){
                    final Class<?> type = propSchema.getType();
                    if(type != null){
                        return type;
                    }
                }
            }
        }
        final Class<?> clazz = obj.getClass();
        Method readMethod = null;
        if(property.length() == 0){
            return getIndexedObjectPropertyType(clazz, isGeneric);
        }else{
            if(indexedReadMethodCache.containsKey(clazz)){
                readMethod = indexedReadMethodCache.get(clazz);
            }else{
                readMethod = getReadIndexedMethod(clazz);
                indexedReadMethodCache.put(clazz, readMethod);
            }
            if(readMethod == null){
                Method setMethod = getWriteIndexedMethod(clazz, null);
                if(setMethod != null){
                    if(indexedWriteMethodCache.containsKey(clazz)){
                        final Object methodObj
                             = indexedWriteMethodCache.get(clazz);
                        if(!(methodObj instanceof Method)){
                            Map<Class<?>, Method> overloadMap = (Map<Class<?>, Method>)methodObj;
                            if(overloadMap.size() > 2
                                || (overloadMap.size() == 2
                                        && !overloadMap.containsKey(null))
                            ){
                                return null;
                            }
                        }
                    }
                    return isGeneric ? setMethod.getGenericParameterTypes()[1] : setMethod.getParameterTypes()[1];
                }
                Type retType = null;
                try{
                    retType = isGeneric ? super.getPropertyGenericType(obj) : super.getPropertyType(obj);
                }catch(NoSuchPropertyException e){
                    throw new NoSuchPropertyException(clazz, property + '[' + index + ']');
                }
                return getIndexedObjectPropertyType(retType, isGeneric);
            }else{
                return isGeneric ? readMethod.getGenericReturnType() : readMethod.getReturnType();
            }
        }
    }
    
    @Override
    public boolean isReadable(Object obj){
        try{
            getProperty(obj);
        }catch(NoSuchPropertyException e){
            return false;
        }catch(InvocationTargetException e){
            return false;
        }
        return true;
    }
    
    @Override
    public boolean isWritable(Object obj, Class<?> clazz){
        final Class<?> objClazz = obj.getClass();
        Method writeMethod = null;
        Method readMethod = null;
        if(getMethodCache.containsKey(objClazz) && getMethodCache.get(objClazz) != null){
            readMethod = getMethodCache.get(objClazz);
            return isWritableNoIndexedProperty(obj, readMethod, clazz);
        }else if(property.length() == 0){
            return isWritableIndexedObjectProperty(obj, clazz);
        }else{
            writeMethod = getWriteIndexedMethod(
                objClazz,
                clazz
            );
            if(writeMethod != null){
                return true;
            }
            Object prop = null;
            try{
                prop = super.getProperty(obj);
            }catch(NoSuchPropertyException e){
                return false;
            }catch(InvocationTargetException e){
                return false;
            }
            if(prop == null){
                return false;
            }
            return isWritableIndexedObjectProperty(prop, clazz);
        }
    }
    
    /**
     * 指定したオブジェクトから、このプロパティが表すプロパティ値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @return プロパティ値
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NullIndexPropertyException 指定されたプロパティのインデックス付き戻り値が、nullの場合
     * @exception NoSuchIndexPropertyException 指定されたBeanに、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    @Override
    public Object getProperty(Object obj)
     throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        Method readMethod = null;
        if(getMethodCache.containsKey(clazz) && getMethodCache.get(clazz) != null){
            readMethod = getMethodCache.get(clazz);
            return getNoIndexedProperty(obj, readMethod);
        }else if(indexedReadMethodCache.containsKey(clazz)){
            readMethod = indexedReadMethodCache.get(clazz);
            return getIndexedProperty(obj, readMethod);
        }else if(property.length() == 0){
            return getIndexedObjectProperty(clazz, obj);
        }else{
            readMethod = getReadIndexedMethod(clazz);
            if(readMethod != null){
                indexedReadMethodCache.put(clazz, readMethod);
                return getIndexedProperty(obj, readMethod);
            }
            Object prop = super.getProperty(obj);
            if(prop == null){
                throw new NoSuchPropertyException(
                    clazz,
                    property + '[' + index + ']'
                );
            }
            return getIndexedObjectProperty(prop.getClass(), prop);
        }
    }
    
    /**
     * 指定されたクラスからインデックス付きGetter（getプロパティ名(int)）メソッドを取得する。<p>
     * メソッドが見つからない場合は、nullを返す。
     *
     * @param clazz 対象のBeanのクラスオブジェクト
     * @return インデックス付きGetter（getプロパティ名(int)）メソッド
     */
    protected Method getReadIndexedMethod(Class<?> clazz){
        if(!isAccessableClass(clazz)){
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                final Method method = getReadIndexedMethod(itf);
                if(method != null){
                    return method;
                }
            }
            final Class<?> superClass = clazz.getSuperclass();
            if(superClass != null){
                return getReadIndexedMethod(superClass);
            }
            return null;
        }
        final StringBuilder methodName = new StringBuilder(GET_METHOD_NAME);
        if(property.length() != 0){
            char capital = property.charAt(0);
            if(Character.isUpperCase(capital)){
                methodName.append(property);
            }else{
                capital = Character.toUpperCase(capital);
                methodName.append(capital);
                if(property.length() > 1){
                    methodName.append(property.substring(1));
                }
            }
        }
        try{
            return clazz.getMethod(
                methodName.toString(),
                GET_METHOD_ARGS
            );
        }catch(NoSuchMethodException e){
            try{
                return clazz.getMethod(
                    property,
                    GET_METHOD_ARGS
                );
            }catch(NoSuchMethodException e2){
                return null;
            }
        }
    }
    
    /**
     * 指定したオブジェクトに、このプロパティが表すプロパティ値を設定する。<p>
     *
     * @param obj 対象となるBean
     * @param value 設定するプロパティ値
     * @exception NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NullIndexPropertyException 指定されたプロパティのインデックス付き戻り値が、nullの場合
     * @exception NoSuchIndexPropertyException 指定されたBeanに、このインデックスプロパティが持つインデックスが存在しない場合
     * @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 NoSuchReadablePropertyException 指定されたプロパティのGetterが存在しない場合
     * @exception NullIndexPropertyException 指定されたプロパティのインデックス付き戻り値が、nullの場合
     * @exception NoSuchIndexPropertyException 指定されたBeanに、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    @Override
    public void setProperty(Object obj, Class<?> type, Object value)
     throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        Method writeMethod = null;
        Method readMethod = null;
        if(getMethodCache.containsKey(clazz) && getMethodCache.get(clazz) != null){
            readMethod = getMethodCache.get(clazz);
            setNoIndexedProperty(obj, readMethod, value);
        }else if(property.length() == 0){
            setIndexedObjectProperty(clazz, obj, value);
        }else{
            if(type == null && value != null){
                type = value.getClass();
            }
            writeMethod = getWriteIndexedMethod(
                clazz,
                type
            );
            if(writeMethod != null){
                setIndexedProperty(obj, writeMethod, value);
                return;
            }
            Object prop = super.getProperty(obj);
            if(prop == null){
                throw new NoSuchPropertyException(
                    clazz,
                    property + '[' + index + ']'
                );
            }
            setIndexedObjectProperty(prop.getClass(), prop, value);
        }
    }
    
    /**
     * 指定されたクラスからインデックス付きSetter（setプロパティ名(int, 引数で指定したparamのクラス型)）メソッドを取得する。<p>
     * メソッドが見つからない場合は、nullを返す。
     *
     * @param clazz 対象のBeanのクラスオブジェクト
     * @param param 設定する値のクラスオブジェクト
     * @return インデックス付きSetter（setプロパティ名(int, 引数で指定したparamのクラス型)）メソッド
     */
    @SuppressWarnings("unchecked")
    protected Method getWriteIndexedMethod(Class<?> clazz, Class<?> param){
        if(indexedWriteMethodCache.containsKey(clazz)){
            final Object methodObj = indexedWriteMethodCache.get(clazz);
            if(methodObj instanceof Method){
                return (Method)methodObj;
            }
            final Map<Class<?>, Method> overloadMap = (Map<Class<?>, Method>)methodObj;
            if(param == null){
                if(overloadMap.size() == 1){
                    return overloadMap.values().iterator().next();
                }else{
                    Method setMethod = overloadMap.get(null);
                    if(setMethod != null){
                        return setMethod;
                    }
                    Object[] classes = overloadMap.keySet().toArray();
                    for(Object key : classes){
                        Method method = overloadMap.get(key);
                        final Class<?>[] params = method.getParameterTypes();
                        if(setMethod == null){
                            if(!params[1].isPrimitive()){
                                setMethod = method;
                            }
                            continue;
                        }
                        if(isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                            setMethod = method;
                        }
                    }
                    final Map<Class<?>, Method> tmpOverloadMap = new HashMap<Class<?>, Method>(overloadMap);
                    tmpOverloadMap.put(null, setMethod);
                    indexedWriteMethodCache.put(clazz, tmpOverloadMap);
                    return setMethod;
                }
            }else if(overloadMap.containsKey(param)){
                return overloadMap.get(param);
            }else{
                Method setMethod = overloadMap.get(null);
                if(setMethod != null){
                    return setMethod;
                }
                final Object[] classes = overloadMap.keySet().toArray();
                final Class<?> primitiveClazz = toPrimitive(param);
                for(Object key : classes){
                    Method method = overloadMap.get(key);
                    final Class<?>[] params = method.getParameterTypes();
                    if(setMethod == null){
                        if(!isAssignableFrom(params[1], param)
                            && !params[1].equals(primitiveClazz)){
                            continue;
                        }
                        setMethod = method;
                        if(param.equals(params[0])
                            || params[0].equals(primitiveClazz)){
                            break;
                        }
                        continue;
                    }
                    if(!isAssignableFrom(params[1], param)
                         && !params[1].equals(primitiveClazz)){
                        continue;
                    }
                    if(params[1].equals(param)
                        || params[1].equals(primitiveClazz)){
                        setMethod = method;
                        break;
                    }
                    if(isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                        setMethod = method;
                    }
                }
                final Map<Class<?>, Method> tmpOverloadMap = new HashMap<Class<?>, Method>(overloadMap);
                tmpOverloadMap.put(param, setMethod);
                indexedWriteMethodCache.put(clazz, tmpOverloadMap);
                return setMethod;
            }
        }
        if(!isAccessableClass(clazz)){
            final Class<?>[] interfaces = clazz.getInterfaces();
            for(Class<?> itf : interfaces){
                final Method method = getWriteIndexedMethod(
                    itf,
                    param
                );
                if(method != null){
                    return method;
                }
            }
            final Class<?> superClass = clazz.getSuperclass();
            if(superClass != null){
                return getWriteIndexedMethod(superClass, param);
            }
            return null;
        }
        final StringBuilder methodName = new StringBuilder(SET_METHOD_NAME);
        if(property.length() != 0){
            char capital = property.charAt(0);
            if(Character.isUpperCase(capital)){
                methodName.append(property);
            }else{
                capital = Character.toUpperCase(capital);
                methodName.append(capital);
                if(property.length() > 1){
                    methodName.append(property.substring(1));
                }
            }
        }
        Method setMethod = null;
        final Method[] methods = clazz.getMethods();
        if(methods == null || methods.length == 0){
            return null;
        }
        final Class<?> primitiveClazz = toPrimitive(param);
        final Map<Class<?>, Method> overloadMap = new HashMap<Class<?>, Method>();
        boolean isMatch = false;
        for(Method method : methods){
            if(!methodName.toString().equals(method.getName())){
                continue;
            }
            final Class<?>[] params = method.getParameterTypes();
            if(params == null || params.length != 2
                 || !params[0].equals(Integer.TYPE)
            ){
                continue;
            }
            overloadMap.put(params[1], method);
            if(isMatch){
                continue;
            }
            if(setMethod == null){
                if(param == null){
                    setMethod = method;
                    continue;
                }
                if(!isAssignableFrom(params[1], param)
                    && !params[1].equals(primitiveClazz)){
                    continue;
                }
                setMethod = method;
                if(param.equals(params[0])
                    || params[0].equals(primitiveClazz)){
                    isMatch = true;
                }
                continue;
            }
            if(param == null){
                if(isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                    setMethod = method;
                }
                continue;
            }
            if(!isAssignableFrom(params[1], param)
                 && !params[1].equals(primitiveClazz)){
                continue;
            }
            if(params[1].equals(param)
                || params[1].equals(primitiveClazz)){
                isMatch = true;
                setMethod = method;
                continue;
            }
            if(isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                setMethod = method;
            }
        }
        if(param == null){
            overloadMap.put(null, setMethod);
        }
        if(setMethod != null){
            if(overloadMap.size() > 1){
                indexedWriteMethodCache.put(clazz, overloadMap);
            }else{
                indexedWriteMethodCache.put(clazz, setMethod);
            }
        }
        return setMethod;
    }
    
    /**
     * 指定したオブジェクトのインデックス付きGetter（getプロパティ名(int)）を呼び出しプロパティ値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @param readMethod インデックス付きGetter（getプロパティ名(int)）
     * @return プロパティ値
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    protected Object getIndexedProperty(Object obj, Method readMethod)
     throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        try{
            return readMethod.invoke(
                obj,
                index
            );
        }catch(IllegalAccessException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }catch(IllegalArgumentException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }
    }
    
    /**
     * 指定したオブジェクトのインデックス付きSetter（setプロパティ名(int, 任意のクラス)）を呼び出しプロパティ値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @param writeMethod インデックス付きSetter（setプロパティ名(int, 任意のクラス)）
     * @param value 設定するプロパティ値
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合
     * @exception InvocationTargetException 指定されたBeanのアクセサを呼び出した結果、例外がthrowされた場合
     */
    protected void setIndexedProperty(
        Object obj,
        Method writeMethod,
        Object value
    ) throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        try{
            final Class<?> paramType = writeMethod.getParameterTypes()[1];
            if(value instanceof Number
                 && !paramType.isPrimitive()
                 && !paramType.equals(value.getClass())
            ){
                value = castPrimitiveWrapper(paramType, (Number)value);
            }
            writeMethod.invoke(
                obj,
                index, value
            );
        }catch(IllegalAccessException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }catch(IllegalArgumentException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }
    }
    
    /**
     * 指定したオブジェクトのインデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）を呼び出し、その戻り値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @param readMethod インデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）
     * @return インデックス付き戻り値
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合、または戻り値がインデックス付き戻り値でない場合
     * @exception InvocationTargetException 指定されたBeanまたはインデックス付き戻り値のアクセサを呼び出した結果、例外がthrowされた場合
     */
    protected Object getIndexedObject(Object obj, Method readMethod)
     throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        try{
            if(readMethod.getParameterTypes().length == 0){
                return readMethod.invoke(obj);
            }else{
                return readMethod.invoke(
                    obj,
                    super.getPropertyName()
                );
            }
        }catch(IllegalAccessException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }catch(IllegalArgumentException e){
            // 起こらないはず
            throw new NoSuchPropertyException(
                clazz,
                property + '[' + index + ']',
                e
            );
        }
    }
    
    /**
     * 指定したオブジェクトのインデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）を呼び出し、戻り値からプロパティ値を取得する。<p>
     *
     * @param obj 対象となるBean
     * @param readMethod インデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）
     * @return プロパティ値
     * @exception NullIndexPropertyException 指定されたプロパティのインデックス付き戻り値が、nullの場合
     * @exception NoSuchIndexPropertyException インデックス付き戻り値に、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合、または戻り値がインデックス付き戻り値でない場合
     * @exception InvocationTargetException 指定されたBeanまたはインデックス付き戻り値のアクセサを呼び出した結果、例外がthrowされた場合
     */
    protected Object getNoIndexedProperty(Object obj, Method readMethod)
     throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        Object indexedObj = getIndexedObject(obj, readMethod);
        if(indexedObj == null){
            if(isIgnoreNullProperty){
                return null;
            }else{
                throw new NullIndexPropertyException(
                    clazz,
                    property + '[' + index + ']'
                );
            }
        }else{
            return getIndexedObjectProperty(indexedObj.getClass(), indexedObj);
        }
    }
    
    /**
     * 指定したオブジェクトのインデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）を呼び出し、戻り値にプロパティ値を設定する。<p>
     *
     * @param obj 対象となるBean
     * @param readMethod インデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）
     * @param value プロパティ値
     * @exception NullIndexPropertyException 指定されたプロパティのインデックス付き戻り値が、nullの場合
     * @exception NoSuchIndexPropertyException インデックス付き戻り値に、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたBeanが、このプロパティが表すアクセス可能なプロパティを持っていない場合、または戻り値がインデックス付き戻り値でない場合
     * @exception InvocationTargetException 指定されたBeanまたはインデックス付き戻り値のアクセサを呼び出した結果、例外がthrowされた場合
     */
    protected void setNoIndexedProperty(
        Object obj,
        Method readMethod,
        Object value
    ) throws NoSuchPropertyException, InvocationTargetException{
        final Class<?> clazz = obj.getClass();
        Object indexedObj = getIndexedObject(obj, readMethod);
        if(indexedObj == null){
            throw new NullIndexPropertyException(
                clazz,
                property + '[' + index + ']'
            );
        }else{
            setIndexedObjectProperty(indexedObj.getClass(), indexedObj, value);
        }
    }
    
    /**
     * 指定したオブジェクトのインデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）を呼び出し、戻り値にプロパティ値を設定可能か判定する。<p>
     *
     * @param obj 対象となるBean
     * @param readMethod インデックス付き戻り値Getter（インデックス付きオブジェクト getプロパティ名()）
     * @param clazz プロパティの型
     * @return インデックス付き戻り値にプロパティ値を設定可能な場合true
     */
    protected boolean isWritableNoIndexedProperty(
        Object obj,
        Method readMethod,
        Class<?> clazz
    ){
        Object indexedObj = null;
        try{
            indexedObj = getIndexedObject(obj, readMethod);
        }catch(NoSuchPropertyException e){
            return false;
        }catch(InvocationTargetException e){
            return false;
        }
        if(indexedObj == null){
            return false;
        }else{
            return isWritableIndexedObjectProperty(indexedObj, clazz);
        }
    }
    
    /**
     * 指定したインデックス付きオブジェクトに、このインデックスプロパティが持つインデックスのプロパティ値を設定可能か判定する。<p>
     * ここで言う、インデックス付きオブジェクトとは、配列、{@link java.util.List}、インデックス付きSetter（set(int, プロパティ値の型に適合する任意のクラス)）を持つオブジェクトのいずれかである。
     *
     * @param obj インデックス付きオブジェクト
     * @param clazz プロパティの型
     * @return 指定したインデックス付きオブジェクトに、このインデックスプロパティが持つインデックスのプロパティ値を設定可能な場合true
     */
    protected boolean isWritableIndexedObjectProperty(Object obj, Class<?> clazz){
        final Class<?> indexdClazz = obj.getClass();
        if(indexdClazz.isArray()){
            if(Array.getLength(obj) <= index){
                return false;
            }else{
                return true;
            }
        }else if(obj instanceof List<?>){
            final List<?> list = (List<?>)obj;
            if(list.size() <= index){
                return false;
            }else{
                return true;
            }
        }else{
            Method setMethod = null;
            final Method[] methods = indexdClazz.getMethods();
            if(methods == null || methods.length == 0){
                return false;
            }
            Class<?> valueClass = clazz == null ? null : clazz;
            for(Method method : methods){
                if(!SET_METHOD_NAME.equals(method.getName())
                    || !Modifier.isPublic(method.getModifiers())
                    || Modifier.isNative(method.getModifiers())
                ){
                    continue;
                }
                final Class<?>[] params = method.getParameterTypes();
                if(params == null || params.length != 2
                     || (!params[0].equals(Integer.class)
                         && !params[0].equals(Integer.TYPE))
                     || (valueClass != null
                         && !isAssignableFrom(params[1], valueClass))
                ){
                    continue;
                }
                if(setMethod == null
                     || isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                    setMethod = method;
                }
            }
            if(setMethod == null){
                return false;
            }
            return true;
        }
    }
    
    /**
     * 指定したインデックス付きオブジェクトから、このインデックスプロパティが持つインデックスのプロパティ値を取得する。<p>
     * ここで言う、インデックス付きオブジェクトとは、配列、{@link java.util.List}、インデックス付きGetter（get(int)）を持つオブジェクトのいずれかである。
     *
     * @param obj インデックス付きオブジェクト
     * @return プロパティ値
     * @exception NoSuchIndexPropertyException 指定されたインデックス付き戻り値に、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたインデックス付き戻り値が、インデックス付き戻り値でない場合
     * @exception InvocationTargetException 指定されたインデックス付き戻り値のアクセサを呼び出した結果、例外がthrowされた場合
     */
    @SuppressWarnings("unchecked")
    protected Object getIndexedObjectProperty(Class<?> clazz, Object obj)
     throws NoSuchPropertyException, InvocationTargetException{
        if(clazz.isArray()){
            if(Array.getLength(obj) <= index){
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index
                );
            }else{
                return Array.get(obj, index);
            }
        }else if(obj instanceof List){
            final List<Object> list = (List<Object>)obj;
            try{
                return list.get(index);
            }catch(IndexOutOfBoundsException e){
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index,
                    e
                );
            }
        }else{
            if(!isAccessableClass(clazz)){
                final Class<?>[] interfaces = clazz.getInterfaces();
                for(Class<?> itf : interfaces){
                    if(isAccessableClass(itf)){
                        try{
                            return getIndexedObjectProperty(itf, obj);
                        }catch(NoSuchPropertyException e){
                        }
                    }
                }
                final Class<?> superClass = clazz.getSuperclass();
                if(superClass != null){
                    return getIndexedObjectProperty(superClass, obj);
                }
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index
                );
            }
            Method getMethod = null;
            try{
                getMethod = clazz.getMethod(
                    GET_METHOD_NAME,
                    GET_METHOD_ARGS
                );
            }catch(NoSuchMethodException e){
                throw new NoSuchPropertyException(clazz, property + '[' + index + ']');
            }
            if(Modifier.isPublic(getMethod.getModifiers())
                && !Modifier.isNative(getMethod.getModifiers())){
                indexedReadMethodCache.put(clazz, getMethod);
                try{
                    return getMethod.invoke(
                        obj,
                        index
                    );
                }catch(IllegalAccessException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        clazz,
                        property + '[' + index + ']',
                        e
                    );
                }catch(IllegalArgumentException e){
                    // 起こらないはず
                    throw new NoSuchPropertyException(
                        clazz,
                        property + '[' + index + ']',
                        e
                    );
                }
            }else{
                throw new NoSuchPropertyException(clazz, property + '[' + index + ']');
            }
        }
    }
    
    /**
     * 指定したインデックス付きオブジェクトのClassから、このインデックスプロパティが持つインデックスのプロパティ型を取得する。<p>
     * ここで言う、インデックス付きオブジェクトとは、配列、{@link java.util.List}、インデックス付きGetter（get(int)）を持つオブジェクトのいずれかである。
     *
     * @param indexdType インデックス付き型
     * @param isGeneric trueの場合、総称型を返す
     * @return プロパティ型
     * @exception NoSuchIndexPropertyException 指定されたインデックス付き戻り値に、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたインデックス付き戻り値が、インデックス付き戻り値でない場合
     */
    protected Type getIndexedObjectPropertyType(Type indexdType, boolean isGeneric)
     throws NoSuchPropertyException{
        Class<?> indexdClazz = null;
        if(indexdType instanceof Class){
            indexdClazz = (Class<?>)indexdType;
        }else if(indexdType instanceof ParameterizedType){
            indexdClazz = (Class<?>)((ParameterizedType)indexdType).getRawType();
        }else if(indexdType instanceof GenericArrayType){
            return isGeneric ? ((GenericArrayType)indexdType).getGenericComponentType() : Object.class;
        }else{
            return isGeneric ? indexdType : Object.class;
        }
        if(indexdClazz.isArray()){
            return indexdClazz.getComponentType();
        }else if(List.class.isAssignableFrom(indexdClazz)){
            if(indexdType instanceof ParameterizedType){
                Type elementType = ((ParameterizedType)indexdType).getActualTypeArguments()[0];
                if(isGeneric){
                    return elementType;
                }else if(elementType instanceof Class){
                    return elementType;
                }else if(elementType instanceof ParameterizedType){
                    return ((ParameterizedType)elementType).getRawType();
                }else{
                    return Object.class;
                }
            }else{
                return Object.class;
            }
        }else{
            Method getMethod = null;
            try{
                getMethod = indexdClazz.getMethod(
                    GET_METHOD_NAME,
                    GET_METHOD_ARGS
                );
            }catch(NoSuchMethodException e){
                Method setMethod = null;
                final Method[] methods = indexdClazz.getMethods();
                if(methods == null || methods.length == 0){
                    throw new NoSuchPropertyException(indexdClazz, property + '[' + index + ']');
                }
                for(Method method : methods){
                    if(!SET_METHOD_NAME.equals(method.getName())
                         || !Modifier.isPublic(method.getModifiers())
                         || Modifier.isNative(method.getModifiers())
                    ){
                        continue;
                    }
                    final Class<?>[] params = method.getParameterTypes();
                    if(params == null || params.length != 2
                         || (!params[0].equals(Integer.class)
                             && !params[0].equals(Integer.TYPE))
                    ){
                        continue;
                    }
                    if(setMethod == null){
                        setMethod = method;
                    }else{
                        // 確定できないのでエラー
                        throw new NoSuchPropertyException(
                            indexdClazz,
                            property + '[' + index + ']'
                        );
                    }
                }
                if(setMethod == null){
                    throw new NoSuchPropertyException(indexdClazz, property + '[' + index + ']');
                }
                return isGeneric ? setMethod.getGenericParameterTypes()[1] : setMethod.getParameterTypes()[1];
            }
            if(!Modifier.isPublic(getMethod.getModifiers()) || Modifier.isNative(getMethod.getModifiers())){
                throw new NoSuchPropertyException(indexdClazz, property + '[' + index + ']');
            }
            return isGeneric ? getMethod.getGenericReturnType() : getMethod.getReturnType();
        }
    }
    
    /**
     * 指定したインデックス付きオブジェクトに、このインデックスプロパティが持つインデックスのプロパティ値を設定する。<p>
     * ここで言う、インデックス付きオブジェクトとは、配列、{@link java.util.List}、インデックス付きSetter（set(int, プロパティ値の型に適合する任意のクラス)）を持つオブジェクトのいずれかである。
     *
     * @param obj インデックス付きオブジェクト
     * @param value プロパティ値
     * @exception NoSuchIndexPropertyException 指定されたインデックス付き戻り値に、このインデックスプロパティが持つインデックスが存在しない場合
     * @exception NoSuchPropertyException 指定されたインデックス付き戻り値が、インデックス付き戻り値でない場合
     * @exception InvocationTargetException 指定されたインデックス付き戻り値のアクセサを呼び出した結果、例外がthrowされた場合
     */
    @SuppressWarnings("unchecked")
    protected void setIndexedObjectProperty(Class<?> clazz, Object obj, Object value)
     throws NoSuchPropertyException, InvocationTargetException{
        if(clazz.isArray()){
            if(Array.getLength(obj) <= index){
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index
                );
            }else{
                Array.set(obj, index, value);
            }
        }else if(obj instanceof List){
            final List<Object> list = (List<Object>)obj;
            if(list.size() <= index){
                for(int i = list.size(); i <= index; i++){
                    list.add(null);
                }
            }
            try{
                list.set(index, value);
            }catch(IndexOutOfBoundsException e){
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index,
                    e
                );
            }
        }else{
            if(!isAccessableClass(clazz)){
                final Class<?>[] interfaces = clazz.getInterfaces();
                for(Class<?> itf : interfaces){
                    if(isAccessableClass(itf)){
                        try{
                            setIndexedObjectProperty(itf, obj, value);
                            return;
                        }catch(NoSuchPropertyException e){
                        }
                    }
                }
                final Class<?> superClass = clazz.getSuperclass();
                if(superClass != null){
                    setIndexedObjectProperty(superClass, obj, value);
                    return;
                }
                throw new NoSuchIndexPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    index
                );
            }
            Method setMethod = null;
            Class<?> valueClass = value == null ? null : value.getClass();
            final Method[] methods = clazz.getMethods();
            if(methods == null || methods.length == 0){
                throw new NoSuchPropertyException(clazz, property + '[' + index + ']');
            }
            for(Method method : methods){
                if(!SET_METHOD_NAME.equals(method.getName())
                     || !Modifier.isPublic(method.getModifiers())
                     || Modifier.isNative(method.getModifiers())){
                    continue;
                }
                final Class<?>[] params = method.getParameterTypes();
                if(params == null || params.length != 2
                     || (!params[0].equals(Integer.class)
                         && !params[0].equals(Integer.TYPE))
                     ||(valueClass != null
                         && !isAssignableFrom(params[1], valueClass))
                ){
                    continue;
                }
                if(setMethod == null
                     || isAssignableFrom(setMethod.getParameterTypes()[1], params[1])){
                    setMethod = method;
                }
            }
            if(setMethod == null){
                throw new NoSuchPropertyException(clazz, property + '[' + index + ']');
            }
            try{
                setMethod.invoke(
                    obj,
                    index, value
                );
            }catch(IllegalAccessException e){
                // 起こらないはず
                throw new NoSuchPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    e
                );
            }catch(IllegalArgumentException e){
                // 起こらないはず
                throw new NoSuchPropertyException(
                    clazz,
                    property + '[' + index + ']',
                    e
                );
            }
        }
    }
    
    /**
     * 指定されたBeanの、全てのインデックス付きプロパティを取得する。<p>
     * 但し、インデックスは、0。<br>
     *
     * @param bean 対象となるBean
     * @return 指定されたBeanの、全てのインデックス付きプロパティを取得する。
     */
    public static IndexedProperty[] getIndexedProperties(Object bean){
        return getIndexedProperties(bean.getClass());
    }
    
    /**
     * 指定されたクラスの、全てのインデックス付きプロパティを取得する。<p>
     * 但し、インデックスは、0。<br>
     *
     * @param clazz 対象となるクラス
     * @return 指定されたBeanの、全てのインデックス付きプロパティを取得する。
     */
    public static IndexedProperty[] getIndexedProperties(Class<?> clazz){
        Set<IndexedProperty> props = new HashSet<IndexedProperty>();
        if(isAccessableClass(clazz)){
            props = getIndexedProperties(clazz, props);
        }else{
            final Class<?>[] interfaces = clazz.getInterfaces();
                for(Class<?> itf : interfaces){
                if(isAccessableClass(itf)){
                    props = getIndexedProperties(itf, props);
                    break;
                }
            }
        }
        return props.toArray(new IndexedProperty[props.size()]);
    }
    
    private static Set<IndexedProperty> getIndexedProperties(Class<?> clazz, Set<IndexedProperty> props){
        final Method[] methods = clazz.getMethods();
        if(methods == null || methods.length == 0){
            return props;
        }
        for(Method method : methods){
            if(Modifier.isNative(method.getModifiers())){
                continue;
            }
            final Class<?>[] params = method.getParameterTypes();
            if(method.getName().startsWith(GET_METHOD_NAME)){
                final Class<?> retType = method.getReturnType();
                if(Void.TYPE.equals(retType)){
                    continue;
                }
                if(params == null){
                    if(retType.isArray()
                        || List.class.isAssignableFrom(retType)){
                        props.add(
                            new IndexedProperty(
                                method.getName().substring(3)
                            )
                        );
                    }else{
                        try{
                            retType.getMethod(
                                GET_METHOD_NAME,
                                GET_METHOD_ARGS
                            );
                            props.add(
                                new IndexedProperty(
                                    method.getName().substring(3)
                                )
                            );
                        }catch(NoSuchMethodException e){
                            final Method[] nestedMethods = retType.getMethods();
                            boolean isFound = false;
                            for(Method nestedMethod : nestedMethods){
                                final Class<?>[] nestedParams
                                     = nestedMethod.getParameterTypes();
                                if(SET_METHOD_NAME.equals(nestedMethod.getName())
                                    && !Modifier.isNative(nestedMethod.getModifiers())
                                    && nestedParams.length == 2
                                    && Integer.TYPE.equals(nestedParams[0])){
                                    isFound = true;
                                    break;
                                }
                            }
                            if(isFound){
                                props.add(
                                    new IndexedProperty(
                                        method.getName().substring(3)
                                    )
                                );
                            }
                        }
                    }
                }else if(params.length == 1){
                    if(!Integer.TYPE.equals(params[0])){
                        continue;
                    }
                    props.add(
                        new IndexedProperty(
                            method.getName().substring(3)
                        )
                    );
                }else{
                    continue;
                }
            }else if(method.getName().startsWith(SET_METHOD_NAME)){
                if(params != null && params.length == 2){
                    if(!Integer.TYPE.equals(params[0])){
                        continue;
                    }
                    props.add(
                        new IndexedProperty(
                            method.getName().substring(3)
                        )
                    );
                }else{
                    continue;
                }
            }else{
                continue;
            }
        }
        return props;
    }
    
    /**
     * このインデックスプロパティの文字列表現を取得する。<p>
     *
     * @return IndexedProperty{プロパティ名[インデックス]}
     */
    @Override
    public String toString(){
        return "IndexedProperty{" + property + '[' + index + "]}";
    }
    
    /**
     * このオブジェクトと他のオブジェクトが等しいかどうかを示します。 <p>
     *
     * @param obj 比較対象のオブジェクト
     * @return 引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false。
     */
    @Override
    public boolean equals(Object obj){
        if(obj == null){
            return false;
        }
        if(!(obj instanceof IndexedProperty)){
            return false;
        }
        final IndexedProperty comp = (IndexedProperty)obj;
        if(property == null && comp.property != null
            || property != null && comp.property == null){
            return false;
        }else if(property != null && comp.property != null
            && !property.equals(comp.property)){
            return false;
        }
        return index == comp.index;
    }
    
    /**
     * ハッシュ値を取得する。<p>
     *
     * @return ハッシュ値
     */
    @Override
    public int hashCode(){
        return (property == null ? 0 : property.hashCode()) + index + 2;
    }
    
    /**
     * このオブジェクトと指定されたオブジェクトの順序を比較する。<p>
     *
     * @param obj 比較対象のオブジェクト
     * @return このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
     */
    @Override
    public int compareTo(Property obj){
        if(obj == null){
            return 1;
        }
        if(!(obj instanceof IndexedProperty)){
            return 1;
        }
        final IndexedProperty comp = (IndexedProperty)obj;
        if(property == null && comp.property != null){
            return -1;
        }else if(property != null && comp.property == null){
            return 1;
        }else if(property != null && comp.property != null){
            final int val = property.compareTo(comp.property);
            if(val != 0){
                return val;
            }
        }
        return index - comp.index;
    }
    
    private void readObject(ObjectInputStream in)
     throws IOException, ClassNotFoundException{
        in.defaultReadObject();
        getMethodCache = new HashMap<Class<?>, Method>();
        setMethodCache = new HashMap<Class<?>, Object>();
        indexedReadMethodCache = new HashMap<Class<?>, Method>();
        indexedWriteMethodCache = new HashMap<Class<?>, Object>();
    }
}
