1
2
3
4
5
6
7
8
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
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
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
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
252 }
253 }
254 }