package com.jware.util.bean;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * BeanのBeanInfo及びプロパティのRead/Writeメソッドのキャッシュ
 * jgetReadMethod,
 * getWriteMethod, getPropertyDescriptorsでキャッシュからBeanの情報をロードする。
 * キャッシュに存在していない場合にjava.beans.IntrospectorによりBean
 * オブジェクトを分析し、BeanInfo及び各プロパティのRead,Writeメソッドをキャッシュに
 * 格納する。
 */
public final class BeanInfoCache {
    /**
     * JavaBeanのPropertyDescriptorのキャッシュ
     * Key はJavaBeanのClass, ValueはPropertyDescriptor[]
     */
    private static HashMap descriptorsCache = new HashMap();
    /**
     *   JavaBeanのメソッドキャッシュ
     * keyはJavaBeanのClass, ValueはMethodMap.
     * MethodMapのkeyはPropertyName, valueは
     * Method[] . 0番がReadMethod.
     */
    private static HashMap methodCache = new HashMap();

    /**
     *   デフォルトコンストラクタ
     */
    private BeanInfoCache() {
    }

    /**
     * 指定するbeanClassのPropertyDescriptor[]の取得
     * キャッシュにある場合、キャッシュから取得する。
     * キャッシュになければ、Introspectorの機能
     * を使って、PropertyDescriptor[]を取得し、キャッシュに追加する。
     * @param beanClass
     * @return　IntrospectionExceptionは発生する場合、
     * 長さは０のPropertyDescriptorを戻る。
     */
    public static PropertyDescriptor[]
        getPropertyDescriptors(Class beanClass) {

        if (beanClass == null) {
            throw new IllegalArgumentException("No bean class specified");
        }

        // Look up any cached descriptors for this bean class
        PropertyDescriptor descriptors[] = null;
        descriptors =
            (PropertyDescriptor[])descriptorsCache.get(beanClass);
        if (descriptors != null) {
            return descriptors;
        }

        // Introspect the bean and cache the generated descriptors
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(beanClass);
        } catch (IntrospectionException e) {
            return new PropertyDescriptor[0];
        }
        descriptors = beanInfo.getPropertyDescriptors();
        if (descriptors == null) {
            descriptors = new PropertyDescriptor[0];
        }
        descriptorsCache.put(beanClass, descriptors);
        addMethodMap(beanClass, descriptors);

        return descriptors;
    }

    /**
     * メソッドキャッシュへメソッドの追加
     * descriptorsの各プロパティのRead/Writeメソッドを
     * 取得し、マップを作成する(キーはプロパティ名、valueはMethod[],0
     * 番はReadMethod),最後にmethodCacheへ作成するマップをプットする
     * 
     * @param beanClass Beanのクラス
     * @param descriptors　Beanのプロパティディスクリプター
     * @return　作成したメソッドマップ
     */
    private static Map addMethodMap(Class beanClass,
        PropertyDescriptor[] descriptors) {
        int length = descriptors.length;
        Map methodMap = new HashMap();
        for (int i = 0; i < length; i++) {
            Method[] methods = new Method[2];
            methods[0] = descriptors[i].getReadMethod();
            methods[1] = descriptors[i].getWriteMethod();
            methodMap.put(descriptors[i].getName(), methods);
        }
        methodCache.put(beanClass, methodMap);
        return methodMap;
    }

    /**
     * beanClassの各プロパティのRead/Writeメソッドをキャッシュへの追加
     * getPropertyDescriptorsでbeanClassのPropertyDescriptor[]を
     * 取得し、addMethodMap(Class, PropertyDescriptor[])をコールする
     * @param beanClass　Beanのクラス
     * @return　作成した該当Beanのメソッドマップ
     */
    private static Map addMethodMap(Class beanClass) {
        PropertyDescriptor[] descriptors = getPropertyDescriptors(beanClass);
        return addMethodMap(beanClass, descriptors);
    }

    /**
     * 指定するbeanClassのnameプロパティのRead,
     * Writeメソッドを取得する。
     * @param beanClass　Beanのクラス
     * @param name　プロパティ名
     * @return　メソッド配列（０：ReadMethod,１：WriteMethod)
     */
    private static Method[] getMethods(Class beanClass, String name) {
        Map methodMap = (Map)methodCache.get(beanClass);
        if (methodMap == null) {
            methodMap = addMethodMap(beanClass);
        }
        return (Method[])methodMap.get(name);

    }

    /**
     * 指定するbeanClassのnameプロパティのReadメソッドの取得
     * beanClassはキャッシュに存在する場合、キャッシュ中の情報を戻る、
     * 存在しなければ、beanClassを分析し、キャッシュに追加する。
     * @param beanClass　クラス名
     * @param name　プロパティ名
     * @return　ReadMethodはない場合、nullを戻る。
     */
    public static Method getReadMethod(Class beanClass, String name) {
        Method[] methods = getMethods(beanClass, name);
        if (methods == null) {
            return null;
        } else {
            return methods[0];
        }
    }

    /**
     * 指定するbeanClassのnameプロパティのWriteメソッドの取得
     * beanClassはキャッシュに存在する場合、キャッシュ中の情報を戻る、
     * 存在しなければ、beanClassを分析し、キャッシュに追加する。
     * @param beanClass　クラス名
     * @param name　プロパティ名
     * @return　Writeメソッドはない場合、nullを戻る。
     */
    public static Method getWriteMethod(Class beanClass, String name) {
        Method[] methods = getMethods(beanClass, name);
        if (methods == null) {
            return null;
        } else {
            return methods[1];
        }
    }
}