JFC(Swing)と負の遺産(その2)
前回のエントリではSwingのイベント処理に関して、任意のコードを任意のイベントにアタッチするには
1.イベント(イベントID)に合わせたリスナオブジェクトを生成しなくてはならない
2.生成したリスナオブジェクトに合わせたリスナ登録メソッドを実行しなくてはならない
これらの問題があると書いた。
このうち、1.は未だ解決していない。やはりイベントに合わせたリスナオブジェクトを生成しなくてはならない。
protected EventListener createEventListener(final int eventId, final IAction action) { //ActionListener if((eventId & AWTEvent.ACTION_EVENT_MASK) != 0) return new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { action.runEvent(e); }}; //AdjustmentListener if((eventId & AWTEvent.ADJUSTMENT_EVENT_MASK) != 0) return new AdjustmentListener(){ @Override public void adjustmentValueChanged(AdjustmentEvent e) { action.runEvent(e); }}; if((eventId & AWTEvent.COMPONENT_EVENT_MASK) != 0) : :
しかし、後者に関してはリフレクションを使うことにより若干コードを短くすることが出来そうだ。(今のJavaの現状だ。困ったときのリフレクション、何が無くともリフレクション)
上記で生成したイベントID毎のリスナの実装は匿名クラスであるため、インタフェースは一つしか実装しないはず、なのでそれを利用してインタフェースを取得、そのインタフェース名から登録メソッドを類推して探し、実行することができる。メソッド名の類推といっても、イベントリスナの登録メソッドは幸いにも"add"+"リスナ名称"で統一されているので、以下のようなメソッドを書くことができる。
private void doAddSuitableLister(EventListener el, Object component) { Class listenerInterface = el.getClass().getInterfaces()[0]; String listenerName = listenerInterface.getSimpleName(); Class clazz = component.getClass(); String methodName = "add" + listenerName; try { Method method = clazz.getMethod(methodName, listenerInterface); method.invoke(component, el); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } }
ベタだしリスナ登録メソッドの命名規約が対応していない場合にはリスナ名をメソッド名の中に持つ、メソッドの一覧を抽出して引数の型が合致するメソッドを選び出さなくてはならず実装が破綻してしまうが、現状はこれで凌ぐしかなさそうだ。
前者の問題に関しては、AWTEventListenerというAWTのイベントを全て捕捉するリスナがあることを知った。今度はこいつを使ってもう少しすっきりしたコードが書けるか試してみようと思う。
それにしてもこのようにイベント処理に関して苦労していると、.NET C#のデリゲートによるイベントの実装の良さ(メソッドシグネチャを型として扱える、デリゲートをメソッドのポインタやリストのように扱える)が身に沁みて解るな。