/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.utils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Beanを操作するユーティリティです。
 * @author nakamura
 *
 */
public final class BeanUtils {
	private static final Pattern LOCAL_CLASS_NAME = Pattern.compile("([^\\.]+\\.)*([^\\.]*)");
	
	private BeanUtils() {
	}

	/**
	 * パッケージ名を除いたクラス名を返します。
	 * @param clazz
	 * @return パッケージ名を除いたクラス名。
	 */
	public static String getLocalClassName(final Class clazz) {
		final Matcher m = LOCAL_CLASS_NAME.matcher(clazz.getName());
		m.matches();
		return m.group(2);
	}

	/**
	 * object に対して method を引数無しで実行しその結果を返します。
	 * 
	 * @param object 作用対象。
	 * @param method 実行するメソッド。
	 * @return method を実行した後の返却値。
	 * @throws IllegalArgumentException メソッド実行で発生した場合。
	 * @throws IllegalStateException ({@link IllegalAccessException}, {@link InvocationTargetException})メソッド実行で発生した場合。
	 */
	public static Object get(final Object object, final Method method) {
		try {
			return method.invoke(object, new Object[0]);
		} catch (final IllegalAccessException e) {
			throw new IllegalStateException(e);
		} catch (final InvocationTargetException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * クラスのゲッターメソッド一覧を返します。
	 * 
	 * @param clazz 調査対象クラス。
	 * @return ゲッターメソッド一覧。
	 * @throws IllegalArgumentException ({@link IntrospectionException})clazz の解析に失敗した場合。
	 */
	public static List<PropertyDescriptor> getGetterList(final Class clazz) {
		final BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(clazz);
		} catch (final IntrospectionException e) {
			throw new IllegalArgumentException(e);
		}
		final List<PropertyDescriptor> list = new ArrayList<PropertyDescriptor>(
				beanInfo.getPropertyDescriptors().length);
		for (final PropertyDescriptor p : beanInfo.getPropertyDescriptors()) {
			final Method m = p.getReadMethod();
			if (m == null) {
				continue;
			}
			final String key = p.getName();
			if ("class".equals(key)) {
				continue;
			}
			list.add(p);
		}
		return list;
	}

	/**
	 * コンストラクタ引数とゲッターメソッド一覧が一致する場合に
	 * コンストラクタの引数の順序で並べたゲッターメソッド一覧を返します。
	 * このメソッドはコンストラクタ引数の順序を調べるために
	 * 引数のクラスオブジェクトを用いてオブジェクトを生成します。
	 * オブジェクト生成によりオブジェクト外のリソースに副作用が発生する
	 * 可能性がある場合はこのメソッドを使用すべきではありません。
	 * 
	 * @param object 調査対象クラス。
	 * @return ゲッターメソッド一覧。
	 * @throws IllegalArgumentException ({@link IntrospectionException})clazz の解析に失敗した場合またはゲッターメソッド一覧と一致するコンストラクタが存在しない場合。
	 */
	public static List<PropertyDescriptor> getConstructorGetterList(final Object object) {
		final Class clazz = object.getClass();
		final List<PropertyDescriptor> getterList = getGetterList(clazz);
		final int length = getterList.size();
		final Class[] baseClassArray = new Class[length];
		final Object[] baseValueArray = new Object[length];
		for (int i = 0; i < length; i++) {
			final PropertyDescriptor p = getterList.get(i);
			baseClassArray[i] = p.getPropertyType();
			baseValueArray[i] = get(object, p.getReadMethod());
		}
		final Class[] tmpClassArray = new Class[length];
		final Object[] tmpValueArray = new Object[length];
		final int[] a = new int[length];
		for (final Constructor constructor : clazz.getConstructors()) {
			final Class[] classArray = constructor.getParameterTypes();
			if (classArray.length != length) {
				continue;
			}
			if (classArray.length == 0) {
				if (length == 0) {
					return Collections.emptyList();
				}
				continue;
			}
			final Permutation pe = new Permutation(length, a);
			myloop: while (pe.next()) {
				for (int i = 0; i < length; i++) {
					tmpClassArray[i] = baseClassArray[a[i]];
				}
				if (!Arrays.equals(classArray, tmpClassArray)) {
					continue;
				}
				for (int i = 0; i < length; i++) {
					tmpValueArray[i] = baseValueArray[a[i]];
				}
				final Object newObject;
				try {
					newObject = constructor.newInstance(tmpValueArray);
				} catch (final IllegalArgumentException e) {
					continue;
				} catch (final InstantiationException e) {
					continue;
				} catch (final IllegalAccessException e) {
					continue;
				} catch (final InvocationTargetException e) {
					continue;
				}
				for (int i = 0; i < length; i++) {
					final PropertyDescriptor p = getterList.get(i);
					final Object o0 = baseValueArray[i];
					final Object o1 = get(newObject, p.getReadMethod());
					if (o0 == null ? o1 != null : !o0.equals(o1)) {
						continue myloop;
					}
				}
				final List<PropertyDescriptor> newList = new ArrayList<PropertyDescriptor>();
				for (int i = 0; i < length; i++) {
					newList.add(getterList.get(a[i]));
				}
				return newList;
			}
		}
		throw new IllegalArgumentException("not match constructor and parameter : " + object);
	}
}
