/**
 * Copyright (C) 2006-2007  NTT DATA CORPORATION
 * 
 * Version: 1.0.0 2007/04/01
 *  
 */
package net.cellcomputing.himawari.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;


/**
 * 
 * privateなメソッド・フィールド・コンストラクタにアクセスするためのヘルパクラス。
 * 
 * @author NTT DATA Corporation
 */
public final class AccessSupplier {
	
	/**
	 * 
	 * 任意の属性値を取り出す。<br>
	 * Objectで返すので、適切なクラスにキャストすることが必要。<br>
	 * 静的クラスから静的属性値を取得したい場合は、静的クラスのClassを渡せばよい。
	 * 
	 * @param instance	取り出したい属性値を持つクラスのインスタンス
	 * @param fieldName	属性名
	 * @return	属性値（適切なキャストが必要）
	 */
	public static Object getField( Object instance, String fieldName ) {
		
		return getField( instance, fieldName, 0 );
	}
	
	/**
	 * 
	 * 任意の属性値を取り出す。<br>
	 * rankにインスタンスと属性を保持するクラスとの階層差を与えることで、スーパークラスの属性であっても取得できる。
	 * 
	 * @param instance	取り出したい属性値を持つクラスのインスタンス
	 * @param fieldName	属性名
	 * @param rank	インスタンスと実際にその属性を保持するクラスとのクラス階層差。
	 * 				０ならインスタンス自体が保持していることを、１なら１つ上のスーパークラスが保持していることを示す。
	 * @return	属性値（適切なキャストが必要）
	 */
	private static Object getField( Object instance, String fieldName, int rank ) {
		
		Class cls;
		if( instance instanceof Class )	cls = (Class)instance;		//静的クラスである場合
		else								cls = instance.getClass();	//通常のインスタンスである場合
		
		try {
			
			//階層を遡る
			for( int depth = rank; depth>0; depth-- ){
				cls = cls.getSuperclass();
			}
			
			//属性値の取得
			Field field = cls.getDeclaredField(fieldName);
			field.setAccessible(true);
			
			return field.get(instance);
			
		} catch (NoSuchFieldException ne) {
			
			//親クラスがある場合はそちらを検索
			if( cls.getSuperclass() != null ){
				return getField(instance,fieldName, rank+1 );
			}
			
			HimawariLogger.outputException( ne );
			return null;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
		
	}
	
	/**
	 * 
	 * 任意の属性値に対して任意の値を設定する。<br>
	 * 静的クラスの静的属性値を設定したい場合は、静的クラスのClassを渡せばよい。
	 * 
	 * @param instance	設定したい属性値を持つクラスのインスタンス
	 * @param fieldName	属性名
	 * @param value	設定したい値
	 */
	public static void setField( Object instance, String fieldName, Object value ) {
		
		setField( instance, fieldName, value, 0 );
	}
	
	/**
	 * 
	 * 任意の属性値に対して任意の値を設定する。<br>
	 * rankにインスタンスと属性を保持するクラスとの階層差を与えることで、スーパークラスの属性であっても設定できる。
	 * 
	 * @param instance	設定したい属性値を持つクラスのインスタンス
	 * @param fieldName	属性名
	 * @param value 	設定したい値
	 * @param rank	インスタンスと実際にその属性を保持するクラスとのクラス階層差。
	 * 				０ならインスタンス自体が保持していることを、１なら１つ上のスーパークラスが保持していることを示す。
	 */
	private static void setField( Object instance, String fieldName, Object value, int rank ) {
		
		Class cls;
		if( instance instanceof Class )	cls = (Class)instance;		//静的クラスである場合
		else								cls = instance.getClass();	//通常のインスタンスである場合
		
		try {
			
			//階層を遡る
			for( ; rank>0; rank-- ){
				cls = cls.getSuperclass();
			}
			
			//属性値の取得
			Field field = cls.getDeclaredField(fieldName);
			field.setAccessible(true);
			
			field.set( instance, value );
			return;
			
		} catch (NoSuchFieldException ne) {
			
			//親クラスがある場合はそちらを検索
			if( cls.getSuperclass() != null ){
				setField(instance,fieldName,value, rank+1 );
				return;
			}
			
			HimawariLogger.outputException( ne );
			return;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return;
		}
		
	}
	
	/**
	 * 
	 * 任意のメソッドを取り出す。<br>
	 * 静的クラスから静的メソッドを取得したい場合は、静的クラスのClassを渡せばよい。
	 * 
	 * @param instance	取り出したいメソッドを持つクラスのインスタンス
	 * @param methodName	メソッド名
	 * @param parameterTypes	メソッドの引数
	 * @return	Methodオブジェクト
	 */
	public static Method getMethod( Object instance, String methodName, Class ... parameterTypes ) {
		try {
			
			Class cls;
			if( instance instanceof Class )	cls = (Class)instance;
			else								cls = instance.getClass();
			
			
			//引数クラスリストの作成
			Class tmpCls;
			for( int i=0; i<parameterTypes.length; i++ ){
				
				//引数クラスを取得
				tmpCls = parameterTypes[i];

				//プリミティブのラッパならばプリミティブに戻す（Booleanも含む）
				if( tmpCls.getSuperclass() == Number.class || tmpCls == Boolean.class ){
					tmpCls = (Class)tmpCls.getField("TYPE").get(tmpCls);
				}
				
				parameterTypes[i] = tmpCls;
			}
			
			//メソッドの取得
			Method method = cls.getDeclaredMethod(methodName, parameterTypes);
			method.setAccessible(true);
			
			return method;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
	}
	
	/**
	 * 
	 * 任意のメソッドを実行し、その結果を取得する。<br>
	 * Objectで返すので、適切なクラスにキャストすることが必要。<br>
	 * 静的クラスの静的メソッドを実行したい場合は、静的クラスのClassを渡せばよい。
	 * 
	 * @param instance	実行したいメソッドを持つクラスのインスタンス
	 * @param methodName	メソッド名
	 * @param args	メソッド実行時に必要なオブジェクトのインスタンス
	 * @return	実行結果（適切なキャストが必要）
	 */
	public static Object invokeMethod( Object instance, String methodName, Object ... args ){
		try {
			
			Class[] parameterTypes = new Class[args.length];
			for( int i=0; i<parameterTypes.length; i++ ){
				parameterTypes[i] = args[i].getClass();
			}
			
			Method method = getMethod( instance, methodName, parameterTypes );
			
			Object ret = method.invoke( instance, args );
			
			return ret;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
	}
	
	/**
	 * 
	 * 任意のコンストラクタを取り出す。
	 * 
	 * @param cls	取り出したいコンストラクタを持つクラス
	 * @param parameterTypes	コンストラクタの引数
	 * @return	Constructorオブジェクト
	 */
	public static Constructor getConstructor( Class cls, Class ... parameterTypes ) {
		try {
			
			Constructor constr = cls.getDeclaredConstructor(parameterTypes);
			constr.setAccessible(true);
			
			return constr;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
	}
	
	/**
	 * 
	 * 任意のコンストラクタでインスタンスを生成する。
	 * 
	 */
	public static <T> T getNewInstance( Class<T> cls, Object ... args ){
		try {
			
			//引数クラスリストの作成
			Class[] argsClss = new Class[args.length];
			
			Class tmpCls;
			for( int i=0; i<argsClss.length; i++ ){
				
				//引数クラスを取得
				tmpCls = args[i].getClass();

				//プリミティブのラッパならばプリミティブに戻す（Booleanも含む）
				if( tmpCls.getSuperclass() == Number.class || tmpCls == Boolean.class ){
					tmpCls = (Class)tmpCls.getField("TYPE").get(tmpCls);
				}
				
				argsClss[i] = tmpCls;
			}
			
			//コンストラクタを取得	
			Constructor<T> cnst = cls.getConstructor( argsClss );
			
			//そのコンストラクタでインスタンス化
			return cnst.newInstance( args );
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
	}
	
	/**
	 * 
	 * 任意の内部クラスを取り出す。
	 * 
	 * @param instance	取り出したい内部クラスを持つクラスのインスタンス
	 * @param clsname	内部クラス名
	 * @return	内部クラスのClassオブジェクト
	 */
	public static Class getInnerClass( Object instance, String clsname ){
		try {
			
			Class cls;
			if( instance instanceof Class )	cls = (Class)instance;
			else								cls = instance.getClass();
			
			Class[] classes = cls.getDeclaredClasses();
			
			//内部クラスが定義されないときはnullを返す
			if( classes.length == 0 ){
				return null;
			}
			
			//内部クラスの中から指定名称のクラスを探す
			for( Class decClass : classes ){
				
				if( decClass.getSimpleName().equals( clsname ) ){
					cls = decClass;
				}
				
			}//end of for
			
			return cls;
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
		
	}
	
	/**
	 * 
	 * 任意の内部クラスのインスタンスを取り出す。
	 * 
	 * @param instance	取り出したい内部クラスを持つクラスのインスタンス
	 * @param clsname	内部クラス名
	 * @return	内部クラスのインスタンス（適切なキャストが必要）
	 */
	public static Object getInnerClassInstance( Object instance, String clsname ){
		try {
			
			//クラス定義を取得
			Class innerClass = getInnerClass( instance, clsname );
			
			if( innerClass==null ){
				return null;
			}
			
			//内部クラスのインスタンス
			Object ins;
			
			//内部クラスがstaticかどうか。
			if( Modifier.isStatic( innerClass.getModifiers() ) ){
				
				Constructor constr = getConstructor( innerClass );
				ins = constr.newInstance();
			
			}
			else{
				
				Constructor constr = getConstructor( innerClass, instance.getClass() );
				ins = constr.newInstance( instance );
				
			}
			
			return ins;
			
			
		} catch (Exception e) {
			HimawariLogger.outputException( e );
			return null;
		}
	}
}