/*
 * Common Library for TBAS Softwares
 * Language: Java
 *
 * All Rights Reserved.
 * (c) Copyright 2002 by Tsukuba Bunko.
 *
 * $Id: GenericListener.java,v 1.1 2003/08/31 08:20:49 ppoi Exp $
 */
package tsukuba_bunko.util;

import	java.lang.reflect.Method;
import	java.lang.reflect.InvocationHandler;
import	java.lang.reflect.InvocationTargetException;
import	java.lang.reflect.Proxy;


/**
 * イベントソースとイベントハンドラを接続するための枠組みを提供します. この枠組みを利用するためには,
 * Dynamic Proxy Class API を VM がサポートしている必要があります.
 * @author	$Author: ppoi $
 */
public class GenericListener	{

	/**
	 * <code>GenericListener</code> はインスタンスを生成できません。
	 */
	private GenericListener(){;}


	/**
	 * イベントソースとイベントハンドラを接続します。このメソッドで登録した場合、イベントハンドラはイベントソースと同じスレッドで実行されます。
	 * @param	eventSource		イベントソース
	 * @param	eventTarget		イベントハンドラ
	 * @param	listenerClass	イベントソースに本来接続されるリスナクラス
	 * @param	sourceMethod	イベントソースで呼び出されるメソッド名
	 * @param	targetMethod	イベントハンドラで実際にイベントを処理するメソッド名
	 * @return	イベントソースとイベントハンドラが接続された場合 <code>true</code>
	 */
	public static boolean connect( Object eventSource, Object eventTarget, Class listenerClass, String sourceMethod, String targetMethod )
	{
		return connect( eventSource, eventTarget, listenerClass, sourceMethod, targetMethod, false );
	}

	/**
	 * イベントソースとイベントハンドラを接続します。
	 * 新しいスレッドを起動してイベントハンドラを実行する場合、戻り値を返すことは出来ません。また、例外の発生は通知されません。
	 * @param	eventSource		イベントソース
	 * @param	eventTarget		イベントハンドラ
	 * @param	listenerClass	イベントソースに本来接続されるリスナクラス
	 * @param	sourceMethod	イベントソースで呼び出されるメソッド名
	 * @param	targetMethod	イベントハンドラで実際にイベントを処理するメソッド名
	 * @param	newThread	新しいスレッドを起動してイベントハンドラを実行する場合 <code>true</code>、それ以外の場合 <code>false</code>
	 * @return	イベントソースとイベントハンドラが接続された場合 <code>true</code>
	 */
	public static boolean connect( Object eventSource, Object eventTarget, Class listenerClass, String sourceMethod, String targetMethod, boolean newThread )
	{
		try	{
		//	"add" メソッドの取得.
		Method	addMethod = eventSource.getClass().getMethod( ("add" + identifierOf(listenerClass)), new Class[]{listenerClass} );
		if( addMethod == null )	{
			//	エラー
			return false;
		}

		//	リスナのインスタンスの取得.
		Method	sourceMethodObject = getListenerMethod( listenerClass, sourceMethod );
		Method	targetMethodObject = getTargetMethod( eventTarget.getClass(), targetMethod, sourceMethodObject.getParameterTypes() );
		Object	listenerImple = Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[]{listenerClass}, new InvocHandler(eventTarget, targetMethodObject, sourceMethodObject, newThread) );

		addMethod.invoke( eventSource, new Object[]{listenerImple} );
		}
		catch( Exception e )
		{
			e.printStackTrace();
			return false;
		}

		return true;
	}

	/**
	 * 指定されたクラスの Identifier を取得します. ここでの Identidier はクラス宣言の際に
	 *用いられるクラス名のことを指します.
	 * @param	c	Identifier を取得するクラス
	 * @return	Identifier
	 */
	private static String identifierOf( Class c )
	{
		String	className = c.getName();
		return className.substring( className.lastIndexOf('.') + 1, className.length() );
	}

	/**
	 * イベントソースで呼び出されるメソッドを取得します.
	 * @param	listenerClass	リスナクラス
	 * @param	methodName		メソッド名
	 */
	private static Method getListenerMethod( Class listenerClass, String methodName )
		throws NoSuchMethodException
	{
		Method	methods[] = listenerClass.getMethods();
		for( int i = methods.length - 1; i >= 0; --i )	{
			if( methodName.equals(methods[i].getName()) )	{
				return methods[i];
			}
		}
		throw new NoSuchMethodException( "No such method \"" + methodName + "\" in \"" + listenerClass.getName() + "\"." );
	}

	/**
	 * 指定されたクラスにおいて指定された名前で定義されたメソッドを取得します.
	 * @param	c	メソッドが定義されているクラス
	 * @param	methodName	メソッド名
	 * @param	parameterTypes	仮引数の型リスト
	 */
	private static Method getTargetMethod( Class c, String methodName, Class[] parameterTypes )
		throws NoSuchMethodException
	{
		Method	method = null;
		try	{
			method = c.getDeclaredMethod( methodName, parameterTypes );
		}
		catch( NoSuchMethodException nsme )	{
			method = c.getDeclaredMethod( methodName, null );
		}

		try	{
			method.setAccessible( true );
		}
		catch( SecurityException se )	{
			//	ignore.
		}
		return method;
	}
}


/**
 * <code>InvocationHandler</code> の実装です.
 */
class InvocHandler	implements InvocationHandler	{

	/**
	 * 処理を実際に行うクラス
	 */
	private Object	_target =null;

	/**
	 * 処理を実際に行うメソッド
	 */
	private Method	_targetMethod = null;

	/**
	 * イベントソースによって呼び出されるメソッド
	 */
	private Method	_sourceMethod = null;

	/**
	 * 別スレッドを起動するかどうか
	 */
	private boolean	_newThread = false;


	/**
	 * <code>InvocHandler</code> のインスタンスを生成します.
	 * @param	target	実際のイベントハンドラ
	 * @param	targetMethod	実際に処理を行うメソッド
	 * @param	sourceMethod	イベントソースによって呼び出されるメソッド
	 */
	InvocHandler( Object target, Method targetMethod, Method sourceMethod, boolean newThread )
	{
		_target = target;
		_targetMethod = targetMethod;
		_sourceMethod = sourceMethod;
		_newThread = newThread;
	}


//
//	InvocationHandler の実装
//
	/**
	 * Processes a method invocation on a proxy instance and returns the result. This method
	 * will be invoked on an invocation handler when a method is invoked on a proxy instance
	 * that it is associated with.
	 * @param	proxy	the proxy instance that the method was invoked on
	 * @param	meth	the Method instance corresponding to the interface method invoked on
	 * the proxy instance. The declaring class of the Method object will be the interface that
	 * the method was declared in, which may be a superinterface of the proxy interface that
	 * the proxy class inherits the method through.
	 * @param	args	an array of objects containing the values of the arguments passed in
	 * the method invocation on the proxy instance, or null if interface method takes no arguments.
	 * Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper
	 * class, such as <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>.
	 * @return	the value to return from the method invocation on the proxy instance. If the
	 * declared return type of the interface method is a primitive type, then the value returned
	 * by this method must be an instance of the corresponding primitive wrapper class; otherwise,
	 * it must be a type assignable to the declared return type. If the value returned by this
	 * method is <code>null</code> and the interface method's return type is primitive, then a
	 * <code>NullPointerException<code> will be thrown by the method invocation on the proxy
	 * instance. If the value returned by this method is otherwise not compatible with the
	 * interface method's declared return type as described above, a <code>ClassCastException</code>
	 * will be thrown by the method invocation on the proxy instance
	 */
	public Object invoke( Object proxy, Method meth, Object[] args )
		throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
	{
		if( !_sourceMethod.equals(meth) )	{
			return null;
		}

		if( _targetMethod.getParameterTypes().length == 0 )	{
			args = null;
		}

		if( _newThread )	{
			new Thread( new InvokerRunner(_target, _targetMethod, args) ).start();
			return null;
		}
		else	{
			return _targetMethod.invoke( _target, args );
		}
	}
}

class InvokerRunner implements Runnable	{

	protected Object	_target = null;

	protected Method	_method = null;

	protected Object[]	_args = null;


	InvokerRunner( Object target, Method method, Object[] args )
	{
		super();
		_target = target;
		_method = method;
		_args = args;
	}


	public void run()
	{
		try	{
			_method.invoke( _target, _args );
		}
		catch( Exception e )	{
			//ignore.
		}
	}
}