View Javadoc

    1   /*
    2    * Common Library for TBAS Softwares
    3    * Language: Java
    4    *
    5    * All Rights Reserved.
    6    * (c) Copyright 2002 by Tsukuba Bunko.
    7    *
    8    * $Id: GenericListener.java,v 1.1 2005/07/11 12:49:19 ppoi Exp $
    9    */
    10  package tsukuba_bunko.util;
    11
    12  import	java.lang.reflect.Method;
    13  import	java.lang.reflect.InvocationHandler;
    14  import	java.lang.reflect.InvocationTargetException;
    15  import	java.lang.reflect.Proxy;
    16
    17
    18  /***
    19   * イベントソースとイベントハンドラを接続するための枠組みを提供します. この枠組みを利用するためには,
    20   * Dynamic Proxy Class API を VM がサポートしている必要があります.
    21   * @author	$Author: ppoi $
    22   */
    23  public class GenericListener	{
    24
    25  	/***
    26  	 * <code>GenericListener</code> はインスタンスを生成できません。
    27  	 */
    28  	private GenericListener(){;}
    29
    30
    31  	/***
    32  	 * イベントソースとイベントハンドラを接続します。このメソッドで登録した場合、イベントハンドラはイベントソースと同じスレッドで実行されます。
    33  	 * @param	eventSource		イベントソース
    34  	 * @param	eventTarget		イベントハンドラ
    35  	 * @param	listenerClass	イベントソースに本来接続されるリスナクラス
    36  	 * @param	sourceMethod	イベントソースで呼び出されるメソッド名
    37  	 * @param	targetMethod	イベントハンドラで実際にイベントを処理するメソッド名
    38  	 * @return	イベントソースとイベントハンドラが接続された場合 <code>true</code>
    39  	 */
    40  	public static boolean connect( Object eventSource, Object eventTarget, Class listenerClass, String sourceMethod, String targetMethod )
    41  	{
    42  		return connect( eventSource, eventTarget, listenerClass, sourceMethod, targetMethod, false );
    43  	}
    44
    45  	/***
    46  	 * イベントソースとイベントハンドラを接続します。
    47  	 * 新しいスレッドを起動してイベントハンドラを実行する場合、戻り値を返すことは出来ません。また、例外の発生は通知されません。
    48  	 * @param	eventSource		イベントソース
    49  	 * @param	eventTarget		イベントハンドラ
    50  	 * @param	listenerClass	イベントソースに本来接続されるリスナクラス
    51  	 * @param	sourceMethod	イベントソースで呼び出されるメソッド名
    52  	 * @param	targetMethod	イベントハンドラで実際にイベントを処理するメソッド名
    53  	 * @param	newThread	新しいスレッドを起動してイベントハンドラを実行する場合 <code>true</code>、それ以外の場合 <code>false</code>
    54  	 * @return	イベントソースとイベントハンドラが接続された場合 <code>true</code>
    55  	 */
    56  	public static boolean connect( Object eventSource, Object eventTarget, Class listenerClass, String sourceMethod, String targetMethod, boolean newThread )
    57  	{
    58  		try	{
    59  		//	"add" メソッドの取得.
    60  		Method	addMethod = eventSource.getClass().getMethod( ("add" + identifierOf(listenerClass)), new Class[]{listenerClass} );
    61  		if( addMethod == null )	{
    62  			//	エラー
    63  			return false;
    64  		}
    65
    66  		//	リスナのインスタンスの取得.
    67  		Method	sourceMethodObject = getListenerMethod( listenerClass, sourceMethod );
    68  		Method	targetMethodObject = getTargetMethod( eventTarget.getClass(), targetMethod, sourceMethodObject.getParameterTypes() );
    69  		Object	listenerImple = Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[]{listenerClass}, new InvocHandler(eventTarget, targetMethodObject, sourceMethodObject, newThread) );
    70
    71  		addMethod.invoke( eventSource, new Object[]{listenerImple} );
    72  		}
    73  		catch( Exception e )
    74  		{
    75  			e.printStackTrace();
    76  			return false;
    77  		}
    78
    79  		return true;
    80  	}
    81
    82  	/***
    83  	 * 指定されたクラスの Identifier を取得します. ここでの Identidier はクラス宣言の際に
    84  	 *用いられるクラス名のことを指します.
    85  	 * @param	c	Identifier を取得するクラス
    86  	 * @return	Identifier
    87  	 */
    88  	private static String identifierOf( Class c )
    89  	{
    90  		String	className = c.getName();
    91  		return className.substring( className.lastIndexOf('.') + 1, className.length() );
    92  	}
    93
    94  	/***
    95  	 * イベントソースで呼び出されるメソッドを取得します.
    96  	 * @param	listenerClass	リスナクラス
    97  	 * @param	methodName		メソッド名
    98  	 */
    99  	private static Method getListenerMethod( Class listenerClass, String methodName )
    100 		throws NoSuchMethodException
    101 	{
    102 		Method	methods[] = listenerClass.getMethods();
    103 		for( int i = methods.length - 1; i >= 0; --i )	{
    104 			if( methodName.equals(methods[i].getName()) )	{
    105 				return methods[i];
    106 			}
    107 		}
    108 		throw new NoSuchMethodException( "No such method \"" + methodName + "\" in \"" + listenerClass.getName() + "\"." );
    109 	}
    110
    111 	/***
    112 	 * 指定されたクラスにおいて指定された名前で定義されたメソッドを取得します.
    113 	 * @param	c	メソッドが定義されているクラス
    114 	 * @param	methodName	メソッド名
    115 	 * @param	parameterTypes	仮引数の型リスト
    116 	 */
    117 	private static Method getTargetMethod( Class c, String methodName, Class[] parameterTypes )
    118 		throws NoSuchMethodException
    119 	{
    120 		Method	method = null;
    121 		try	{
    122 			method = c.getDeclaredMethod( methodName, parameterTypes );
    123 		}
    124 		catch( NoSuchMethodException nsme )	{
    125 			method = c.getDeclaredMethod( methodName, null );
    126 		}
    127
    128 		try	{
    129 			method.setAccessible( true );
    130 		}
    131 		catch( SecurityException se )	{
    132 			//	ignore.
    133 		}
    134 		return method;
    135 	}
    136 }
    137
    138
    139 /***
    140  * <code>InvocationHandler</code> の実装です.
    141  */
    142 class InvocHandler	implements InvocationHandler	{
    143
    144 	/***
    145 	 * 処理を実際に行うクラス
    146 	 */
    147 	private Object	_target =null;
    148
    149 	/***
    150 	 * 処理を実際に行うメソッド
    151 	 */
    152 	private Method	_targetMethod = null;
    153
    154 	/***
    155 	 * イベントソースによって呼び出されるメソッド
    156 	 */
    157 	private Method	_sourceMethod = null;
    158
    159 	/***
    160 	 * 別スレッドを起動するかどうか
    161 	 */
    162 	private boolean	_newThread = false;
    163
    164
    165 	/***
    166 	 * <code>InvocHandler</code> のインスタンスを生成します.
    167 	 * @param	target	実際のイベントハンドラ
    168 	 * @param	targetMethod	実際に処理を行うメソッド
    169 	 * @param	sourceMethod	イベントソースによって呼び出されるメソッド
    170 	 */
    171 	InvocHandler( Object target, Method targetMethod, Method sourceMethod, boolean newThread )
    172 	{
    173 		_target = target;
    174 		_targetMethod = targetMethod;
    175 		_sourceMethod = sourceMethod;
    176 		_newThread = newThread;
    177 	}
    178
    179
    180 //
    181 //	InvocationHandler の実装
    182 //
    183 	/***
    184 	 * Processes a method invocation on a proxy instance and returns the result. This method
    185 	 * will be invoked on an invocation handler when a method is invoked on a proxy instance
    186 	 * that it is associated with.
    187 	 * @param	proxy	the proxy instance that the method was invoked on
    188 	 * @param	meth	the Method instance corresponding to the interface method invoked on
    189 	 * the proxy instance. The declaring class of the Method object will be the interface that
    190 	 * the method was declared in, which may be a superinterface of the proxy interface that
    191 	 * the proxy class inherits the method through.
    192 	 * @param	args	an array of objects containing the values of the arguments passed in
    193 	 * the method invocation on the proxy instance, or null if interface method takes no arguments.
    194 	 * Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper
    195 	 * class, such as <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>.
    196 	 * @return	the value to return from the method invocation on the proxy instance. If the
    197 	 * declared return type of the interface method is a primitive type, then the value returned
    198 	 * by this method must be an instance of the corresponding primitive wrapper class; otherwise,
    199 	 * it must be a type assignable to the declared return type. If the value returned by this
    200 	 * method is <code>null</code> and the interface method's return type is primitive, then a
    201 	 * <code>NullPointerException<code> will be thrown by the method invocation on the proxy
    202 	 * instance. If the value returned by this method is otherwise not compatible with the
    203 	 * interface method's declared return type as described above, a <code>ClassCastException</code>
    204 	 * will be thrown by the method invocation on the proxy instance
    205 	 */
    206 	public Object invoke( Object proxy, Method meth, Object[] args )
    207 		throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    208 	{
    209 		if( !_sourceMethod.equals(meth) )	{
    210 			return null;
    211 		}
    212
    213 		if( _targetMethod.getParameterTypes().length == 0 )	{
    214 			args = null;
    215 		}
    216
    217 		if( _newThread )	{
    218 			new Thread( new InvokerRunner(_target, _targetMethod, args) ).start();
    219 			return null;
    220 		}
    221 		else	{
    222 			return _targetMethod.invoke( _target, args );
    223 		}
    224 	}
    225 }
    226
    227 class InvokerRunner implements Runnable	{
    228
    229 	protected Object	_target = null;
    230
    231 	protected Method	_method = null;
    232
    233 	protected Object[]	_args = null;
    234
    235
    236 	InvokerRunner( Object target, Method method, Object[] args )
    237 	{
    238 		super();
    239 		_target = target;
    240 		_method = method;
    241 		_args = args;
    242 	}
    243
    244
    245 	public void run()
    246 	{
    247 		try	{
    248 			_method.invoke( _target, _args );
    249 		}
    250 		catch( Exception e )	{
    251 			//ignore.
    252 		}
    253 	}
    254 }