package org.maachang.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

/**
 * リフレクション処理.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class Reflect {
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param name 生成対象のオブジェクト名を設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( String name )
        throws Exception {
        return newObject( null,name ) ;
    }
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param loader 対象のクラスローダを設定します.
     * @param name 生成対象のオブジェクト名を設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( ClassLoader loader,String name )
        throws Exception {
        if( loader != null ) {
            return Class.forName( name ).newInstance() ;
        }
        return Class.forName( name,true,loader ).newInstance() ;
    }
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param name 生成対象のオブジェクト名を設定します.
     * @param params コンストラクタ引数を設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( String name,Object... params )
        throws Exception {
        return newObject( name,null,params ) ;
    }
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param loader 対象のクラスローダを設定します.
     * @param name 生成対象のオブジェクト名を設定します.
     * @param params コンストラクタ引数を設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( String name,ClassLoader loader,Object... params )
        throws Exception {
        if( params == null || params.length <= 0 ) {
            return Class.forName( name ).newInstance() ;
        }
        int len = params.length ;
        Class[] clss = new Class[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            clss[ i ] = params[ i ].getClass() ;
        }
        Class c = null ;
        if( loader == null ) {
            c = Class.forName( name ) ;
        }
        else {
            c = Class.forName( name,true,loader ) ;
        }
        Constructor cns = c.getConstructor( clss ) ;
        return cns.newInstance( params ) ;
    }
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param clzz 生成対象のオブジェクトクラスを設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( Class clzz )
        throws Exception {
        return clzz.newInstance() ;
    }
    
    /**
     * オブジェクトを生成.
     * <BR><BR>
     * 指定名のオブジェクトを生成します.
     * <BR>
     * @param clzz 生成対象のオブジェクトクラスを設定します.
     * @param params コンストラクタ引数を設定します.
     * @return Object 生成されたオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static final Object newObject( Class clzz,Object... params )
        throws Exception {
        if( params == null || params.length <= 0 ) {
            return clzz.newInstance() ;
        }
        int len = params.length ;
        Class[] clss = new Class[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            clss[ i ] = params[ i ].getClass() ;
        }
        Constructor cns = clzz.getConstructor( clss ) ;
        return cns.newInstance( params ) ;
    }
    
    /**
     * 指定名のメソッドを実行.
     * <BR><BR>
     * 指定名のメソッドを実行します.
     * <BR>
     * @param methodName 対象のメソッド名を設定します.
     * @param obj メソッド実行対象のオブジェクトを設定します.
     * @return Object 実行結果の内容が返されます.
     * @exception Exception 例外.
     */
    public static final Object executionMethod( String methodName,Object obj )
        throws Exception {
        if( methodName == null || ( methodName = methodName.trim() ).length() <= 0 ||
            obj == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        //Method md = obj.getClass().getDeclaredMethod( methodName ) ;
        Method md = obj.getClass().getMethod( methodName ) ;
        return md.invoke( obj ) ;
    }
    /**
     * 指定名のメソッドを実行.
     * <BR><BR>
     * 指定名のメソッドを実行します.
     * <BR>
     * @param methodName 対象のメソッド名を設定します.
     * @param obj メソッド実行対象のオブジェクトを設定します.
     * @param params 実行パラメータを設定します.
     * @return Object 実行結果の内容が返されます.
     * @exception Exception 例外.
     */
    public static final Object executionMethod( String methodName,Object obj,Object... params )
        throws Exception {
        if( methodName == null || ( methodName = methodName.trim() ).length() <= 0 ||
            obj == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        Class cls = obj.getClass() ;
        if( params == null || params.length <= 0 ) {
            //Method md = cls.getDeclaredMethod( methodName ) ;
            Method md = cls.getMethod( methodName ) ;
            return md.invoke( obj ) ;
        }
        Method target = getSearchMethod( methodName,obj,params ) ;
        if( target == null ) {
            StringBuilder buf = new StringBuilder() ;
            buf.append( "指定メソッド " ).append( methodName ).append( "(" ) ;
            int len = params.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    buf.append( "," ) ;
                }
                if( params[ i ] == null ) {
                    buf.append( "null" ) ;
                }
                else {
                    buf.append( params[ i ].getClass().getName() ) ;
                }
            }
            buf.append( ") は存在しません" ) ;
            throw new NoSuchMethodException( buf.toString() ) ;
        }
        //Method md = cls.getDeclaredMethod( methodName,( Class[] )target.getParameterTypes() ) ;
        Method md = cls.getMethod( methodName,( Class[] )target.getParameterTypes() ) ;
        return md.invoke( obj,params ) ;
    }
    
    /**
     * 指定名のメソッドが存在するか確認.
     * <BR><BR>
     * 指定名のメソッドが存在するか確認します.
     * <BR>
     * @param methodName 対象のメソッド名を設定します.
     * @param obj メソッド実行対象のオブジェクトを設定します.
     * @return boolean [true]の場合存在します.
     * @exception Exception 例外.
     */
    public static final boolean isMethod( String methodName,Object obj )
        throws Exception {
        try {
            if( getSearchMethod( methodName,obj,null ) != null ) {
                return true ;
            }
        } catch( Exception e ) {
        }
        return false ;
    }
    
    /**
     * 指定名のメソッドが存在するか確認.
     * <BR><BR>
     * 指定名のメソッドが存在するか確認します.
     * <BR>
     * @param methodName 対象のメソッド名を設定します.
     * @param obj メソッド実行対象のオブジェクトを設定します.
     * @param params 実行パラメータを設定します.
     * @return boolean [true]の場合存在します.
     * @exception Exception 例外.
     */
    public static final boolean isMethod( String methodName,Object obj,Object... params )
        throws Exception {
        try {
            if( getSearchMethod( methodName,obj,params ) != null ) {
                return true ;
            }
        } catch( Exception e ) {
        }
        return false ;
    }
    
    /**
     * 有効メソッド名一覧を取得.
     * <BR><BR>
     * 指定オブジェクト内の有効メソッド一覧を取得します.
     * <BR>
     * @param obj 対象のオブジェクトを設定します.
     * @return ArrayList<String> 有効メソッド一覧が返されます.<BR>
     *                           また、返されるメソッド一覧は、Publicな条件のみです.
     * @exception Exception 例外.
     */
    public static final ArrayList<String> getMethodNameByList( Object obj )
        throws Exception {
        if( obj == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        ArrayList<String> ret = new ArrayList<String>() ;
        Class cls = obj.getClass() ;
        Method[] ms = cls.getMethods() ;
        if( ms != null && ms.length > 0 ) {
            int len = ms.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( ms[ i ] != null ) {
                    ret.add( ms[ i ].getName() ) ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * 有効メソッド名一覧を取得.
     * <BR><BR>
     * 指定オブジェクト内の有効メソッド一覧を取得します.
     * <BR>
     * @param obj 対象のオブジェクトを設定します.
     * @return HashSet<String> 有効メソッド一覧が返されます.<BR>
     *                         また、返されるメソッド一覧は、Publicな条件のみです.
     * @exception Exception 例外.
     */
    public static final HashSet<String> getMethodNameByMap( Object obj )
        throws Exception {
        if( obj == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        HashSet<String> ret = new HashSet<String>() ;
        Class cls = obj.getClass() ;
        Method[] ms = cls.getMethods() ;
        if( ms != null && ms.length > 0 ) {
            int len = ms.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( ms[ i ] != null ) {
                    ret.add( ms[ i ].getName() ) ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * 有効メソッドオブジェクトを取得.
     * <BR><BR>
     * 指定オブジェクト内の有効メソッドオブジェクト一覧を取得します.
     * <BR>
     * @param obj 対象のオブジェクトを設定します.
     * @return ArrayList<Method> 有効メソッドオブジェクト一覧が返されます.<BR>
     *                           また、返されるメソッド一覧は、Publicな条件のみです.
     * @exception Exception 例外.
     */
    public static final ArrayList<Method> getMethodObjectList( Object obj )
        throws Exception {
        if( obj == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        ArrayList<Method> ret = new ArrayList<Method>() ;
        Class cls = obj.getClass() ;
        Method[] ms = cls.getMethods() ;
        if( ms != null && ms.length > 0 ) {
            int len = ms.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( ms[ i ] != null ) {
                    ret.add( ms[ i ] ) ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * 指定クラス同士が一致しているかチェック.
     * <BR><BR>
     * 指定クラス同士が一致しているかチェックします.
     * <BR>
     * @param src チェック対象のクラスを設定します.
     * @param dest チェック対象のクラスを設定します.
     * @return boolean [true]の場合は一致しています.
     */
    public static final boolean equalsClass( Class src,Class dest ) {
        if( src == null || dest == null ) {
            return false ;
        }
        if( src.isInterface() ) {
            if( dest.isInterface() ) {
                return src.equals( dest ) ;
            }
            HashSet<String> destSet = getInterfaceAll( null,dest ) ;
            return destSet.contains( src.getName() ) ;
        }
        else {
            if( dest.isInterface() ) {
                HashSet<String> srcSet = getInterfaceAll( null,src ) ;
                return srcSet.contains( dest.getName() ) ;
            }
            HashSet srcSet = getClassAll( src ) ;
            HashSet destSet = getClassAll( dest ) ;
            if( srcSet.size() <= 0 && destSet.size() <= 0 ) {
                return false ;
            }
            int len = srcSet.size() ;
            Object[] objs = srcSet.toArray() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( destSet.contains( ( String )objs[ i ] ) ) {
                    return true ;
                }
            }
        }
        return false ;
    }
    
    /**
     * Getter情報群を取得.
     * <BR><BR>
     * 指定オブジェクトのGetter情報を取得します.
     * <BR>
     * @param object 対象のオブジェクトを設定します.
     * @return HashMap<String,Object> 変換された文字列が返されます.
     * @exception Exception 例外.
     */
    public static final HashMap<String,Object> getter( Object object )
        throws Exception {
        if( object == null ) {
            return new HashMap<String,Object>() ;
        }
        ArrayList<Method> mds = Reflect.getMethodObjectList( object ) ;
        if( mds != null && mds.size() > 0 ) {
            HashMap<String,Object> ret = new HashMap<String,Object>() ;
            int len = mds.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                Method md = mds.get( i ) ;
                String name = md.getName() ;
                if( ( md.getParameterTypes() == null || md.getParameterTypes().length <= 0 ) &&
                    ( name.startsWith( "get" ) || name.startsWith( "is" ) ) &&
                    name.equals( "getClass" ) == false ) {
                    String key = null ;
                    if( name.startsWith( "get" ) ) {
                        key = name.substring( "get".length(),name.length() ) ;
                    }
                    else {
                        key = name.substring( "is".length(),name.length() ) ;
                    }
                    key = key.substring( 0,1 ).toLowerCase() + key.substring( 1,key.length() ) ;
                    Object val = Reflect.executionMethod( name,object ) ;
                    ret.put( key,val ) ;
                }
            }
            return ret ;
        }
        return new HashMap<String,Object>() ;
    }
    
    /**
     * 指定オブジェクトの内容を文字列に変換.
     * <BR><BR>
     * 指定オブジェクトの内容を文字列に変換します.
     * <BR>
     * @param object 対象のオブジェクトを設定します.
     * @return String 変換された文字列が返されます.
     * @exception Exception 例外.
     */
    public static final String toString( Object object )
        throws Exception {
        if( object == null ) {
            return "" ;
        }
        HashMap<String,Object> map = getter( object ) ;
        int size = map.size() ;
        if( size > 0 ) {
            Object[] objs = map.keySet().toArray() ;
            StringBuilder buf = new StringBuilder( 1024 ) ;
            for( int i = 0 ; i < size ; i ++ ) {
                Object val = map.get( ( String )objs[ i ] ) ;
                if( val == null ) {
                    val = "" ;
                }
                buf.append( " " ).append( objs[ i ] ).append( ":\"" ).append( val ).append( "\"" ) ;
            }
            return buf.toString() ;
        }
        return "" ;
    }
    
    /**
     * 対象のクラス一覧を取得.
     */
    private static final HashSet<String> getClassAll( Class cls ) {
        if( cls == null ) {
            return new HashSet<String>() ;
        }
        HashSet<String> ret = new HashSet<String>() ;
        Class tmp = cls ;
        for( int i = 0 ;; i ++ ) {
            if( tmp == null || Object.class.equals( tmp ) ) {
                break ;
            }
            ret.add( tmp.getName() ) ;
            tmp = tmp.getSuperclass() ;
        }
        return ret ;
    }
    
    /**
     * 対象のクラス/インターフェイス一覧を取得.
     */
    private static final HashSet<String> getInterfaceAll( HashSet<String> out,Class cls ) {
        if( cls == null ) {
            if( out != null ) {
                return out ;
            }
            return new HashSet<String>() ;
        }
        HashSet<String> ret = ( out == null ) ? new HashSet<String>() : out ;
        Class[] ifc = cls.getInterfaces() ;
        int len = ifc.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            ret.add( ifc[ i ].getName() ) ;
        }
        return ret ;
    }
    
    /**
     * 指定メソッドを検索.
     */
    private static final Method getSearchMethod( String methodName,Object object,Object[] args )
        throws Exception {
        Class[] argsCls = null ;
        if( args != null && args.length > 0 ) {
            int len = args.length ;
            argsCls = new Class[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                argsCls[ i ] = args[ i ].getClass() ;
            }
        }
        return getSearchMethod( methodName,object,argsCls ) ;
    }
    
    /**
     * 指定メソッドを検索.
     */
    private static final Method getSearchMethod( String methodName,Object object,Class[] args )
        throws Exception {
        if( methodName != null && object != null ) {
            int argsLen = ( args == null ) ? 0 : args.length ;
            Method[] list = object.getClass().getMethods() ;
            if( list != null && list.length > 0 ) {
                int len = list.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( methodName.equals( list[ i ].getName() ) ) {
                        Class[] params = list[ i ].getParameterTypes() ;
                        if( argsLen == 0 && ( params == null || params.length == 0 ) ) {
                            return list[ i ] ;
                        }
                        else if( argsLen == params.length ) {
                            boolean equalsFlag = true ;
                            int lenJ = params.length ;
                            for( int j = 0 ; j < lenJ ; j ++ ) {
                                if( args[ j ] != null ) {
                                    if( equalsClass( args[ j ],params[ j ] ) == false ) {
                                        equalsFlag = false ;
                                        break ;
                                    }
                                }
                            }
                            if( equalsFlag == true ) {
                                return list[ i ] ;
                            }
                        }
                    }
                }
            }
        }
        return null ;
    }
}

