package org.itscool.commons.dao;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import org.itscool.commons.bean.BeanFactory;

import org.itscool.commons.bean.BeanUtil;
import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.util.StringUtil;

/**
 * RecordsMp作成クラス<P>
 *
 * JavaBeansのアノテーションを読み取り、テーブルマッピングクラス（RecordMap)
 * を生成するユーティリティクラスです<br>
 * JavaBeansには以下のクラス情報が含まれている必要があります<BR>
 * public static final TABLEで定義されたテーブル名（省略された場合は、クラス名
 * がテーブル名となります）<BR>
 * public static final COLUM_メンバ名で定義されたフィールド名（省略された場合
 * は,テーブルにマッピングされません）<BR>
 * メンバ名は、JavaBeansで定義されているプロパティ名と同じ名前を使用する必要が
 * あります。
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2004/11/1
 */
public class RecordMapFactory {
    /**
     * COLUMマッピング定義宣言
     */
    public static final String COLUM = "COLUM_";
    
    public static final String PK_COLUM = "PK_COLUM_";
    
    public static final String NO_MAP_COLUM = "NO_MAP_COLUM_";
    
    protected static AbstractLog log;
    protected static boolean initFlag = false;
    /**
     * TABLEマッピング定義宣言
     */
    public static final String TABLE = "TABLE";
    
    public static void init(){
        if( initFlag == false){
            initFlag = true;
            BeanFactory factory = BeanFactory.getInstance();
            try{
                log = (AbstractLog)factory.getInstance("Log");
                if(!log.isInit()){
                    log = (AbstractLog)factory.createInstance("Log");
                }
            }catch(Exception ioe){
            }
        }
    }
    
    /**
     * JavaBeansのクラス情報を元にRecordMapインスタンスを生成します
     * @param className 永続化対象となるJavaBeansのクラス情報
     * @return RecordMapインスタンスを取得します
     */
    public static RecordMap createRecordMap( String className){
        try{
            Class entity = Class.forName(className);
            return create( entity );
        }catch(ClassNotFoundException e){
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * JavaBeansのクラス情報を元にRecordMapインスタンスを生成します
     * @param entity 永続化対象となるJavaBeansのクラス情報
     * @return RecordMapインスタンスを取得します
     */
    public static RecordMap createRecordMap( Class entity){
        init();
        try{
            return create( entity );
        }catch(Exception e){
            log.error(e.getMessage());
            throw new DaoRuntimeException(e.getMessage());
        }
    }
    
    private static synchronized RecordMap create( Class entity ){
        Object obj = BeanUtil.createInstance(entity.getName());
        String tableName = getTableName(entity, obj, TABLE);
        RecordMap recordMap = new RecordMap(entity.getName(), tableName);
        
        recordMap = create( entity, obj, recordMap );
        //-----------------------------------------------------------
        //"COLUM_メンバ名"に対応するプロパティ名をJavaBeansクラス情報から
        //取得します
        //-----------------------------------------------------------
        HashMap records = recordMap.getFields();
        Set keySet = records.keySet();
        Iterator iterator = keySet.iterator();
        while( iterator.hasNext() ){
            try{
                Object key = iterator.next();
                FieldMap fieldMap = (FieldMap)records.get(key);
                //modify 2005/09/20 start
                //Field field = entity.getDeclaredField(fieldMap.getPropertyName());
                Field field = BeanUtil.getField(entity, fieldMap.getPropertyName());
                if( field == null ){
                    continue;
                }
                //modify 2005/09/20 end
                String type = field.getType().getName();
                
                fieldMap.setType(type);
            }catch(Exception e){
                log.error(e);
            }
        }
        return recordMap;
    }
    
    private static RecordMap create( Class entity, Object obj, RecordMap recordMap ){
        //親クラスのメンバから走査
        Class superClazz = entity.getSuperclass();
        if( superClazz != null ){
            recordMap = create( superClazz, obj, recordMap );
        }
        //-------------------------------------------------------
        //RecordMapが保有する各FieldMapインスタンスに対して
        //対応するカラム情報をセットしていきます。
        //カラムをセットする変数は"COLUM_プロパティ名"である必要があります
        //-------------------------------------------------------
        Field[] fields = entity.getDeclaredFields();
        for( int i=0; i<fields.length; i++){
            //JavaBeansに定義されたフィールド情報を取得します
            Field field = fields[i];
            //フィールドのModifier情報を取得します
            int modifier = field.getModifiers();
            //FieldMapインスタンスを作成します
            String staticName = field.getName();
            try{
                int index = staticName.indexOf(COLUM);
                int pkIndex = staticName.indexOf(PK_COLUM);
                if( modifier == (Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL)
                && pkIndex == 0 ){
                    FieldMap fieldMap = new FieldMap();
                    String fieldName = (String)field.get(obj);
         //add 2007/04/13 kano start
                    fieldName = fieldName.toUpperCase();
         //add 2007/04/13 kano end
                    String propertyName = staticName.substring((PK_COLUM).length(), staticName.length());
                    fieldMap.setColumName(fieldName);
                    fieldMap.setPropertyName(propertyName);
                    fieldMap.setRecordMap(recordMap);
                    fieldMap.setPrimaryKey(true);
                    recordMap.addField(fieldMap);
                }else if( modifier == (Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL)
                && index == 0 ){
                    FieldMap fieldMap = new FieldMap();
                    String fieldName = (String)field.get(obj);
         //add 2007/04/13 kano start
                    fieldName = fieldName.toUpperCase();
         //add 2007/04/13 kano end
                    String propertyName = staticName.substring((COLUM).length(), staticName.length());
                    fieldMap.setColumName(fieldName);
                    fieldMap.setPropertyName(propertyName);
                    fieldMap.setRecordMap(recordMap);
                    fieldMap.setPrimaryKey(false);
                    recordMap.addField(fieldMap);
                    
                }
                //プロパティからFieldMapを生成(COLUM定義省略対応）
                else if( modifier == Modifier.PRIVATE || modifier == Modifier.PROTECTED ){
                    String fieldName = StringUtil.cnvUpperNameToUnderScoreName(staticName);
                    fieldName = fieldName.toUpperCase();
                    Field check1=null;
                    Field check2=null;
                    Field check3=null;
                    try{
                        check1 = entity.getField(COLUM+staticName);
                    }catch(Exception e){}
                    try{
                        check2 = entity.getField(PK_COLUM+staticName);
                    }catch(Exception e){}
                    try{
                        check3 = entity.getField(NO_MAP_COLUM+staticName);
                    }catch(Exception e){}
                    
                    if( recordMap.getFieldByFieldName(fieldName) == null &&
                        check1 == null && check2 == null && check3 == null)
                        {
                        Class type = field.getType();
                        if( !BeanUtil.isJavaType(type) ){   //プリミティブ及び基本データ型（String,Date)のみを対象
                            //オブジェクト型、配列型は対象外
                            continue;
                        }
                        FieldMap fieldMap = new FieldMap();
                        fieldName = fieldName.toUpperCase();
                        String propertyName = staticName;
                        fieldMap.setColumName(fieldName);
                        fieldMap.setPropertyName(propertyName);
                        fieldMap.setRecordMap(recordMap);
                        if(propertyName.equals("id")){
                            fieldMap.setPrimaryKey(true);
                        }else{    
                            fieldMap.setPrimaryKey(false);
                        }
                        recordMap.addField(fieldMap);
                    }
                }
            }catch(IllegalAccessException e){
                String err = "[" + obj.getClass().getName() + ":" + staticName + "]" +
                    "create() failed(" + RecordMapFactory.class.getName() + ") (" +
                    e.getMessage() + ")";
                throw new DaoRuntimeException(err);
            }catch(NullPointerException e){
                String err = "[" + obj.getClass().getName() + ":" + staticName + "]" +
                    "create() failed(" + RecordMapFactory.class.getName() + ") (" +
                    e.getMessage() + ")";
                throw new DaoRuntimeException(err);
            }
            
        }
        return recordMap;
    }
    /**
     * JavaBeansクラス定義からテーブル名を取得します
     * @param entity JavaBeansクラス定義情報
     * @param obj JavaBeansから生成された仮のインスタンス
     * @return TABEL名を取得します
     */
    protected static String getTableName(Class entity, Object obj, String tableHeaderName){
        String tableName = entity.getName();
        try{
            //modify 2005/09/20 start
            //Field field = entity.getField(tableHeaderName);
            Field field = BeanUtil.getField(entity, tableHeaderName);
            //modify 2005/09/20 end
            if( field != null ){
                tableName = (String)field.get(obj);
            }else{
                //TABLE_NAMEが無い場合はクラス名をセットする
                int index = tableName.lastIndexOf(".");
                int length = tableName.length();
                if( index < 0 ) return tableName;

                tableName = tableName.substring(index+1, length);
                tableName = StringUtil.cnvUpperNameToUnderScoreName(tableName);
                tableName = tableName.toUpperCase();
            }
        }catch(Exception e){
             throw new DaoRuntimeException(e.getMessage());
        }
        return tableName;
    }
    
    /**
     * JavaBeansクラス定義からプロパティ名で指定したプロパティの型を取得します
     * @param entity JavaBeansクラス定義情報
     * @param propertyName プロパティ名
     * @return TABEL名を取得します
     */
    protected static String getPropertyType(Class entity, String propertyName){
        String propertyType = null;
        try{
            //Field field = entity.getDeclaredField(propertyName);
            Field field = BeanUtil.getField(entity, propertyName);
            if( field != null ){
                propertyType = field.getType().getName();
            }
        }catch(Exception e){}
        return propertyType;
    }
    
    public static void testParseClass(Class clazz) throws Exception{
        System.out.println("---------------------------");
        RecordMap record = RecordMapFactory.createRecordMap(clazz);
        System.out.println(record.getTableName() + "," + record.getClassName());
        HashMap fields = record.getFields();
        Set keySet = fields.keySet();
        Iterator iterator = keySet.iterator();
        while( iterator.hasNext() ){
            Object key = iterator.next();
            FieldMap fieldMap = (FieldMap)fields.get(key);
            System.out.println(fieldMap.getColumName()+","+fieldMap.getPropertyName()+","+fieldMap.getType());
        }
    }
    
}

