/*
 * $Id: ObjectUtil.java,v 1.1 2006/04/11 16:14:32 sugimotokenichi Exp $
 * Copyright (C) 2005 SUGIMOTO Ken-ichi
 * 作成日: 2005/03/23
 */
package feat2;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.collections.BeanMap;

import feat2.config.type.Identifier;
import feat2.config.type.IdentifierConverter;
import feat2.config.type.IdentifierList;
import feat2.config.type.IdentifierListConverter;
import feat2.config.type.JavaIdentifier;
import feat2.config.type.JavaIdentifierConverter;
import feat2.config.type.ObjectRef;
import feat2.config.type.ObjectRefConverter;
import feat2.config.type.Scope;
import feat2.config.type.ScopeConverter;

/**
 * オブジェクトの操作のためのユーティリティクラス。
 * @author SUGIMOTO Ken-ichi
 */
public class ObjectUtil {

    private static ConvertUtilsBean convertUtils = new ConvertUtilsBean();
    private static PropertyUtilsBean propUtils = new PropertyUtilsBean();
    private static BeanUtilsBean beanutils = new BeanUtilsBean(convertUtils, propUtils);

    static {

        setFeatConverters(convertUtils);

    }


    /**
     * クラスをロードする。
     * nameで指定された名前を完全修飾名としてロードを試みて、見つからなければnameの前に
     * パッケージ名を付けてロードを試みる。
     * デフォルトパッケージに同名のクラスがあるとそちらが優先されてしまうため、デフォルトパッケージには
     * クラスを含めないほうが良い。
     * ロードには、このクラスをロードしたクラスローダが使われる。
     * @param name ロードするクラスの名前。pkgを指定しない場合は完全修飾名
     * @param pkg パッケージ。nullでも良い
     * @throws ClassNotFoundException
     * @return Class
     */
    public static Class loadClass(String name, String pkg) throws
            ClassNotFoundException {
        // 引数のチェック
        if ( pkg == null )
            pkg = "";
        if ( name == null )
            throw new ClassNotFoundException("クラス名がnull");

        ClassLoader loader = ObjectUtil.class.getClassLoader();//Thread.currentThread().getContextClassLoader();
        Class ret = null;
        try {
            ret = loader.loadClass(name);
        }
        catch (ClassNotFoundException ex) {
            // 見つからなかったらパッケージ名を付けて探してみる
            if ( pkg.length() > 0 ) {
                name = pkg + "." + name;
                ret = loader.loadClass(name);
            }
        }

        return ret;
    }

    /**
     * 文字列の配列を適切な型に変換してコンストラクタを呼び出す。
     * @param clazz Class
     * @param args String[]
     * @return Object
     */
    public static Object newInstance(Class clazz, String[] args) throws
            NoSuchMethodException, InvocationTargetException,
            IllegalArgumentException, IllegalAccessException,
            InstantiationException {
        if ( clazz == null || args == null )
            throw new IllegalArgumentException("引数がnull");

        // コンストラクタを探す
        // 引数の数がargsと同じものを探す
        Constructor[] constructors = clazz.getConstructors();
        Constructor cons = null;
        for(int i=0; i<constructors.length; i++) {
            if ( constructors[i].getParameterTypes().length == args.length )
                cons = constructors[i];
        }

        if ( cons != null ) {
            // 渡された文字列をコンストラクタの型に変換
            Class[] types = cons.getParameterTypes();
            Object[] params = new Object[args.length];
            for(int i=0; i<args.length; i++) {
                params[i] = ConvertUtils.convert(args[i], types[i]);
            }
            return cons.newInstance(params);
        }
        throw new NoSuchMethodException(clazz.getName()+"には引数が"+args.length+"個のコンストラクタは定義されていない");
    }

    /**
     * オブジェクトのプロパティの値を取得する。
     * @param bean Object
     * @param name プロパティ名。ネストした名前やインデックス、マップも指定できる。
     * @return Object
     */
    public static Object getProperty(Object bean, String name) throws
            NoSuchMethodException, InvocationTargetException,
            IllegalAccessException {
        return PropertyUtils.getProperty(bean, name);
    }

    public static void setProperty(Object bean, String name, Object value)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        PropertyUtils.setProperty(bean, name, value);
    }

    /**
     * リストのすべてのオブジェクトのnameプロパティにvalueをセットする。
     * @param list
     * @param name
     * @param value
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static void setProperty(List list, String name, Object value)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        for(int i=0; i<list.size(); i++) {
            setProperty(list.get(i), name, value);
        }
    }

    public static Class getPropertyType(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        return PropertyUtils.getPropertyType(bean, name);
    }


    public static void populate(Object bean, Map values) throws IllegalAccessException, InvocationTargetException {
        try {
            beanutils.populate(bean, values);
        }
        catch(ConversionException ex) {
            // TODO コンバートに失敗したときの例外
        }
    }

    /**
     * ConvertUtilsBeanにfeatの型へのコンバータを追加する。
     * @param convertUtils
     */
    public static void setFeatConverters(ConvertUtilsBean convertUtils) {

        convertUtils.register(new IdentifierConverter(), Identifier.class);
        //convertUtils.register(new IdentifierArrayConverter(), new Identifier[0].getClass());
        convertUtils.register(new IdentifierListConverter(), IdentifierList.class);
        convertUtils.register(new JavaIdentifierConverter(), JavaIdentifier.class);
        convertUtils.register(new ObjectRefConverter(), ObjectRef.class);
        convertUtils.register(new ScopeConverter(), Scope.class);

    }


    /**
     * オブジェクトをMapインタフェースでラップしたオブジェクトを返す。
     * @param bean
     * @return
     */
    public static Map getBeanMap(Object bean) {
        return new BeanMap(bean);
    }


}
