package org.arefgard.icerya.container.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

/**
 * リフレクション操作を簡単に行うためのユーティリティ。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class ReflectionUtil {
	
	private static final String PREFIX_GET = "get";
	private static final String PREFIX_SET = "set";
	
	/**
	 * クラスの完全修飾名からオブジェクトを生成する。
	 * 
	 * @param clazz クラス
	 * @return 生成したオブジェクト
	 * @throws ClassNotFoundException クラスが見つからない場合
	 */
	public static Object generate(Class clazz) throws ClassNotFoundException {
		try {
			if(clazz.getName().equals("byte") || clazz.getName().equals("java.lang.Byte")) {
				return new Byte((byte)0);
			}else if(clazz.getName().equals("short") || clazz.getName().equals("java.lang.Short")) {
				return new Short((short)0);
			}else if(clazz.getName().equals("int") || clazz.getName().equals("java.lang.Integer")) {
				return new Integer(0);
			}else if(clazz.getName().equals("long") || clazz.getName().equals("java.lang.Long")) {
				return new Long(0L);
			}else if(clazz.getName().equals("float") || clazz.getName().equals("java.lang.Float")) {
				return new Float(0.0F);
			}else if(clazz.getName().equals("double") || clazz.getName().equals("java.lang.Double")) {
				return new Double(0.0D);
			}else if(clazz.getName().equals("char") || clazz.getName().equals("java.lang.Character")) {
				return new Character((char)0);
			}else {
				return clazz.newInstance();
			}
		}catch(Exception e) {
			throw new ClassNotFoundException("Class[" + clazz.getName() + "] is not found.", e);
		}
	}
	
	public static Object generate(Class clazz, Object ... args) throws ClassNotFoundException {
		try {
			Class[] types = new Class[args.length];
			
			for(int i = 0; i < args.length; i++) {
				types[i] = args[i].getClass();
			}
			
			Constructor constructor = clazz.getConstructor(types);
			return constructor.newInstance(args);
		}catch(Exception e) {
			throw new ClassNotFoundException("Class[" + clazz.getName() + "] is not found.", e);
		}
	}
	
	/**
	 * 指定したプロパティに値を設定する。
	 * 
	 * @param obj 対象オブジェクト
	 * @param property プロパティ名
	 * @param value 設定する値
	 * @throws Exception プロパティの設定に失敗した場合
	 */
	public static void setProperty(Object obj, String property, Object value) throws Exception {
		Class clazz = obj.getClass();
		Class superClazz = null;
		// setterを使って設定
		StringBuffer methodName = new StringBuffer(PREFIX_SET);
		methodName.append(property.substring(0, 1).toUpperCase());
		methodName.append(property.substring(1));
		Method method = null;
		Method[] methods = clazz.getDeclaredMethods();
		for(int i = 0, n = methods.length;i < n; i++) {
			if(methods[i].getName().equals(methodName.toString())) {
				method = methods[i];
				break;
			}
		}
		// スーパクラスから対象のメソッドを探す
		if(method == null) {
			superClazz = clazz.getSuperclass();
			while(!(superClazz.equals(Object.class))) {
				methods = superClazz.getDeclaredMethods();
				for(int i = 0, n = methods.length;i < n; i++) {
					if(methods[i].getName().equals(methodName.toString())) {
						method = methods[i];
						break;
					}
				}
				superClazz = superClazz.getSuperclass();
			}
		}
		if(method != null) {
			method.invoke(obj, new Object[] { value });
		}else {
			// フィールドに直接設定
			Field field = null;
			try {
				field = clazz.getDeclaredField(property);
				field.setAccessible(true);
				field.set(obj, value);
			}catch(NoSuchFieldException e1) {
				// スーパクラスから対象のフィールドを探す
				superClazz = clazz.getSuperclass();
				while(!(superClazz instanceof Object)) {
					try {
						field = superClazz.getDeclaredField(property);
						field.setAccessible(true);
						field.set(obj, value);
					}catch(NoSuchFieldException e2) {
						// ignore
					}
					superClazz = superClazz.getSuperclass();
				}
			}
		}
	}
	
	/**
	 * 指定したプロパティから値を取得する。
	 * @param obj 対象オブジェクト
	 * @param property プロパティ名
	 * @return 取得した値
	 * @throws Exception プロパティの取得に失敗した場合
	 */
	public static Object getProperty(Object obj, String property) throws Exception {
		Class clazz = obj.getClass();
		Class superClazz = null;
		Object result = null;
		StringBuffer methodName = new StringBuffer(PREFIX_GET);
		methodName.append(property.substring(0, 1).toUpperCase());
		methodName.append(property.substring(1));
		Method method = null;
		Method[] methods = clazz.getDeclaredMethods();
		for(int i = 0, n = methods.length;i < n; i++) {
			if(methods[i].getName().equals(methodName.toString())) {
				method = methods[i];
				break;
			}
		}
		// スーパクラスから対象のメソッドを探す
		if(method == null) {
			superClazz = clazz.getSuperclass();
			while(!(superClazz instanceof Object)) {
				methods = superClazz.getDeclaredMethods();
				for(int i = 0, n = methods.length;i < n; i++) {
					if(methods[i].getName().equals(methodName.toString())) {
						method = methods[i];
						break;
					}
				}
				superClazz = superClazz.getSuperclass();
			}
		}
		if(method != null) {
			result = method.invoke(obj, (Object[])null);
		}else {
			// フィールドから直接取得
			Field field = null;
			try {
				field = clazz.getDeclaredField(property);
				field.setAccessible(true);
				result = field.get(obj);
			}catch(NoSuchFieldException e1) {
				// スーパクラスから対象のフィールドを探す
				superClazz = clazz.getSuperclass();
				while(!(superClazz instanceof Object)) {
					try {
						field = superClazz.getDeclaredField(property);
						field.setAccessible(true);
						result = field.get(obj);
					}catch(NoSuchFieldException e2) {
						// ignore
					}
					superClazz = superClazz.getSuperclass();
				}
			}
		}
		return result;
	}
	
	/**
	 * 指定したメソッドを実行する。
	 * 
	 * @param obj 対象オブジェクト
	 * @param methodName メソッド名
	 * @param parameter パラメータリスト
	 * @return 戻り値
	 * @throws Exception メソッドの実行に失敗した場合
	 */
	public static Object execute(Object obj, String methodName, List<Object> parameter) throws Exception {
		Class clazz = obj.getClass();
		Class superClazz = null;
		Method method = null;
		Object result = null;
		if(parameter == null) {
			try {
				method = clazz.getDeclaredMethod(methodName, (Class[])null);
				method.setAccessible(true);
				result = method.invoke(obj, (Object[])null);
			}catch(NoSuchMethodException e1) {
				superClazz = clazz.getSuperclass();
				while(!(superClazz instanceof Object)) {
					try {
						method = superClazz.getDeclaredMethod(methodName, (Class[])null);
						method.setAccessible(true);
						result = method.invoke(obj, (Object[])null);
					}catch(NoSuchMethodException e2) {
						// ignore
					}
					superClazz = superClazz.getSuperclass();
				}
			}
		}else {
			Object[] objList = parameter.toArray();
			Class[] classList = new Class[objList.length];
			for(int i = 0; i < objList.length; i++) {
				classList[i] = objList[i].getClass();
			}
			try {
				method = clazz.getDeclaredMethod(methodName, classList);
				method.setAccessible(true);
				result = method.invoke(obj, objList);
			}catch(NoSuchMethodException e1) {
				superClazz = clazz.getSuperclass();
				while(!(superClazz instanceof Object)) {
					try {
						method = superClazz.getDeclaredMethod(methodName, classList);
						method.setAccessible(true);
						result = method.invoke(obj, objList);
					}catch(NoSuchMethodException e2) {
						// ignore
					}
					superClazz = superClazz.getSuperclass();
				}
			}
		}
		return result;
	}

	/**
	 * 文字列で渡された値を対象オブジェクトのフィールドの型に合ったオブジェクトに
	 * 変換する。
	 * @param obj 対象オブジェクト
	 * @param fieldName フィールド名
	 * @param value 入力値
	 * @return 変換後の値
	 * @throws Exception 変換に失敗した場合
	 */
	public static Object convertObject(Object obj, String fieldName, String value) throws Exception {
		Class clazz = obj.getClass();
		Field field = clazz.getDeclaredField(fieldName);
		field.setAccessible(true);
		Class type = field.getType();
		if(value.startsWith("\"") && value.endsWith("\"")) {
			return value.substring(1, value.length() -1);
		}else if(type.getName().equals("byte") || type.getName().equals("java.lang.Byte")) {
			return new Byte(value);
		}else if(type.getName().equals("short") || type.getName().equals("java.lang.Short")) {
			return new Short(value);
		}else if(type.getName().equals("int") || type.getName().equals("java.lang.Integer")) {
			return new Integer(value);
		}else if(type.getName().equals("long") || type.getName().equals("java.lang.Long")) {
			return new Long(value);
		}else if(type.getName().equals("float") || type.getName().equals("java.lang.Float")) {
			return new Float(value);
		}else if(type.getName().equals("double") || type.getName().equals("java.lang.Double")) {
			return new Double(value);
		}else if(type.getName().equals("char") || type.getName().equals("java.lang.Character")) {
			return new Character(value.toCharArray()[0]);
		}
		return value;
	}
	
	/**
	 * 文字列で渡された値を対象オブジェクトのフィールドの型に合ったオブジェクトに
	 * 変換する。
	 * @param obj 対象オブジェクト
	 * @param value 入力値
	 * @return 変換後の値
	 * @throws Exception 変換に失敗した場合
	 */
	public static Object convertObject(Object obj, String value) throws Exception {
		Class clazz = obj.getClass();
		if(value.startsWith("\"") && value.endsWith("\"")) {
			return value.substring(1, value.length() -1);
		}else if(clazz.getName().equals("byte") || clazz.getName().equals("java.lang.Byte")) {
			return new Byte(value);
		}else if(clazz.getName().equals("short") || clazz.getName().equals("java.lang.Short")) {
			return new Short(value);
		}else if(clazz.getName().equals("int") || clazz.getName().equals("java.lang.Integer")) {
			return new Integer(value);
		}else if(clazz.getName().equals("long") || clazz.getName().equals("java.lang.Long")) {
			return new Long(value);
		}else if(clazz.getName().equals("float") || clazz.getName().equals("java.lang.Float")) {
			return new Float(value);
		}else if(clazz.getName().equals("double") || clazz.getName().equals("java.lang.Double")) {
			return new Double(value);
		}else if(clazz.getName().equals("char") || clazz.getName().equals("java.lang.Character")) {
			return new Character(value.toCharArray()[0]);
		}
		return value;
	}
	
	/**
	 * 文字列で渡された値を対象オブジェクトのフィールドの型に合ったオブジェクトに
	 * 変換する。
	 * @param obj 対象オブジェクト
	 * @param value 入力値
	 * @return 変換後の値
	 * @throws Exception 変換に失敗した場合
	 */
	public static String getFieldType(Class cls, String fieldName) throws Exception {
		Field[] fields = cls.getDeclaredFields();
		int len = fields.length;
		for(int i = 0; i < len; i++) {
			if(fields[i].getName().equals(fieldName)) {
				return fields[i].getType().getName();
			}
		}
		return null;
	}
	
}
