package org.itscool.commons.bean;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import org.itscool.commons.collection.KeyLinkedList;

import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.logging.SimpleLog;
import org.itscool.commons.util.URLDecodeUtil;
import org.itscool.commons.util.UrlUtil;

/**
 * Beanファクトリクラス<br/>
 * Bean定義ファイルに指定されたJavaBeansを生成します。<p/>
 *
 * Bean定義ファイルの記述例<hr>
 * <pre>
 * &lt;?xml version="1.0" encoding="Shift_JIS" ?&gt;
 * &lt;di-config&gt;
 *     &lt;bean-mappings&gt;
 *         &lt;bean id="sample" type="SampleBeanImpl"&gt;
 *             &lt;property name="id" value="1"/&gt;
 *             &lt;property name="name" value="ios taro"/&gt;
 *             &lt;property name="age" value="32"/&gt;
 *             &lt;property name="child" ref="child"/&gt;
 *         &lt;/bean&gt;
 *         &lt;bean&gt;
 *             &lt;bean id="child" type="SampleChild"&gt;
 *             &lt;property name="name" value="ios Jr"/&gt;
 *         &lt;/bean&gt;
 *         &lt;bean id="params" type="org.itscool.commons.bean.InitParamMap" singleton="true"&gt;
 *         &lt;property name="infos" collection="map"&gt;
 *             &lt;bean id="encoding" type="java.lang.String" value="Shift_JIS"/&gt;
 *             &lt;bean id="weber-config" value="weber-config.xml"/&gt;
 *             &lt;bean id="stylist-config" value="stylist-config.xml"/&gt;
 *             &lt;bean id="message-config" value="message-config.xml"/&gt;
 *         &lt;/property&gt;
 *         &lt;/bean&gt;
 *     &lt;/bean-mappings&gt;
 * &lt;/di-config&gt;
 * </pre>
 *
 * <p>
 * タグの説明<hr><br/>
 * &lt;di-config&gt; ：必須項目です（Bean定義ファイルに1つだけ定義)<br/>
 * &lt;bean-mappings&gt;：必須項目です（Bean定義ファイルに1つだけ定義）<br/>
 * &lt;bean&gt;JavaBeans情報を定義します<br/>
 * &lt;bean&gt;タグは更に以下の属性を指定します<br/>
 * 　id：JavaBeansの論理名称<BR>
 * 　type：JavaBeansの物理名称（パッケージ名を含めたクラス名）<br/>
 * 　singleton：シングルトンクラスの場合はtrueを指定してください<br/>
 *　<br>
 *　※ シングルトンクラスの場合、シングルトンクラスに以下に示すgetInstanceメソッド
 *　の記述が必要です。<br/>
 *　Stylistはオブジェクト生成時のこのメソッドを使ってオブジェクトを生成します<br/>
 *<div id="area">
 *<pre>
 * public class InitParamMap{
 *    private static InitParamMap instance;
 *
 *     public static synchronized InitParamMap getInstance(){
 *         if( instance == null ){
 *             instance = new InitParamMap();
 *         }
 *         return instance;
 *     }
 * }
 * </pre>
 * </div>
 * </p>
 * <p>
 * &lt;property&gt;：JavaBeansのプロパティ情報を定義します<br/>
 * &lt;property&gt;タグは更に以下の属性を指定します<br/>
 * 　name：属性の名前（JavaBeansではこの属性名に対応したアクセッサメソッドが必要です）<br/>
 * 　value：プロパティの初期値<br/>
 * 　ref：valueの代わりにrefを指定することもできます。<br/>
 * 　refは、Bean定義ファイル内に定義された別のJavaBeansを論理名称で指定することができます。<br/>
 *
 *
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2005/08/20
 */
public class BeanFactory {
    private static BeanFactory instance = new BeanFactory();
    private KeyLinkedList beanMap = new KeyLinkedList();
    private BeanConfigReader reader = new BeanConfigReader();
    private boolean initFlg = false;
    private AbstractLog log = SimpleLog.getInstance();
    
    public boolean isInit(){
        return initFlg;
    }
    
    /**
     * BeanFactoryインスタンスを取得します
     * @return BeanFactoryインスタンス
     */
    public static BeanFactory getInstance(){
        return instance;
    }
    
    /**
     * Bean定義ファイルをロードします
     * @param configName Bean定義ファイルのパス
     * @throws IOException
     */
    public void createForUrl(String configName) throws IOException{
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        URL diconfigUrl = parent.getResource(configName);
        try{
            if( diconfigUrl == null ){
                throw new IOException(configName+"へのパスが見つかりません");
            }
            String configFwName = diconfigUrl.getPath();
            configFwName = UrlUtil.replacePath(configFwName);
            configFwName = URLDecodeUtil.decode(configFwName, "utf8");
            create(configFwName);
        }catch(Exception ex){
            log.error(ex.getMessage());
            throw new IOException(ex.getMessage());
        }
    }
    
    /**
     * Bean定義ファイルをロードします
     * @param pathList Bean定義ファイルのパスリスト
     * @throws IOException
     */
    public void create(String[] pathList) throws IOException{
        String path = new String("");
        try{
            for(int i=0; i<pathList.length; i++ ){
                path = pathList[i];
                path = UrlUtil.replacePath(path);
                path = URLDecodeUtil.decode(path, "utf8");
                reader.create(path, beanMap);
            }
        }catch( IOException ioe ){
            log.error(ioe.getMessage());
            throw new IOException(ioe.getMessage() + "[" + path + "]");
        }
    }
    
    /**
     * Bean定義ファイルをロードします
     * @param path Bean定義ファイルのパス
     * @throws IOException
     */
    public void create(String path) throws IOException{
        reader.create(path, beanMap);
        initFlg = true;
        log.info("completed initialize config='" + path + "'");
    }
    
    /**
     * 指定されたBean論理名に紐づくシングルトンインスタンスを取得します
     * @param id Bean定義ファイルに関連するBean論理名
     * @return Bean論理名に紐づくインスタンスを取得します
     */
    public Object getInstance(String id){
        BeanInfoMapping beanInfo = (BeanInfoMapping)beanMap.get(id);
        Object obj = null;
        
        if( beanInfo == null ){
            String msg = getClass().getName() + " not found[" + id + "]";
            //log.error(msg);
            throw new BeanFactoryRuntimeException(msg);
        }
        if( beanInfo.getSingleton() ){
            //Singletonインスタンスを生成
            obj = BeanUtil.getInstance(beanInfo.getType());
        }
        return obj;
    }
    
    /**
     * 指定されたBean論理名に紐づくインスタンスを生成します
     * @param id Bean定義ファイルに関連するBean論理名
     * @return Bean論理名に紐づくインスタンスを取得します
     */
    public Object createInstance(String id){
        BeanInfoMapping beanInfo = (BeanInfoMapping)beanMap.get(id);
        Object obj = null;
        
        if( beanInfo == null ){
            String msg = getClass().getName() + " not found[" + id + "]";
            //log.error(msg);
            throw new BeanFactoryRuntimeException(msg);
        }
        obj = createInstance(beanInfo);
        return obj;
    }
    
    public boolean isDefined(String id){
        BeanInfoMapping beanInfo = (BeanInfoMapping)beanMap.get(id);
        if(beanInfo!=null){
            return true;
        }else{
            return false;
        }
    }
    
    /**
     * 指定されたBean論理名に紐づくインスタンスを生成します
     * @param beanInfo Bean定義ファイルに関連するBean情報
     * @return Bean論理名に紐づくインスタンスを取得します
     */
    public Object createInstance(BeanInfoMapping beanInfo){
        Object obj = null;
        
        //シングルトンの指定がある場合
        if( beanInfo.getSingleton() ){
            //Singletonインスタンスを生成
            obj = BeanUtil.getInstance(beanInfo.getType());
            
//add 2005/01/18 start
            //コンストラクタにString型の引数が1つある場合
        }else if( beanInfo.getValue() != null ){
            String[] argTypes = new String[1];
            String[] argValues = new String[1];
            argTypes[0] = String.class.getName();
            argValues[0] = beanInfo.getValue();
            
            obj = BeanUtil.createInstance(beanInfo.getType(), argTypes, argValues);
//add 2005/01/18 end
            
        }else{
            obj = BeanUtil.createInstance(beanInfo.getType());
        }
        
        //生成されたインスタンスに初期値をセットします
        KeyLinkedList pMap = beanInfo.getProperties();
        if( pMap == null ){
            return obj;
        }
        Set pKeySet = pMap.keySet();
        Iterator pIt = pKeySet.iterator();
        while(pIt.hasNext()){
            String pKey = (String)pIt.next();
            BeanPropertyInfo propertyInfo = (BeanPropertyInfo)pMap.get(pKey);
            String ref = propertyInfo.getRef();
            String collection = propertyInfo.getCollection();
            
            //Collection型のオブジェクトを設定する場合
            if( collection != null ){
                Object colObj = createCollection(propertyInfo);
                BeanUtil.setProperty(obj, propertyInfo.getName(), colObj);
                
                //他のオブジェクトを設定する場合
            }else if(ref != null){
                Object refObj = this.createInstance(ref);
                BeanUtil.setProperty(obj, propertyInfo.getName(), refObj);
                
                //指定された値で設定する場合
            }else if(propertyInfo.getValue() != null ){
                BeanUtil.setProperty(obj, propertyInfo.getName(), propertyInfo.getValue());
                
                //設定エラー
            }else{
                String msg = getClass().getName() + " not found property[" + pKey + "]";
                log.warn(msg);
            }
        }
        log.trace(beanInfo.getType() + " is created instance.");
        return obj;
    }
    
//add 2006/01/18 start
    /**
     * コレクションインスタンスを生成します
     * @param propertyInfo Bean定義情報
     * @return コレクションインスタンスを返します
     */
    protected Object createCollection(BeanPropertyInfo propertyInfo){
        KeyLinkedList attrList = propertyInfo.getAttrList();
//        Set set = attrList.keySet();
//        Iterator it = set.iterator();
        int size = attrList.size();
        String collection = propertyInfo.getCollection();
        
        //Map型コレクションの場合
        if( collection.equals("map")){
            HashMap map = new HashMap();
            
            for(int i=0; i<size; i++){
                BeanInfoMapping childBeanInfo  = (BeanInfoMapping)attrList.get(i);
                Object chldObj = createInstance(childBeanInfo);
                map.put(childBeanInfo.getId(), chldObj);
            }
            
            return map;
            
            //List型コレクションの場合
        }else if( collection.equals("list")){
            ArrayList lst = new ArrayList();
            
            for(int i=0; i<size; i++){
                BeanInfoMapping childBeanInfo  = (BeanInfoMapping)attrList.get(i);
                Object chldObj = createInstance(childBeanInfo);
                lst.add(i, chldObj);
            }
            
            return lst;
        }else{
            String msg = getClass().getName() + " not found collection kind.[" + collection + "]";
            log.warn(msg);
            return null;
        }
    }
//add 2006/01/18 end
}
